Tu as configuré tes bots grùce au guide des fichiers de configuration, mais comment créer le moteur logique de tes robots ? Les fichiers de stratégies définissent la logique de trading : quand acheter, quand vendre, et comment gérer le risque.
Ce guide complet te servira de rĂ©fĂ©rence permanente. Garde-le dans tes favoris ! đ

đ Note fork : la structure d'une stratĂ©gie est la mĂȘme sur le Freqtrade officiel et sur mon fork freqtrade-ultimate. Mais le fork t'apporte des outils cĂŽtĂ© stratĂ©gie : choix du sampler d'hyperopt + une loss profit/drawdown, un filtre de pairlist pour les shorts, et surtout la gestion automatique des fermetures externes sur Hyperliquid (j'y reviens). PrĂ©sentation complĂšte : mes forks Freqtrade et FreqUI.
đŻ Qu'est-ce qu'un fichier de stratĂ©gie ?
Les fichiers de stratégie (en .py) sont le moteur intelligent de tes bots. Ils contiennent :
đ Les indicateurs techniques à calculer
đŻÂ Les conditions d'entrĂ©e en position
đȘ Les conditions de sortie et prises de profit
đĄïžÂ La gestion du risque (stoploss, ROI, trailing)
âïžÂ Les paramĂštres optimisables pour l'hyperoptimisation
Point crucial : pour lancer n'importe quelle action, tu as TOUJOURS besoin de deux fichiers : un fichier de stratégie (
.py) et un fichier de configuration (.json).
đ Ressources essentielles
đïž Structure d'un fichier stratĂ©gie
Un fichier de stratĂ©gie suit toujours la mĂȘme structure :
# 1. Imports nécessaires
from freqtrade.strategy import IStrategy
import pandas as pd
import talib.abstract as ta
# 2. Définition de la classe
class ma_strategie(IStrategy):
# 3. ParamĂštres de risque
minimal_roi = {"0": 0.1}
stoploss = -0.1
# 4. Configuration
timeframe = '1h'
# 5. Trois fonctions principales
def populate_indicators()
def populate_entry_trend()
def populate_exit_trend()đ Niveau 1 : StratĂ©gie simple (basic_emas)
Commençons avec un exemple concret : la stratégie basic_emas.py, qui utilise le croisement de moyennes mobiles.
1ïžâŁ Les imports
from functools import reduce
from freqtrade.strategy import IStrategy
from freqtrade.strategy import CategoricalParameter, DecimalParameter, IntParameter
from pandas import DataFrame
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylibIStrategy : la classe de base obligatoirepandas : pour manipuler les donnĂ©es (DataFrame)talib : bibliothĂšque d'indicateurs techniques (il existe aussiÂta et d'autres)qtpylib : utilitaires supplĂ©mentaires (commeÂcrossed_above)
2ïžâŁ DĂ©finition de la classe
class basic_emas(IStrategy):
# Le code de la stratĂ©gie sera iciâ ïžÂ Important : le nom de la classe (
basic_emas) doit correspondre EXACTEMENT au champÂ"strategy" de ta config JSON, ou alors tu dois prĂ©ciserÂ--strategy basic_emas dans ta commande :freqtrade trade --strategy basic_emas --config live_configs/... freqtrade backtesting --strategy basic_emas --config backtest_configs/...
3ïžâŁ ParamĂštres de risque / take profit
minimal_roi = {
"0": 1.0, # 100% de gain â vendre immĂ©diatement
"10000": 0.40, # 40% aprĂšs 10000 minutes â vendre
"25000": 0.15, # 15% aprĂšs 25000 minutes â vendre
"50000": 0 # Break-even aprĂšs 50000 minutes â vendre
}
stoploss = -0.35 # Stop Ă -35%đ Le ROI progressif permet de prendre des profits Ă diffĂ©rents niveaux selon le temps passĂ© en position. Le dernier niveau ("50000": 0) est un break-even.
đĄ Ces valeurs sont en minutes, peu importe la timeframe ! On en parle en dĂ©tail dans Comment optimiser une stratĂ©gie ? (Guide complet + VidĂ©o).
â ïžÂ PiĂšge du trailing stop natif : leÂ
trailing_stop intĂ©grĂ© de Freqtrade peut, dans certaines configs, produire des « profits fantĂŽmes » et des sorties incohĂ©rentes. Par expĂ©rience, si tu as besoin d'un stop suiveur fiable, je te conseille de le coder toi-mĂȘme dansÂcustom_exit() plutĂŽt que de te reposer sur le trailing natif.
4ïžâŁ Configuration de base
timeframe = '1d' # Timeframe journaliÚreTimeframes courantes : 1m, 5m, 15m, 30m, 1h, 4h, 1d.
5ïžâŁ Les trois fonctions principales
A. Calcul des indicateurs
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['ema12'] = ta.EMA(dataframe, timeperiod=12)
dataframe['ema80'] = ta.EMA(dataframe, timeperiod=80)
return dataframeCette fonction calcule les indicateurs et les ajoute au DataFrame. Elle est appelée une fois par paire et par bougie.
B. Conditions d'entrée
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
qtpylib.crossed_above(dataframe['ema12'], dataframe['ema80']) &
(dataframe['volume'] > 0)
),
'enter_long'] = 1
return dataframeSignal d'achat quand l'EMA 12 croise l'EMA 80 à la hausse ET que le volume est positif (sécurité).
đĄ Alternative sans qtpylib (fait la mĂȘme chose) :
(dataframe['ema12'] > dataframe['ema80']) & (dataframe['ema12'].shift(1) <= dataframe['ema80'].shift(1))
C. Conditions de sortie
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
qtpylib.crossed_above(dataframe['ema80'], dataframe['ema12']) &
(dataframe['volume'] > 0)
),
'exit_long'] = 1
return dataframeSignal de vente : le croisement inverse (baissier).
Le fichier complet forme une stratégie trÚs basique mais parfaitement valide :
from functools import reduce
from freqtrade.strategy import IStrategy
from freqtrade.strategy import CategoricalParameter, DecimalParameter, IntParameter
from pandas import DataFrame
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
class basic_emas(IStrategy):
INTERFACE_VERSION: int = 3
minimal_roi = {
"0": 1.0,
"10000": 0.40,
"25000": 0.15,
"50000": 0
}
stoploss = -0.35
timeframe = '1d'
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['ema12'] = ta.EMA(dataframe, timeperiod=12)
dataframe['ema80'] = ta.EMA(dataframe, timeperiod=80)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
qtpylib.crossed_above(dataframe['ema12'], dataframe['ema80']) &
(dataframe['volume'] > 0)
),
'enter_long'] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
qtpylib.crossed_above(dataframe['ema80'], dataframe['ema12']) &
(dataframe['volume'] > 0)
),
'exit_long'] = 1
return dataframeđ§ Customiser davantage la stratĂ©gie (les callbacks)
On peut customiser une stratégie en réécrivant les fonctions dites de « callback », par exemple :
custom_stake_amount() : ajuster la somme allouĂ©e Ă une positioncustom_exit() : sorties personnalisĂ©es (crash de marchĂ©, objectif de portefeuille atteint, ATH du BTCâŠ) â et c'est aussi lĂ qu'on code un trailing stop fiablecustom_entry_price() /Âcustom_exit_price() : prix d'entrĂ©e/sortie spĂ©cifiqueadjust_trade_position() : changer la taille d'une position dans le temps (DCA)
đ Toutes les fonctions de callback sont listĂ©es dans la documentation officielle.
đĄ Pour que les exemples de callbacks ci-dessous tournent, ajoute ces imports en haut de ta stratĂ©gie :Â
from datetime import datetime,Âfrom typing import Optional,Âfrom freqtrade.persistence import Trade.
Par exemple, pour ajouter à  basic_emas une logique de DCA qui recharge la position quand un nouveau signal d'achat apparaßt, on ajoute adjust_trade_position() :
# Nombre de DCA maximum autorisé par trade
max_dca = 3
def adjust_trade_position(self, trade: Trade, current_time, current_rate: float,
current_profit: float, min_stake: float, max_stake: float,
**kwargs) -> Optional[float]:
"""
Recharge une position si elle est en perte et que le signal d'entrée est de nouveau valide.
"""
# 1. En perte ?
if current_profit >= 0:
return None
# 2. Trop de DCA ?
filled_orders = trade.select_filled_orders(trade.entry_side)
if len(filled_orders) > self.max_dca:
return None
# 3. Nouveau signal d'achat ?
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
if dataframe is None or dataframe.empty:
return None
last_row = dataframe.iloc[-1]
if last_row.get('enter_long', 0) != 1:
return None
# 4. Recalculer le montant du DCA (simple : x1.5)
try:
initial_stake = filled_orders[0].cost
dca_stake = min(initial_stake * 1.5, max_stake)
return dca_stake
except Exception as e:
logger.error(f"DCA error for {trade.pair}: {e}")
return Noneâïž Ordre d'exĂ©cution du bot (fonctions et callbacks)
Avant d'introduire des fonctions comme adjust_trade_position(), il faut comprendre ce qui se passe à chaque cycle. Freqtrade exécute chaque boucle d'analyse selon une séquence définie (le bot execution logic). Pour chaque paire de ta pairlist, dans l'ordre :
Récupération des paires tradables (+ les paires des positions ouvertes si elles ne sont plus tradables)
Téléchargement des données OHLCV (une fois par bougie)
Appel Ă Â
bot_start()ExĂ©cution (deux fois) deÂ
populate_indicators(),Âpopulate_entry_trend(),Âpopulate_exit_trend()Synchronisation des ordres limites ouverts, puisÂ
check_entry_timeout(),Âcheck_exit_timeout() etÂadjust_entry_price() (voir le guide des ordres)Ăvaluation des sorties : stoploss â ROI â signaux (
exit_long/exit_short) âÂcustom_exit() âÂcustom_stoploss()Confirmation des sorties viaÂ
confirm_trade_exit()Â avant placement de l'ordreAjustement de la taille des positions viaÂ
adjust_trade_position() (rĂ©duire ou recharger â le DCA)VĂ©rification des slots disponibles (
max_open_trades) : si un signal d'entrĂ©e est valide, l'entrĂ©e est envoyĂ©e. Le prix vient deÂentry_pricing ouÂcustom_entry_price(), la taille deÂcustom_stake_amount() (sinonÂstake_amount), etÂconfirm_trade_entry() doit retournerÂTrue
Ces étapes sont rejouées toutes les process_throttle_secs secondes (défini dans la config). Plus ta timeframe est haute, plus tu peux mettre une grande valeur. Détails dans la doc officielle.
đ Important si tu trades sur Hyperliquid : une position peut disparaĂźtre de l'exchange sans que ton bot l'ait demandĂ© (ADL, liquidation, fermeture manuelle). Avec le Freqtrade officiel, le bot tourne alors en boucle sur un « trade fantĂŽme ». freqtrade-ultimate dĂ©tecte et ferme proprement ces fermetures externes automatiquement â rien Ă coder dans ta stratĂ©gie.
âïž SpĂ©cificitĂ© liĂ©e Ă l'hyperopt
Par défaut, lors d'un hyperopt : populate_indicators() est appelé une seule fois par paire, puis chaque jeu de paramÚtres testé (epoch) déclenche populate_entry_trend() et populate_exit_trend().
En ajoutant l'option --analyze-per-epoch à  freqtrade hyperopt, tu demandes Ă recalculer les indicateurs à chaque epoch. Utile pour optimiser des indicateurs dont les pĂ©riodes sont elles-mĂȘmes des paramĂštres (EMA, VWAP configurables), mais nettement plus coĂ»teux en calcul. Plus de dĂ©tails : Comment optimiser une stratĂ©gie ?
â ïžÂ Le piĂšge du fichierÂ
.json co-localisĂ© : quand tu lances un hyperopt, Freqtrade peut Ă©crire les meilleurs paramĂštres dans unÂMaStrategie.json posĂ© Ă cĂŽtĂ© de tonÂMaStrategie.py. Ce fichier écrase silencieusement tesÂbuy_params/sell_params dĂ©finis dans le code Python. Autrement dit : si tes valeurs live ne correspondent pas Ă ce que tu lis dans leÂ.py, vĂ©rifie toujours s'il existe unÂ.json du mĂȘme nom â c'est lui qui fait foi.
đĄÂ Bonus fork : freqtrade-ultimate te laisse choisir l'algorithme d'optimisation viaÂ
--sampler (TPE, CMA-ES, NSGA-II/III, GP, QMC) et fournit une loss profit/drawdown. Tout est expliqué dans le guide Hyperopt & Hyperopt-Loss et dans le post sur les forks.
đ Niveau 2 : StratĂ©gie avancĂ©e (simple_vwap_v1)
Pour les traders plus expĂ©rimentĂ©s, examinons simple_vwap_v1, prĂ©sentĂ©e en dĂ©tail dans 𧩠Simple VWAP v1 â la stratĂ©gie du placement constant.
đŻÂ Philosophie : exposition maximale au marchĂ© â conditions d'entrĂ©e ultra-permissives et DCA intelligent.
đ Indicateurs sophistiquĂ©s
def VWAPB(dataframe, window_size=20, num_of_std=1):
"""Bandes VWAP avec écart-type"""
df = dataframe.copy()
df['vwap'] = qtpylib.rolling_vwap(df, window=window_size)
rolling_std = df['vwap'].rolling(window=window_size).std()
df['vwap_low'] = df['vwap'] - (rolling_std * num_of_std)
df['vwap_high'] = df['vwap'] + (rolling_std * num_of_std)
return df['vwap_low'], df['vwap'], df['vwap_high']ParamĂštres optimisables
# Stoploss personnalisé optimisable
my_custom_stoploss = DecimalParameter(
-0.45, -0.2, # Plage : -45% Ă -20%
decimals=2,
default=-0.37, # Valeur par défaut : -37%
space="sell",
optimize=True # Peut ĂȘtre optimisĂ©
)
# Ratio du capital Ă utiliser
tradable_balance_ratio = CategoricalParameter(
[0.25, 0.35, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
default=0.35,
space="buy",
optimize=True
)Ces paramĂštres peuvent ĂȘtre optimisĂ©s automatiquement avec la commande hyperopt (voir aussi DecimalParameter et CategoricalParameter).
Gestion avancée des positions
1. Taille dynamique selon la volatilité (voir aussi le post sur le position sizing) :
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
proposed_stake: float, min_stake: float, max_stake: float,
**kwargs) -> float:
# Calcul de l'ATR pour mesurer la volatilité
recent_atr_pct = (recent_high - recent_low).mean() / recent_close
# Réduction si volatilité élevée
if recent_atr_pct > 0.102: # >10.2%
volatility_adjustment = 0.5 # -50%
elif recent_atr_pct > 0.061: # >6.1%
volatility_adjustment = 0.75 # -25%
else:
volatility_adjustment = 1.0 # Taille normale2. SystÚme DCA intelligent (rechargement progressif des positions perdantes) :
def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, min_stake: float, max_stake: float,
**kwargs) -> Optional[float]:
"""
SystĂšme de DCA (Dollar Cost Averaging) intelligent.
Rechargements déclenchés à des seuils de perte croissants, avec des montants croissants.
Exemple avec les paramÚtres par défaut :
- 1er DCA Ă -1.2% avec 1.8x le montant initial
- 2e DCA Ă -3.6% avec 3.24x le montant initial
- 3e DCA Ă -10.8% avec 5.83x le montant initial
- 4e DCA Ă -32.4% avec 10.5x le montant initial
"""
# Ne rien faire si on n'est pas en perte suffisante
if current_profit > self.initial_safety_order_trigger.value:
return None
# Récupérer l'historique des ordres
filled_buys = trade.select_filled_orders(trade.entry_side)
count_of_buys = len(filled_buys)
# Vérifier qu'on n'a pas atteint la limite de DCA
if not (1 <= count_of_buys <= self.max_so_multiplier_orig.value):
return None
# Calcul du seuil de déclenchement pour ce DCA
if self.safety_order_step_scale.value == 1:
# Espacement linéaire
safety_order_trigger = abs(self.initial_safety_order_trigger.value) * count_of_buys
else:
# Espacement exponentiel (recommandé)
initial_trigger = abs(self.initial_safety_order_trigger.value)
scale = self.safety_order_step_scale.value
if scale > 1:
numerator = scale * (pow(scale, count_of_buys - 1) - 1)
denominator = scale - 1
safety_order_trigger = initial_trigger + (initial_trigger * scale * numerator / denominator)
else:
numerator = 1 - pow(scale, count_of_buys - 1)
denominator = 1 - scale
safety_order_trigger = initial_trigger + (initial_trigger * scale * numerator / denominator)
# Vérifier si on a atteint le seuil de DCA
if current_profit > (-1 * abs(safety_order_trigger)):
return None
# Calcul du montant du DCA
try:
actual_initial_stake = filled_buys[0].cost
stake_amount = actual_initial_stake * pow(
self.safety_order_volume_scale.value,
count_of_buys - 1
)
return stake_amount
except Exception as e:
logger.error(f'Erreur DCA pour {trade.pair}: {str(e)}')
return Noneâ ïž Le DCA agressif (rechargement des positions perdantes) peut transformer une petite perte en gros trou si le marchĂ© continue contre toi. Dimensionne toujours en imaginant le pire scĂ©nario â voir le post sur le position sizing.
đ Configuration graphique
Affiche tes indicateurs dans FreqUI :
plot_config = {
"main_plot": {
"EMA": {"color": "#ffffff", "type": "line"},
"VWAP_low": {"color": "#53c3eb", "type": "line"},
},
"subplots": {
"CCI": {
"CCI": {"color": "#ffffff", "type": "line"},
}
}
}Résultat dans FreqUI :

đ§ Bonnes pratiques
â Ă faire
Nommage cohérent : nom de classe = nom dans la config, en minuscules avec underscores (
ma_super_strategie)Logs pour debug : un simpleÂ
print(dataframe) dansÂpopulate_indicators() aide Ă©normĂ©ment Ă comprendre ce qui se passeCommentaires dĂ©taillĂ©s : explique ta logique, documente les paramĂštres
â Erreurs courantes
Nom de classe invalide :Â
class Ma-Super-Strategie â (tirets interdits) âÂclass ma_super_strategie âOubli duÂ
volume > 0 dans les conditions (évite de trader sur des bougies sans volume)Stratégie en avance sur le futur (look-ahead bias) : ne te sers jamais d'une bougie non clÎturée. Voir le piÚge du look-ahead bias
đ Progression recommandĂ©e
đ„ DĂ©butant : commence avec basic_emas, modifie les pĂ©riodes des EMA, ajoute un indicateur simple (RSI), teste en dry-run.
đ„ IntermĂ©diaire : customise avec des callbacks, implĂ©mente des paramĂštres optimisables.
đ„ Avancé : cherche une config rentable avec ces paramĂštres et callbacks, implĂ©mente des sorties custom, optimise avec hyperopt, puis re-teste en dry-run.
Quand ton dry-run est validĂ© et que tu es prĂȘt Ă encaisser le pire scĂ©nario de ta stratĂ©gie â tu peux passer en live.
đĄ Mon approche perso : je valide en live avec un petit capital plutĂŽt que de sur-croire Ă un backtest. Un backtest ne vaut rien, ce qui vaut quelque chose c'est le live â mais commence toujours en dry-run, puis avec une mise minuscule.
đĄ Ressources complĂ©mentaires
đŻÂ Spot vs Futures
đŻ Conclusion
Les fichiers de stratégie sont l'ùme de tes bots Freqtrade. Tu as maintenant les clés pour comprendre leur structure, créer tes conditions, optimiser les performances et gérer le risque.
Prochaine étape : valide tes stratégies en dry-run, puis passe en live avec une petite mise.
â Si ce guide t'a aidĂ©, tu peux me soutenir ici : buymeacoffee.com/freqtrade_france.
đĄ Si tu trades sur Hyperliquid, tu peux passer par mon lien d'affiliation (rĂ©duction sur les frais).
â ïž Je ne suis pas conseiller financier. Ce contenu reflĂšte mon expĂ©rience personnelle. Les performances passĂ©es ne prĂ©jugent pas des performances futures. Il existe d'autres mĂ©thodes tout aussi valables.
â Mouton đ
