💬 Forum

MultiTimeframeMomentum

assinscreedFC/trading_strategie/freqtrade/strategies/MultiTimeframeMomentum.py · first seen 2026-07-16 · repo updated 2026-06-13

Basics mode: spot timeframe: 1h interface version: 3 4h
Settings stoploss: -0.05 has minimal roi trailing startup candle count: 200 hyperopt hyperopt params: 8
Concepts trailing
Methods _init_utils
Other utils
15 related strategies ( identical code, similar name)

Each tile is a different kind of check — from an instant code lint to full sandboxed backtests and forward tests on recent data. Not sure what a check actually proves? See the FAQ →

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# ══════════════════════════════════════════════════════════════
# 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