💬 Forum

RLStrategy

Mohamed-sm/Freqtrade-RLStrategy-IA/RLStrategy.py · ★27 · ⑂4 · first seen 2026-07-16 · repo updated 2025-07-16 · ⬇ 1 download

Basics mode: spot timeframe: 1h freqai
Settings stoploss: -0.051 has minimal roi custom stoploss process only new candles startup candle count: 1440 hyperopt hyperopt params: 2
Indicators ADX ATR Bollinger_Bands CCI EMA MACD MFI ROC RSI SMA Stochastic Williams_R pandas_ta talib
Concepts ml
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 →

Source

Download Raw
  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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
import logging
from typing import Dict, List, Optional, Tuple
import numpy as np
import pandas as pd
from pandas import DataFrame
from datetime import datetime, timedelta
from freqtrade.strategy import IStrategy, IntParameter
from freqtrade.strategy.parameters import CategoricalParameter
from freqtrade.persistence import Trade
import talib.abstract as ta
import pandas_ta as pta
from freqtrade.strategy.parameters import DecimalParameter


logger = logging.getLogger(__name__)


class RLStrategy(IStrategy):
    """
    Stratégie d'apprentissage par renforcement pour FreqAI.
    
    Cette stratégie utilise un agent RL pour prendre des décisions de trading
    basées sur l'apprentissage automatique des patterns de marché.
    """
    
    # Paramètre requis par FreqAI - nombre maximum de bougies de démarrage
    startup_candle_count: int = 1440
    
    # Paramètres de base optimisés par hyperopt
    minimal_roi = {
        "0": 0.228,
        "325": 0.122,
        "580": 0.065,
        "840": 0
    }
    
    stoploss = -0.051
    timeframe = '1h'
    process_only_new_candles = True
    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False
    position_adjustment_enable = False
    
    # Paramètres FreqAI
    model_training_parameters = {
        "n_estimators": 200,
        "learning_rate": 0.05,
        "max_depth": 15,
        "subsample": 0.8,
        "colsample_bytree": 0.8,
        "random_state": 42
    }
    
    # Paramètres d'hyperopt optimisés - Assouplis pour plus de signaux
    buy_rsi = IntParameter(15, 50, default=34, space="buy", optimize=True)
    sell_rsi = IntParameter(50, 85, default=72, space="sell", optimize=True)
    
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Méthode requise par IStrategy pour calculer les indicateurs.
        Pour FreqAI, cette méthode doit appeler self.freqai.start()
        """
        # Appel requis par FreqAI selon la documentation
        dataframe = self.freqai.start(dataframe, metadata, self)
        
        # DEBUG: Identifier les colonnes avec NaN
        print("Colonnes avec NaN :\n", dataframe.isna().sum())
        
        # Nettoyage des NaN - CRITIQUE pour éviter les erreurs d'entraînement
        dataframe.fillna(0, inplace=True)
        
        # Calculer les indicateurs de base nécessaires pour le fallback
        # RSI
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
        dataframe['rsi'] = dataframe['rsi'].fillna(50)  # Valeur neutre
        
        # Moving averages
        dataframe['sma_20'] = ta.SMA(dataframe['close'], timeperiod=20)
        dataframe['sma_20'] = dataframe['sma_20'].fillna(dataframe['close'])
        
        # Volume
        dataframe['volume_sma'] = ta.SMA(dataframe['volume'], timeperiod=20)
        dataframe['volume_sma'] = dataframe['volume_sma'].fillna(dataframe['volume'])
        
        # MACD
        macd = ta.MACD(dataframe)
        dataframe['macd'] = macd['macd'].fillna(0)
        dataframe['macdsignal'] = macd['macdsignal'].fillna(0)
        dataframe['macdhist'] = macd['macdhist'].fillna(0)
        
        # Nettoyage final pour s'assurer qu'il n'y a aucun NaN
        dataframe.fillna(0, inplace=True)
        
        return dataframe
    
    def feature_engineering_standard(self, dataframe: DataFrame, **kwargs) -> DataFrame:
        """
        Génération des features finales pour l'apprentissage par renforcement.
        Version simplifiée pour éviter les NaN.
        """
        # Features brutes nécessaires pour RL
        dataframe[f"%-raw_close"] = dataframe["close"]
        dataframe[f"%-raw_open"] = dataframe["open"]
        dataframe[f"%-raw_high"] = dataframe["high"]
        dataframe[f"%-raw_low"] = dataframe["low"]
        dataframe[f"%-raw_volume"] = dataframe["volume"]
        
        # Indicateurs techniques simples (éviter les NaN)
        # RSI avec gestion des NaN
        dataframe['%-rsi'] = ta.RSI(dataframe, timeperiod=14)
        dataframe['%-rsi'] = dataframe['%-rsi'].fillna(50)  # Valeur neutre
        
        # MACD avec gestion des NaN
        macd = ta.MACD(dataframe)
        dataframe['%-macd'] = macd['macd'].fillna(0)
        dataframe['%-macdsignal'] = macd['macdsignal'].fillna(0)
        dataframe['%-macdhist'] = macd['macdhist'].fillna(0)
        
        # Moving averages avec gestion des NaN
        dataframe['%-sma_20'] = ta.SMA(dataframe['close'], timeperiod=20)
        dataframe['%-sma_20'] = dataframe['%-sma_20'].fillna(dataframe['close'])
        
        dataframe['%-ema_20'] = ta.EMA(dataframe['close'], timeperiod=20)
        dataframe['%-ema_20'] = dataframe['%-ema_20'].fillna(dataframe['close'])
        
        # Volume avec gestion des NaN
        dataframe['%-volume_sma'] = ta.SMA(dataframe['volume'], timeperiod=20)
        dataframe['%-volume_sma'] = dataframe['%-volume_sma'].fillna(dataframe['volume'])
        dataframe['%-volume_ratio'] = dataframe['volume'] / dataframe['%-volume_sma']
        dataframe['%-volume_ratio'] = dataframe['%-volume_ratio'].fillna(1)
        
        # Price action simple
        dataframe['%-price_change'] = dataframe['close'].pct_change()
        dataframe['%-price_change'] = dataframe['%-price_change'].fillna(0)
        
        # Features de base sans NaN
        dataframe['%-high_low_ratio'] = dataframe['high'] / dataframe['low']
        dataframe['%-high_low_ratio'] = dataframe['%-high_low_ratio'].fillna(1)
        
        # Crossovers simples
        dataframe['%-sma_cross'] = np.where(dataframe['%-sma_20'] > dataframe['%-ema_20'], 1, -1)
        
        # Features temporelles
        dataframe['%-hour'] = dataframe['date'].dt.hour
        dataframe['%-day_of_week'] = dataframe['date'].dt.dayofweek
        
        # Features décalées (limitées pour éviter les NaN)
        for i in range(1, 3):  # Réduit de 5 à 3
            dataframe[f'%-rsi_shift_{i}'] = dataframe['%-rsi'].shift(i).fillna(50)
            dataframe[f'%-price_change_shift_{i}'] = dataframe['%-price_change'].shift(i).fillna(0)
            dataframe[f'%-volume_ratio_shift_{i}'] = dataframe['%-volume_ratio'].shift(i).fillna(1)
        
        # Nettoyage final CRITIQUE - s'assurer qu'il n'y a aucun NaN
        dataframe.fillna(0, inplace=True)
        
        return dataframe
    
    def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, **kwargs) -> DataFrame:
        """
        Génération de features supplémentaires pour différents timeframes.
        Cette fonction s'étend automatiquement sur indicator_periods_candles, include_timeframes, 
        include_shifted_candles, et include_corr_pairs.
        
        Toutes les features doivent être préfixées par % pour être reconnues par FreqAI.
        """
        # Indicateurs techniques de base
        dataframe[f"%-rsi-period_{period}"] = ta.RSI(dataframe, timeperiod=period)
        dataframe[f"%-rsi-period_{period}"] = dataframe[f"%-rsi-period_{period}"].fillna(50)
        
        dataframe[f"%-mfi-period_{period}"] = ta.MFI(dataframe, timeperiod=period)
        dataframe[f"%-mfi-period_{period}"] = dataframe[f"%-mfi-period_{period}"].fillna(50)
        
        dataframe[f"%-adx-period_{period}"] = ta.ADX(dataframe, timeperiod=period)
        dataframe[f"%-adx-period_{period}"] = dataframe[f"%-adx-period_{period}"].fillna(25)
        
        dataframe[f"%-sma-period_{period}"] = ta.SMA(dataframe['close'], timeperiod=period)
        dataframe[f"%-sma-period_{period}"] = dataframe[f"%-sma-period_{period}"].fillna(dataframe['close'])
        
        dataframe[f"%-ema-period_{period}"] = ta.EMA(dataframe['close'], timeperiod=period)
        dataframe[f"%-ema-period_{period}"] = dataframe[f"%-ema-period_{period}"].fillna(dataframe['close'])
        
        # Bollinger Bands
        bollinger = ta.BBANDS(dataframe, timeperiod=period, nbdevup=2.2, nbdevdn=2.2, matype=0)
        dataframe[f"%-bb_lowerband-period_{period}"] = bollinger['lowerband'].fillna(dataframe['close'])
        dataframe[f"%-bb_middleband-period_{period}"] = bollinger['middleband'].fillna(dataframe['close'])
        dataframe[f"%-bb_upperband-period_{period}"] = bollinger['upperband'].fillna(dataframe['close'])
        
        # Largeur et position relative des Bollinger Bands
        dataframe[f"%-bb_width-period_{period}"] = (
            dataframe[f"%-bb_upperband-period_{period}"] - 
            dataframe[f"%-bb_lowerband-period_{period}"]
        ) / dataframe[f"%-bb_middleband-period_{period}"]
        dataframe[f"%-bb_width-period_{period}"] = dataframe[f"%-bb_width-period_{period}"].fillna(0.1)
        
        dataframe[f"%-close-bb_lower-period_{period}"] = (
            dataframe['close'] / dataframe[f"%-bb_lowerband-period_{period}"]
        )
        dataframe[f"%-close-bb_lower-period_{period}"] = dataframe[f"%-close-bb_lower-period_{period}"].fillna(1)
        
        # Rate of Change
        dataframe[f"%-roc-period_{period}"] = ta.ROC(dataframe['close'], timeperiod=period)
        dataframe[f"%-roc-period_{period}"] = dataframe[f"%-roc-period_{period}"].fillna(0)
        
        # Stochastic
        stoch = ta.STOCH(dataframe, fastk_period=period, slowk_period=3, slowd_period=3)
        dataframe[f"%-slowk-period_{period}"] = stoch['slowk'].fillna(50)
        dataframe[f"%-slowd-period_{period}"] = stoch['slowd'].fillna(50)
        
        # Williams %R
        dataframe[f"%-willr-period_{period}"] = ta.WILLR(dataframe, timeperiod=period)
        dataframe[f"%-willr-period_{period}"] = dataframe[f"%-willr-period_{period}"].fillna(-50)
        
        # Commodity Channel Index
        dataframe[f"%-cci-period_{period}"] = ta.CCI(dataframe, timeperiod=period)
        dataframe[f"%-cci-period_{period}"] = dataframe[f"%-cci-period_{period}"].fillna(0)
        
        # Nettoyage final des NaN
        dataframe.fillna(0, inplace=True)
        
        return dataframe
    
    def feature_engineering_expand_basic(self, dataframe: DataFrame, **kwargs) -> DataFrame:
        """
        Features de base pour l'expansion.
        Cette fonction s'étend sur include_timeframes, include_shifted_candles, et include_corr_pairs.
        """
        # Features de prix
        dataframe['%-pct-change'] = dataframe['close'].pct_change()
        dataframe['%-pct-change'] = dataframe['%-pct-change'].fillna(0)
        
        dataframe['%-pct-change-abs'] = abs(dataframe['%-pct-change'])
        dataframe['%-pct-change-abs'] = dataframe['%-pct-change-abs'].fillna(0)
        
        dataframe['%-high-low-ratio'] = dataframe['high'] / dataframe['low']
        dataframe['%-high-low-ratio'] = dataframe['%-high-low-ratio'].fillna(1)
        
        dataframe['%-close-open-ratio'] = dataframe['close'] / dataframe['open']
        dataframe['%-close-open-ratio'] = dataframe['%-close-open-ratio'].fillna(1)
        
        # Features de volume
        dataframe['%-volume-mean'] = dataframe['volume'].rolling(20).mean()
        dataframe['%-volume-mean'] = dataframe['%-volume-mean'].fillna(dataframe['volume'])
        
        dataframe['%-volume-ratio'] = dataframe['volume'] / dataframe['%-volume-mean']
        dataframe['%-volume-ratio'] = dataframe['%-volume-ratio'].fillna(1)
        
        dataframe['%-volume-pct-change'] = dataframe['volume'].pct_change()
        dataframe['%-volume-pct-change'] = dataframe['%-volume-pct-change'].fillna(0)
        
        # Features de prix brutes (nécessaires pour RL)
        dataframe['%-raw_close'] = dataframe['close']
        dataframe['%-raw_open'] = dataframe['open']
        dataframe['%-raw_high'] = dataframe['high']
        dataframe['%-raw_low'] = dataframe['low']
        dataframe['%-raw_volume'] = dataframe['volume']
        
        # Features temporelles
        dataframe['%-hour'] = dataframe['date'].dt.hour
        dataframe['%-day_of_week'] = dataframe['date'].dt.dayofweek
        dataframe['%-day_of_month'] = dataframe['date'].dt.day
        
        # ATR pour la volatilité
        dataframe['%-atr'] = ta.ATR(dataframe, timeperiod=14)
        dataframe['%-atr'] = dataframe['%-atr'].fillna(dataframe['close'] * 0.02)  # 2% du prix
        
        # Nettoyage final des NaN
        dataframe.fillna(0, inplace=True)
        
        return dataframe
    
    def set_freqai_targets(self, dataframe: DataFrame, **kwargs) -> DataFrame:
        """
        Définition des cibles pour l'apprentissage par renforcement.
        Pour RL, il n'y a pas de cibles directes à définir. C'est un remplissage (neutre)
        jusqu'à ce que l'agent envoie une action.
        
        Selon la documentation officielle FreqAI RL, cette fonction doit définir
        la colonne &-action avec une valeur neutre (0).
        """
        # Pour RL, il n'y a pas de cibles directes à définir
        # Cette valeur sera remplacée par les actions de l'agent
        # La valeur 0 correspond à l'action neutre dans Base5ActionRLEnv
        dataframe["&-action"] = 0
        return dataframe
    
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Logique d'entrée basée sur les actions de l'agent RL.
        Selon la documentation officielle FreqAI RL, l'agent décide des entrées
        via la colonne &-action générée par FreqAI.
        """
        conditions = []
        
        # L'agent RL décide des entrées via la colonne &-action
        # Actions possibles selon Base5ActionRLEnv :
        # 0 = Actions.Neutral (neutre)
        # 1 = Actions.Long_enter (entrée long)
        # 2 = Actions.Short_enter (entrée short)
        # 3 = Actions.Long_exit (sortie long)
        # 4 = Actions.Short_exit (sortie short)
        
        dataframe.loc[:, 'enter_long'] = 0
        dataframe.loc[:, 'enter_short'] = 0
        
        # Vérifier si la colonne &-action existe (générée par FreqAI)
        if '&-action' in dataframe.columns:
            # Utiliser les actions de l'agent RL
            dataframe.loc[dataframe['&-action'] == 1, 'enter_long'] = 1
            dataframe.loc[dataframe['&-action'] == 2, 'enter_short'] = 1
            
            # Log pour debug
            long_signals = (dataframe['enter_long'] == 1).sum()
            short_signals = (dataframe['enter_short'] == 1).sum()
            if long_signals > 0 or short_signals > 0:
                logger.info(f"Agent RL - Signaux long: {long_signals}, Signaux short: {short_signals}")
                
        else:
            # Si la colonne n'existe pas, utiliser des signaux de base
            logger.info("Colonne &-action non trouvée, utilisation de signaux de base")
            
            # Signaux de base plus permissifs basés sur RSI et MACD
            long_condition = (
                (dataframe['rsi'] < 40) &  # RSI plus permissif (30 -> 40)
                (dataframe['volume'] > 0) &
                (dataframe['close'] > dataframe['sma_20']) &
                (dataframe['macd'] > dataframe['macdsignal'])  # MACD bullish
            )
            
            short_condition = (
                (dataframe['rsi'] > 60) &  # RSI plus permissif (70 -> 60)
                (dataframe['volume'] > 0) &
                (dataframe['close'] < dataframe['sma_20']) &
                (dataframe['macd'] < dataframe['macdsignal'])  # MACD bearish
            )
            
            dataframe.loc[long_condition, 'enter_long'] = 1
            dataframe.loc[short_condition, 'enter_short'] = 1
        
        return dataframe
    
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Logique de sortie basée sur les actions de l'agent RL.
        Selon la documentation officielle FreqAI RL, l'agent décide des sorties
        via la colonne &-action générée par FreqAI.
        """
        dataframe.loc[:, 'exit_long'] = 0
        dataframe.loc[:, 'exit_short'] = 0
        
        # Vérifier si la colonne &-action existe (générée par FreqAI)
        if '&-action' in dataframe.columns:
            # Utiliser les actions de l'agent RL pour les sorties
            dataframe.loc[dataframe['&-action'] == 3, 'exit_long'] = 1
            dataframe.loc[dataframe['&-action'] == 4, 'exit_short'] = 1
            
            # Log pour debug
            long_exits = (dataframe['exit_long'] == 1).sum()
            short_exits = (dataframe['exit_short'] == 1).sum()
            if long_exits > 0 or short_exits > 0:
                logger.info(f"Agent RL - Sorties long: {long_exits}, Sorties short: {short_exits}")
                
        else:
            # Si la colonne n'existe pas, utiliser des signaux de base
            logger.info("Colonne &-action non trouvée pour les sorties, utilisation de signaux de base")
            
            # Signaux de sortie basés sur RSI et MACD
            exit_long_condition = (
                (dataframe['rsi'] > 70) |  # RSI sur-acheté
                (dataframe['macd'] < dataframe['macdsignal']) |  # MACD bearish
                (dataframe['close'] < dataframe['sma_20'])  # Prix sous la moyenne
            )
            
            exit_short_condition = (
                (dataframe['rsi'] < 30) |  # RSI survendu
                (dataframe['macd'] > dataframe['macdsignal']) |  # MACD bullish
                (dataframe['close'] > dataframe['sma_20'])  # Prix au-dessus de la moyenne
            )
            
            dataframe.loc[exit_long_condition, 'exit_long'] = 1
            dataframe.loc[exit_short_condition, 'exit_short'] = 1
        
        return dataframe
    
    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
                       current_rate: float, current_profit: float, **kwargs) -> float:
        """
        Stoploss personnalisé simplifié.
        """
        # Utiliser le stoploss fixe pour éviter les erreurs
        return self.stoploss
    
    def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
                           time_in_force: str, current_time: datetime, entry_tag: Optional[str],
                           side: str, **kwargs) -> bool:
        """
        Confirmation supplémentaire avant l'entrée en trade.
        """
        # Vérifications supplémentaires si nécessaire
        return True