Improved RSI + EMA + MACD Strategy.
Timeframe
5m
Direction
Long Only
Stoploss
-8.0%
Trailing Stop
Yes
ROI
0m: 22.5%, 39m: 3.7%, 73m: 2.2%, 110m: 0.0%
Interface Version
3
Startup Candles
N/A
Indicators
5
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
import numpy as np
import pandas as pd
from pandas import DataFrame
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
class ImprovedStrategy(IStrategy):
"""
Improved RSI + EMA + MACD Strategy.
Enhancements over SampleStrategy:
- ADX-adaptive EMA trend filter (EMA-168 strong trend, EMA-50 sideways)
- RSI crossover entry (momentum shift, not just level)
- MACD bullish confirmation
- Volume filter (70% of average)
- ATR-based dynamic stop loss (1.5x ATR, capped 2-8%)
- Trailing stop (activates after 18.5% profit)
Hyperopt results (50 epochs): buy_rsi=30, sell_rsi=78, ema_period=168
ROI: 22.5% at 0m, 3.7% at 39m, 2.2% at 73m, 0% at 110m
"""
INTERFACE_VERSION = 3
buy_rsi = IntParameter(20, 40, default=30, space="buy")
sell_rsi = IntParameter(60, 85, default=78, space="sell")
ema_period = IntParameter(50, 250, default=168, space="buy")
adx_threshold = IntParameter(20, 35, default=25, space="buy")
volume_factor = DecimalParameter(0.5, 1.5, default=0.7, space="buy")
minimal_roi = {"110": 0, "73": 0.022, "39": 0.037, "0": 0.225}
stoploss = -0.08
trailing_stop = True
trailing_stop_positive = 0.185
trailing_stop_positive_offset = 0.313
trailing_only_offset_is_reached = True
timeframe = "5m"
process_only_new_candles = True
startup_candle_count: int = 250
def custom_stoploss(self, pair: str, trade, current_time, current_rate: float,
current_profit: float, after_fill: bool, **kwargs) -> float:
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if len(dataframe) < 1:
return self.stoploss
last_candle = dataframe.iloc[-1]
if "atr" in last_candle and last_candle["atr"] > 0:
atr_stop = -(1.5 * last_candle["atr"] / current_rate)
return max(min(atr_stop, -0.02), -0.08)
return self.stoploss
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
dataframe["rsi_prev"] = dataframe["rsi"].shift(1)
dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
dataframe["ema_strong"] = ta.EMA(dataframe, timeperiod=self.ema_period.value)
dataframe["ema_weak"] = ta.EMA(dataframe, timeperiod=50)
dataframe["ema_filter"] = np.where(
dataframe["adx"] > self.adx_threshold.value,
dataframe["ema_strong"],
dataframe["ema_weak"]
)
macd = ta.MACD(dataframe)
dataframe["macd"] = macd["macd"]
dataframe["macdsignal"] = macd["macdsignal"]
dataframe["macdhist"] = macd["macdhist"]
dataframe["volume_mean"] = dataframe["volume"].rolling(window=20).mean()
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(dataframe["rsi"] > self.buy_rsi.value) &
(dataframe["rsi_prev"] <= self.buy_rsi.value) &
(dataframe["close"] > dataframe["ema_filter"]) &
(dataframe["macd"] > dataframe["macdsignal"]) &
(dataframe["volume"] > self.volume_factor.value * dataframe["volume_mean"]) &
(dataframe["volume"] > 0)
),
"enter_long",
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(dataframe["rsi"] > self.sell_rsi.value) &
(dataframe["volume"] > 0)
),
"exit_long",
] = 1
return dataframe