Skip to content
All library documents

A Multi-Timeframe EMA Crossover Strategy with ADX Exits

Code Freqtrade

Summary

This Freqtrade strategy template describes a short-term, long-and-short system using a five-minute chart. It enters when price is above or below a simple moving average calculated on a resampled, longer interval, and a short-period EMA crosses the corresponding long-period EMA in the same direction. The strategy calculates Bollinger Bands for charting and multiple ADX and EMA values for parameter selection. It exits both long and short positions when ADX falls below a configurable threshold, with a fixed stop loss and a tiered minimum-return schedule also specified.

The code exposes several indicator periods and thresholds as tunable parameters, but the excerpt gives no optimization procedure, backtest results, market selection, or execution assumptions. Although an ADX exit threshold is parameterized for selling, the exit condition shown uses the entry threshold instead. The template also enables shorting, so its practical use depends on exchange and market support. No evidence is given that the strategy is profitable or robust across assets and regimes.

Key ideas

  • Entries require agreement between price relative to a resampled moving average and a directional EMA crossover.
  • The strategy supports both long and short entries on a five-minute timeframe.
  • ADX is used as an exit condition, while stop loss and minimum-return settings provide additional trade controls.
  • EMA and ADX periods are exposed as tunable parameters, but no optimization or validation results are reported.
  • The shown exit condition references the entry ADX threshold despite a separate exit threshold being defined.

Tags

Full text
# FReinforcedStrategy.py


```py
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
# isort: skip_file
# --- Do not remove these libs ---
from functools import reduce
import numpy as np  # noqa
import pandas as pd  # noqa
from pandas import DataFrame

from freqtrade.strategy import (
    BooleanParameter,
    CategoricalParameter,
    DecimalParameter,
    IStrategy,
    IntParameter,
)

# --------------------------------
# Add your lib to import here
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.exchange import timeframe_to_minutes
from technical.util import resample_to_interval, resampled_merge


# This class is a sample. Feel free to customize it.
class FReinforcedStrategy(IStrategy):

    INTERFACE_VERSION = 3
    timeframe = "5m"
    # Minimal ROI designed for the strategy.
    # This attribute will be overridden if the config file contains "minimal_roi".
    minimal_roi = {"60": 0.075, "30": 0.1, "0": 0.05}
    # minimal_roi = {"0": 1}

    stoploss = -0.05
    can_short = True

    # Trailing stoploss
    trailing_stop = False
    # trailing_only_offset_is_reached = False
    # trailing_stop_positive = 0.01
    # trailing_stop_positive_offset = 0.0  # Disabled / not configured

    # Run "populate_indicators()" only for new candle.
    process_only_new_candles = True

    # Number of candles the strategy requires before producing valid signals
    startup_candle_count: int = 14

    # Hyperoptable parameters

    # Define the guards spaces
    pos_entry_adx = DecimalParameter(15, 40, decimals=1, default=30.0, space="buy")
    pos_exit_adx = DecimalParameter(15, 40, decimals=1, default=30.0, space="sell")

    # Define the parameter spaces
    adx_period = IntParameter(4, 24, default=14)
    ema_short_period = IntParameter(4, 24, default=8)
    ema_long_period = IntParameter(12, 175, default=21)

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

        # Calculate all adx values
        for val in self.adx_period.range:
            dataframe[f"adx_{val}"] = ta.ADX(dataframe, timeperiod=val)

        # Calculate all ema_short values
        for val in self.ema_short_period.range:
            dataframe[f"ema_short_{val}"] = ta.EMA(dataframe, timeperiod=val)

        # Calculate all ema_long values
        for val in self.ema_long_period.range:
            dataframe[f"ema_long_{val}"] = ta.EMA(dataframe, timeperiod=val)

        # required for graphing
        bollinger = qtpylib.bollinger_bands(dataframe["close"], window=20, stds=2)
        dataframe["bb_lowerband"] = bollinger["lower"]
        dataframe["bb_upperband"] = bollinger["upper"]
        dataframe["bb_middleband"] = bollinger["mid"]

        self.resample_interval = timeframe_to_minutes(self.timeframe) * 12
        dataframe_long = resample_to_interval(dataframe, self.resample_interval)
        dataframe_long["sma"] = ta.SMA(dataframe_long, timeperiod=50, price="close")
        dataframe = resampled_merge(dataframe, dataframe_long, fill_na=True)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions_long = []
        conditions_short = []

        # GUARDS AND TRIGGERS
        conditions_long.append(
            dataframe["close"] > dataframe[f"resample_{self.resample_interval}_sma"]
        )

        conditions_short.append(
            dataframe["close"] < dataframe[f"resample_{self.resample_interval}_sma"]
        )

        conditions_long.append(
            qtpylib.crossed_above(
                dataframe[f"ema_short_{self.ema_short_period.value}"],
                dataframe[f"ema_long_{self.ema_long_period.value}"],
            )
        )
        conditions_short.append(
            qtpylib.crossed_below(
                dataframe[f"ema_short_{self.ema_short_period.value}"],
                dataframe[f"ema_long_{self.ema_long_period.value}"],
            )
        )

        dataframe.loc[
            reduce(lambda x, y: x & y, conditions_long),
            "enter_long",
        ] = 1

        dataframe.loc[
            reduce(lambda x, y: x & y, conditions_short),
            "enter_short",
        ] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

        conditions_close = []
        conditions_close.append(
            dataframe[f"adx_{self.adx_period.value}"] < self.pos_entry_adx.value
        )

        dataframe.loc[
            reduce(lambda x, y: x & y, conditions_close),
            "exit_long",
        ] = 1

        dataframe.loc[
            reduce(lambda x, y: x & y, conditions_close),
            "exit_short",
        ] = 1

        return dataframe

```

Shown in full with attribution under the source's licence. Licence: GPL-3.0

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.