Timeframe
5m
Direction
Long Only
Stoploss
-8.0%
Trailing Stop
Yes
ROI
0m: 3.0%
Interface Version
N/A
Startup Candles
210
Indicators
3
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy import IntParameter, DecimalParameter
from pandas import DataFrame
import talib.abstract as ta
class TrendFollowingStrategy(IStrategy):
can_short: bool = True
timeframe = '5m'
# Basic ROI/SL - keep modest defaults; can be tuned later
minimal_roi = {
"0": 0.03
}
stoploss = -0.08
trailing_stop = True
# Ensure we have enough candles for indicators (BB 20, EMA 200, etc.)
startup_candle_count = 210
# ---- Hyperoptable parameters (buy/sell) to enable default spaces ----
# These unlock hyperopt for this strategy (avoid "no parameter for this space" error)
buy_rsi = IntParameter(45, 60, default=50, space='buy')
buy_bb_mid_offset = DecimalParameter(0.0, 0.02, decimals=3, default=0.0, space='buy')
sell_rsi = IntParameter(40, 55, default=50, space='sell')
sell_bb_mid_offset = DecimalParameter(0.0, 0.02, decimals=3, default=0.0, space='sell')
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Moving averages
dataframe['ema_short'] = ta.EMA(dataframe, timeperiod=9)
dataframe['ema_long'] = ta.EMA(dataframe, timeperiod=21)
dataframe['ema_trend'] = ta.EMA(dataframe, timeperiod=200)
# Bollinger Bands (20, 2)
bb_upper, bb_middle, bb_lower = ta.BBANDS(
dataframe['close'], timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0
)
dataframe['bb_upper'] = bb_upper
dataframe['bb_mid'] = bb_middle
dataframe['bb_lower'] = bb_lower
# RSI momentum
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
# Volume (ensure positive)
dataframe['volume_mean_slow'] = dataframe['volume'].rolling(30).mean()
return dataframe
def leverage(self, pair, current_time, current_rate, proposed_leverage, max_leverage, entry_tag, side, **kwargs) -> float:
stop = abs(float(self.stoploss)) if getattr(self, "stoploss", None) is not None else 0.08
base = 0.05 / stop if stop > 0 else (proposed_leverage or 1.0)
base = max(1.0, min(float(base), float(max_leverage)))
return base
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Long entry: EMA9 > EMA21, close breaks above BB mid (trend confirmation), RSI > 50, volume ok
dataframe.loc[
(
(dataframe['ema_short'] > dataframe['ema_long']) &
(dataframe['close'] > dataframe['bb_mid'] * (1 + float(self.buy_bb_mid_offset.value))) &
(dataframe['rsi'] > int(self.buy_rsi.value)) &
(dataframe['close'] > dataframe['ema_trend']) &
(dataframe['volume'] > 0) &
(dataframe['volume'] > dataframe['volume_mean_slow'])
),
'enter_long'
] = 1
# Short entry: EMA9 < EMA21, close below BB mid, RSI < 50, price below long EMA, volume ok
dataframe.loc[
(
(dataframe['ema_short'] < dataframe['ema_long']) &
(dataframe['close'] < dataframe['bb_mid'] * (1 - float(self.buy_bb_mid_offset.value))) &
(dataframe['rsi'] < 100 - int(self.buy_rsi.value)) &
(dataframe['close'] < dataframe['ema_trend']) &
(dataframe['volume'] > 0) &
(dataframe['volume'] > dataframe['volume_mean_slow'])
),
'enter_short'
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Long exit: Cross back down (EMA9 < EMA21) or mean reversion (close < BB mid) or RSI loss of momentum
dataframe.loc[
(
(dataframe['ema_short'] < dataframe['ema_long']) |
(dataframe['close'] < dataframe['bb_mid'] * (1 - float(self.sell_bb_mid_offset.value))) |
(dataframe['rsi'] < int(self.sell_rsi.value))
),
'exit_long'
] = 1
# Short exit: Cross back up (EMA9 > EMA21) or mean reversion (close > BB mid) or RSI momentum fades
dataframe.loc[
(
(dataframe['ema_short'] > dataframe['ema_long']) |
(dataframe['close'] > dataframe['bb_mid'] * (1 + float(self.sell_bb_mid_offset.value))) |
(dataframe['rsi'] > 100 - int(self.sell_rsi.value))
),
'exit_short'
] = 1
return dataframe