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 aussitaet d'autres)qtpylib: utilitaires supplémentaires (commecrossed_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_emasdans 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_stopinté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 danscustom_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()etadjust_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 deentry_pricingoucustom_entry_price(), la taille decustom_stake_amount()(sinonstake_amount), etconfirm_trade_entry()doit retournerTrue
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
.jsonco-localisé : quand tu lances un hyperopt, Freqtrade peut écrire les meilleurs paramètres dans unMaStrategie.jsonposé à côté de tonMaStrategie.py. Ce fichier écrase silencieusement tesbuy_params/sell_paramsdé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.jsondu 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)danspopulate_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 > 0dans 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
🎯 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 🐑
