EMA Crossovers with Resampled SMA Trend Filtering
Summary
This Freqtrade example combines short- and long-period exponential moving average crossovers with a simple moving average calculated on a resampled, longer interval. A long entry requires the close to be above that resampled average and the short EMA to cross above the long EMA; short entries reverse both conditions. The design therefore pairs a local crossover trigger with a broader trend filter.
The strategy also calculates ADX values across candidate periods and defines configurable ADX thresholds, but its entry rules do not use them. Exits for both long and short positions occur when ADX falls below the configured entry threshold. Bollinger Bands are calculated for chart display and do not affect signals. The example sets a five-minute timeframe, allows shorting, and specifies ROI and stop-loss settings. It provides implementation details but no backtest results, so profitability, robustness, and the effects of the parameters are not established.
Key ideas
- A long entry requires price above a resampled longer-interval SMA and a bullish short-versus-long EMA crossover.
- A short entry uses price below the resampled SMA and a bearish EMA crossover.
- Both position directions exit when the selected ADX value falls below the configured entry threshold.
- The code calculates Bollinger Bands for display, while its entry rules do not use the configured ADX thresholds.
- No performance evidence is included, so the strategy requires independent testing.
Tags
Full text
# FReinforcedStrategy
# FReinforcedStrategy
## Source (GPL-3.0)
```python
# 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.