Timeframe
1h
Direction
Long Only
Stoploss
-5.0%
Trailing Stop
Yes
ROI
0m: 10.0%, 360m: 5.0%, 720m: 2.0%
Interface Version
3
Startup Candles
200
Indicators
2
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
# ══════════════════════════════════════════════════════════════
# anis solidscale - Elite Spot Trading Suite
# STRATEGIE : MultiTimeframeMomentum
# CATEGORIE : Multi-Timeframe / Momentum
# ══════════════════════════════════════════════════════════════
#
# LOGIQUE :
# Aligner le momentum sur 2 timeframes pour des entrees
# a haute probabilite :
# - 4h (HTF) : definit la tendance principale via triple EMA
# (EMA9 > EMA21 > EMA50 = tendance haussiere confirmee)
# - 1h (LTF) : entree sur pullback vers EMA20 quand le RSI
# est en zone neutre/basse et une bougie verte confirme
#
# ENTREE :
# 1. 4h : EMA fast > EMA mid > EMA slow (tendance haussiere HTF)
# 2. 1h : close pullback vers EMA20 (close entre 99% et 101% de EMA)
# 3. RSI entre 35 et 55 (zone de pullback, pas de surachat)
# 4. Bougie verte (close > open) = rebond confirme
#
# SORTIE :
# 4h EMA fast < EMA mid (trend casse sur HTF) OU 1h RSI > 75
# ══════════════════════════════════════════════════════════════
import sys
from pathlib import Path
from pandas import DataFrame
from freqtrade.strategy import IStrategy, IntParameter
from freqtrade.strategy import merge_informative_pair
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
from utils.indicators import CommonIndicators
from utils.logging_utils import TradeLogger
from utils.telegram_notifier import TelegramNotifier
class MultiTimeframeMomentum(IStrategy):
INTERFACE_VERSION = 3
can_short = False
timeframe = "1h"
startup_candle_count = 200
minimal_roi = {"0": 0.10, "360": 0.05, "720": 0.02}
stoploss = -0.05
trailing_stop = True
trailing_stop_positive = 0.015
trailing_stop_positive_offset = 0.025
trailing_only_offset_is_reached = True
# ── Buy params (4h) ──
ema_fast_4h = IntParameter(5, 15, default=9, space="buy")
ema_mid_4h = IntParameter(15, 30, default=21, space="buy")
ema_slow_4h = IntParameter(40, 60, default=50, space="buy")
# ── Buy params (1h) ──
ema_pullback_1h = IntParameter(15, 30, default=20, space="buy")
rsi_period = IntParameter(7, 21, default=14, space="buy")
rsi_min = IntParameter(25, 45, default=35, space="buy")
rsi_max = IntParameter(45, 65, default=55, space="buy")
# ── Sell params ──
rsi_exit = IntParameter(65, 85, default=75, space="sell")
_logger = None
_notifier = None
def _init_utils(self) -> None:
if self._logger is None:
self._logger = TradeLogger(strategy_name="MultiTimeframeMomentum")
self._notifier = TelegramNotifier()
def informative_pairs(self):
return [(pair, "4h") for pair in self.dp.current_whitelist()]
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
self._init_utils()
pair = metadata["pair"]
# ── 4h indicators ──
informative_4h = self.dp.get_pair_dataframe(pair, "4h")
# Pre-calculer EMA 4h pour TOUTES les valeurs possibles
for ema_p in range(self.ema_fast_4h.low, self.ema_fast_4h.high + 1):
informative_4h = CommonIndicators.add_ema(informative_4h, period=ema_p)
for ema_p in range(self.ema_mid_4h.low, self.ema_mid_4h.high + 1):
informative_4h = CommonIndicators.add_ema(informative_4h, period=ema_p)
for ema_p in range(self.ema_slow_4h.low, self.ema_slow_4h.high + 1):
informative_4h = CommonIndicators.add_ema(informative_4h, period=ema_p)
dataframe = merge_informative_pair(
dataframe, informative_4h, self.timeframe, "4h", ffill=True
)
# ── 1h indicators ──
# Pre-calculer EMA 1h pour TOUTES les valeurs possibles
for ema_p in range(self.ema_pullback_1h.low, self.ema_pullback_1h.high + 1):
dataframe = CommonIndicators.add_ema(dataframe, period=ema_p)
# Pre-calculer RSI pour TOUTES les valeurs possibles
for rsi_p in range(self.rsi_period.low, self.rsi_period.high + 1):
dataframe = CommonIndicators.add_rsi(dataframe, period=rsi_p)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
ema_fast_col = f"ema_{self.ema_fast_4h.value}_4h"
ema_mid_col = f"ema_{self.ema_mid_4h.value}_4h"
ema_slow_col = f"ema_{self.ema_slow_4h.value}_4h"
ema_pullback_col = f"ema_{self.ema_pullback_1h.value}"
rsi_col = f"rsi_{self.rsi_period.value}"
conditions = (
# 4h : triple EMA alignee (tendance haussiere)
(dataframe[ema_fast_col] > dataframe[ema_mid_col])
& (dataframe[ema_mid_col] > dataframe[ema_slow_col])
# 1h : pullback vers EMA (close entre 99% et 101%)
& (dataframe["close"] < dataframe[ema_pullback_col] * 1.01)
& (dataframe["close"] > dataframe[ema_pullback_col] * 0.99)
# RSI en zone de pullback
& (dataframe[rsi_col] > self.rsi_min.value)
& (dataframe[rsi_col] < self.rsi_max.value)
# Bougie verte (rebond confirme)
& (dataframe["close"] > dataframe["open"])
)
dataframe.loc[conditions, "enter_long"] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
ema_fast_col = f"ema_{self.ema_fast_4h.value}_4h"
ema_mid_col = f"ema_{self.ema_mid_4h.value}_4h"
rsi_col = f"rsi_{self.rsi_period.value}"
conditions = (
# 4h : trend casse (EMA fast < EMA mid)
(dataframe[ema_fast_col] < dataframe[ema_mid_col])
# OU RSI en surachat
| (dataframe[rsi_col] > self.rsi_exit.value)
)
dataframe.loc[conditions, "exit_long"] = 1
return dataframe