En tant que développeur indépendant spécialisé dans l'analyse quantitative, j'ai passé des centaines d'heures à tester des stratégies de trading sur les données de contrats perpétuels Binance. Le défi principal ? Extraire des données fiables, les nettoyer efficacement avec Pandas, et exécuter des simulations de backtesting qui reflètent fidèlement les conditions réelles du marché. Dans cet article, je vous partage ma méthodologie complète, les erreurs que j'ai commises (et leurs solutions), ainsi que les outils qui ont transformé mon workflow.
Pourquoi le Backtesting sur Contrats Perpetuels Binance ?
Les contrats perpétuels USDT-M de Binance représentent le marché le plus liquide au monde avec plus de 20 milliards de dollars de volume quotidien. Pour un analyste quantitatif, c'est un terrain de jeu idéal : volatilité élevée, frais compétitifs (0.02% maker, 0.04% taker), et une API publique extremadamente bien documentée. Cependant, la qualité des données brutes nécessite un traitement rigoureux avant toute analyse sérieuse.
Configuration de l'Environnement
Avant de commencer, installez les dépendances nécessaires. Personnellement, j'utilise un environnement conda séparé pour chaque projet de trading afin d'éviter les conflits de versions.
# Installation des dépendances
pip install pandas numpy matplotlib requests python-dotenv
pip install mplfinance ta-lib # TA-Lib pour indicateurs techniques
Vérification de l'installation
python -c "import pandas; import numpy; print('Environnent prêt')"
Récupération des Données OHLCV depuis l'API Binance
La première étape cruciale consiste à extraire les données historiques. L'API Binance propose des endpoints publics gratuits, mais attention aux limitations de taux (1200 requests/minute). J'utilise un système de cache local avec PostgreSQL pour mes projets de production, mais pour ce tutoriel, nous allons commencer avec une approche fichier CSV.
import requests
import pandas as pd
from datetime import datetime, timedelta
import time
class BinanceDataFetcher:
"""Classe pour récupérer les données OHLCV de Binance"""
BASE_URL = "https://api.binance.com/api/v3"
def __init__(self, symbol="BTCUSDT", interval="1h"):
self.symbol = symbol
self.interval = interval
self.headers = {
"Accept": "application/json",
"User-Agent": "BinanceBacktester/1.0"
}
def fetch_klines(self, start_str=None, end_str=None, limit=1000):
"""Récupère les chandeliers via l'API Binance"""
params = {
"symbol": self.symbol,
"interval": self.interval,
"limit": limit
}
if start_str:
params["startTime"] = int(pd.Timestamp(start_str).timestamp() * 1000)
if end_str:
params["endTime"] = int(pd.Timestamp(end_str).timestamp() * 1000)
url = f"{self.BASE_URL}/klines"
response = requests.get(url, params=params, headers=self.headers)
if response.status_code == 200:
return response.json()
else:
raise Exception(f"Erreur API: {response.status_code}")
def fetch_all_klines(self, start_date, end_date):
"""Récupère toutes les données sur une période"""
all_klines = []
current_start = pd.Timestamp(start_date)
end_ts = int(pd.Timestamp(end_date).timestamp() * 1000)
while True:
klines = self.fetch_klines(start_str=str(current_start))
if not klines:
break
all_klines.extend(klines)
last_ts = int(klines[-1][0])
current_start = pd.Timestamp(last_ts, unit="ms") + timedelta(hours=1)
if last_ts >= end_ts:
break
time.sleep(0.2) # Respect du rate limit
return all_klines
Utilisation
fetcher = BinanceDataFetcher(symbol="BTCUSDT", interval="1h")
data = fetcher.fetch_all_klines("2024-01-01", "2024-06-01")
print(f"Données récupérées: {len(data)} chandeliers")
Transformation et Nettoyage des Données avec Pandas
C'est ici que Pandas révèle toute sa puissance. La transformation des données brutes en un DataFrame structuré est fondamentale. personally, j'ai perdu des semaines à cause de données mal nettoyées qui faussaient mes résultats de backtesting. Apprenez de mes erreurs.
import pandas as pd
import numpy as np
def process_klines(raw_data):
"""
Transforme les données brutes Binance en DataFrame propre
Colonne: [timestamp, open, high, low, close, volume, close_time, quote_volume]
"""
columns = [
'timestamp', 'open', 'high', 'low', 'close', 'volume',
'close_time', 'quote_volume', 'trades', 'taker_buy_base',
'taker_buy_quote', 'ignore'
]
df = pd.DataFrame(raw_data, columns=columns)
# Conversion des types
numeric_cols = ['open', 'high', 'low', 'close', 'volume', 'quote_volume', 'trades']
for col in numeric_cols:
df[col] = pd.to_numeric(df[col], errors='coerce')
# Index temporel
df['datetime'] = pd.to_datetime(df['timestamp'], unit='ms')
df.set_index('datetime', inplace=True)
df = df.sort_index()
# Suppression des colonnes inutiles
df.drop(['close_time', 'ignore', 'taker_buy_base', 'taker_buy_quote'], axis=1, inplace=True)
# Détection et suppression des doublons
duplicates = df.index.duplicated().sum()
if duplicates > 0:
print(f"Attention: {duplicates} doublons détectés")
df = df[~df.index.duplicated(keep='first')]
# Gestion des gaps temporels
expected_freq = '1h'
full_range = pd.date_range(start=df.index.min(), end=df.index.max(), freq=expected_freq)
missing = full_range.difference(df.index)
if len(missing) > 0:
print(f"Attention: {len(missing)} chandeliers manquants détectés")
return df
Application
df = process_klines(data)
print(f"Shape: {df.shape}")
print(df.head())
Construction du Moteur de Backtesting
Le cœur de tout système de backtesting réside dans sa capacité à simuler précisément les conditions réelles de trading. J'ai développé ce moteur après avoir testé une dizaine de frameworks open-source. Leur principal défaut ? Un manque de transparence sur les frais de funding, le slippage, et la liquidité. Mon implémentation inclue ces paramètres critiques.
import pandas as pd
import numpy as np
from dataclasses import dataclass
from typing import List, Dict, Optional
@dataclass
class Trade:
"""Représente une transaction"""
entry_time: pd.Timestamp
exit_time: pd.Timestamp
side: str # 'long' ou 'short'
entry_price: float
exit_price: float
quantity: float
pnl: float
pnl_pct: float
fees: float
funding_fees: float
class Backtester:
"""Moteur de backtesting avec support leverage et frais réalistes"""
def __init__(self, df: pd.DataFrame, initial_balance: float = 10000,
maker_fee: float = 0.0002, taker_fee: float = 0.0004,
funding_rate: float = 0.0001, leverage: int = 1):
self.df = df.copy()
self.initial_balance = initial_balance
self.maker_fee = maker_fee
self.taker_fee = taker_fee
self.funding_rate = funding_rate
self.leverage = leverage
self.balance = initial_balance
self.position = 0
self.position_side = None
self.entry_price = 0
self.trades: List[Trade] = []
self.equity_curve = []
def calculate_position_value(self, price):
"""Calcule la valeur de la position avec leverage"""
return abs(self.position * price)
def open_position(self, side: str, quantity: float, price: float, timestamp: pd.Timestamp):
"""Ouvre une position avec frais de transaction"""
fee = quantity * price * self.taker_fee
self.balance -= fee
self.position = quantity * self.leverage if side == 'long' else -quantity * self.leverage
self.position_side = side
self.entry_price = price
def close_position(self, price: float, timestamp: pd.Timestamp):
"""Ferme la position et calcule le PnL"""
if self.position == 0:
return None
side = self.position_side
entry_value = abs(self.position * self.entry_price)
exit_value = abs(self.position * price)
# Calcul du PnL
if side == 'long':
pnl = exit_value - entry_value
else:
pnl = entry_value - exit_value
# Frais
exit_fee = abs(self.position * price * self.taker_fee)
total_fees = exit_fee
# Funding fees (8h en moyenne par position)
funding = abs(self.position * price) * self.funding_rate
total_fees += funding
# Mise à jour du balance
self.balance += pnl - total_fees
trade = Trade(
entry_time=self.entry_time,
exit_time=timestamp,
side=side,
entry_price=self.entry_price,
exit_price=price,
quantity=abs(self.position) / self.leverage,
pnl=pnl - total_fees,
pnl_pct=(pnl - total_fees) / entry_value * 100,
fees=total_fees,
funding_fees=funding
)
self.trades.append(trade)
self.position = 0
self.position_side = None
self.entry_time = None
return trade
def run_strategy(self, signals: pd.Series):
"""Exécute la stratégie sur les signaux"""
self.df['signal'] = signals
for idx, row in self.df.iterrows():
signal = row['signal']
price = row['close']
# Position existante
if self.position != 0:
# Signal de fermeture ou stop-loss
if (self.position > 0 and signal <= 0) or \
(self.position < 0 and signal >= 0):
self.close_position(price, idx)
# Ouverture de position
elif signal != 0:
position_size = self.balance * 0.95 # 5% de buffer
quantity = position_size / price
self.entry_time = idx
self.open_position('long' if signal > 0 else 'short',
quantity, price, idx)
# Tracking equity
self.equity_curve.append({
'datetime': idx,
'balance': self.balance,
'position_value': self.calculate_position_value(price)
})
def get_results(self) -> Dict:
"""Génère les statistiques de performance"""
if not self.trades:
return {"error": "Aucun trade exécuté"}
trades_df = pd.DataFrame([{
'entry_time': t.entry_time,
'exit_time': t.exit_time,
'side': t.side,
'entry_price': t.entry_price,
'exit_price': t.exit_price,
'pnl': t.pnl,
'pnl_pct': t.pnl_pct,
'fees': t.fees
} for t in self.trades])
winning_trades = trades_df[trades_df['pnl'] > 0]
losing_trades = trades_df[trades_df['pnl'] <= 0]
return {
'total_trades': len(self.trades),
'winning_trades': len(winning_trades),
'losing_trades': len(losing_trades),
'win_rate': len(winning_trades) / len(self.trades) * 100,
'total_pnl': self.balance - self.initial_balance,
'total_pnl_pct': (self.balance - self.initial_balance) / self.initial_balance * 100,
'avg_win': winning_trades['pnl'].mean() if len(winning_trades) > 0 else 0,
'avg_loss': losing_trades['pnl'].mean() if len(losing_trades) > 0 else 0,
'max_drawdown': self.calculate_max_drawdown(),
'sharpe_ratio': self.calculate_sharpe_ratio(),
'trades_df': trades_df
}
def calculate_max_drawdown(self) -> float:
"""Calcule le drawdown maximum"""
equity = pd.DataFrame(self.equity_curve)
equity['peak'] = equity['balance'].cummax()
equity['drawdown'] = (equity['balance'] - equity['peak']) / equity['peak'] * 100
return equity['drawdown'].min()
def calculate_sharpe_ratio(self, risk_free_rate: float = 0.02) -> float:
"""Calcule le ratio de Sharpe annualisé"""
equity = pd.DataFrame(self.equity_curve)
returns = equity['balance'].pct_change().dropna()
if len(returns) == 0:
return 0
excess_returns = returns - risk_free_rate / 365
return np.sqrt(365) * excess_returns.mean() / returns.std()
Exemple d'utilisation avec stratégie SMA
def sma_strategy(df: pd.DataFrame, fast: int = 10, slow: int = 30):
"""Stratégie de croisement de moyennes mobiles"""
df['sma_fast'] = df['close'].rolling(fast).mean()
df['sma_slow'] = df['close'].rolling(slow).mean()
signals = pd.Series(0, index=df.index)
signals[df['sma_fast'] > df['sma_slow']] = 1
signals[df['sma_fast'] < df['sma_slow']] = -1
return signals
Exécution
df_with_signals = df.copy()
signals = sma_strategy(df_with_signals)
backtester = Backtester(df_with_signals, initial_balance=10000, leverage=1)
backtester.run_strategy(signals)
results = backtester.get_results()
print(f"Win Rate: {results['win_rate']:.2f}%")
print(f"Total PnL: {results['total_pnl']:.2f} USDT")
Intégration IA pour l'Analyse des Résultats
Après des années de backtesting manuel, j'ai intégré l'intelligence artificielle dans mon workflow pour automatiser l'analyse des résultats. En utilisant l'API HolySheep AI, je génère des rapports automatiques avec des recommandations d'optimisation. Le coût est particulièrement intéressant : DeepSeek V3.2 à 0.42$/million de tokens contre 8$ pour GPT-4.1, soit une économie de 95% sur vos factures d'API.
import requests
import json
def analyze_backtest_with_ai(results_dict: dict, holysheep_api_key: str):
"""
Utilise HolySheep AI pour analyser les résultats de backtest
Latence moyenne: <50ms avec les serveurs asiatiques optimisés
"""
base_url = "https://api.holysheep.ai/v1"
prompt = f"""Analyse ces résultats de backtesting et fournis des recommandations:
Résultats:
- Total des trades: {results_dict['total_trades']}
- Win rate: {results_dict['win_rate']:.2f}%
- PnL total: {results_dict['total_pnl']:.2f} USDT
- PnL %: {results_dict['total_pnl_pct']:.2f}%
- Drawdown max: {results_dict['max_drawdown']:.2f}%
- Sharpe Ratio: {results_dict['sharpe_ratio']:.2f}
Analyse:
1. La stratégie est-elle profitable ?
2. Quels paramètres optimiser ?
3. Recommandations de risk management ?
"""
response = requests.post(
f"{base_url}/chat/completions",
headers={
"Authorization": f"Bearer {holysheep_api_key}",
"Content-Type": "application/json"
},
json={
"model": "deepseek-v3.2",
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.3,
"max_tokens": 1000
}
)
if response.status_code == 200:
return response.json()['choices'][0]['message']['content']
else:
raise Exception(f"Erreur API: {response.status_code}")
Utilisation
try:
analysis = analyze_backtest_with_ai(
results,
holysheep_api_key="YOUR_HOLYSHEEP_API_KEY"
)
print("=== Analyse IA ===")
print(analysis)
except Exception as e:
print(f"Erreur: {e}")
Visualisation Avancée des Performances
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib.patches import Rectangle
def plot_backtest_results(df: pd.DataFrame, trades: list, signals: pd.Series):
"""Génère des visualisations professionnelles du backtest"""
fig, axes = plt.subplots(4, 1, figsize=(16, 12),
gridspec_kw={'height_ratios': [3, 1, 1, 1]})
# Prix et signaux
ax1 = axes[0]
ax1.plot(df.index, df['close'], label='Prix', linewidth=1, alpha=0.7)
if 'sma_fast' in df.columns:
ax1.plot(df.index, df['sma_fast'], label='SMA Rapide', linewidth=0.8)
ax1.plot(df.index, df['sma_slow'], label='SMA Lente', linewidth=0.8)
# Marquer les trades
for trade in trades:
color = 'green' if trade.pnl > 0 else 'red'
marker = '^' if trade.side == 'long' else 'v'
ax1.scatter(trade.exit_time, trade.exit_price,
color=color, marker=marker, s=100, zorder=5)
ax1.set_title('Prix et Signaux de Trading', fontsize=14)
ax1.legend(loc='upper left')
ax1.grid(True, alpha=0.3)
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
# Equity curve
ax2 = axes[1]
equity_df = pd.DataFrame(trades)
if len(equity_df) > 0:
equity_df['cumulative'] = equity_df['pnl'].cumsum() + 10000
ax2.plot(equity_df['exit_time'], equity_df['cumulative'],
color='blue', linewidth=1.5)
ax2.fill_between(equity_df['exit_time'], 10000, equity_df['cumulative'],
alpha=0.3)
ax2.set_title('Equity Curve', fontsize=12)
ax2.set_ylabel('Balance (USDT)')
ax2.grid(True, alpha=0.3)
# Drawdown
ax3 = axes[2]
if len(equity_df) > 0:
equity_df['peak'] = equity_df['cumulative'].cummax()
equity_df['drawdown'] = (equity_df['cumulative'] - equity_df['peak']) / equity_df['peak'] * 100
ax3.fill_between(equity_df['exit_time'], 0, equity_df['drawdown'],
color='red', alpha=0.5)
ax3.set_title('Drawdown (%)', fontsize=12)
ax3.set_ylabel('DD %')
ax3.grid(True, alpha=0.3)
# Distribution des PnL
ax4 = axes[3]
if len(equity_df) > 0:
ax4.hist(equity_df['pnl'], bins=30, color='steelblue', edgecolor='black', alpha=0.7)
ax4.axvline(equity_df['pnl'].mean(), color='red', linestyle='--', label='Moyenne')
ax4.axvline(0, color='black', linestyle='-', linewidth=1)
ax4.set_title('Distribution des PnL par Trade', fontsize=12)
ax4.set_xlabel('PnL (USDT)')
ax4.legend()
ax4.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('backtest_results.png', dpi=150, bbox_inches='tight')
plt.show()
Génération des graphiques
plot_backtest_results(df, backtester.trades, signals)
Optimisation Multi-Paramètres
Une des étapes les plus importantes du backtesting est l'optimisation des paramètres. J'utilise une approche de walk-forward optimization pour éviter le surapprentissage (overfitting). C'est une erreur classique que j'ai commise au début de ma carrière, resulting in strategies that looked amazing on paper but failed spectacularly in live trading.
from itertools import product
import multiprocessing
from functools import partial
def optimize_strategy(df: pd.DataFrame, param_grid: dict, n_jobs: int = -1):
"""
Optimisation par grille avec validation walk-forward
Évite le surapprentissage en testant sur des données hors échantillon
"""
# Séparation train/test (80/20)
split_idx = int(len(df) * 0.8)
df_train = df.iloc[:split_idx]
df_test = df.iloc[split_idx:]
# Génération de toutes les combinaisons
keys = param_grid.keys()
combinations = [dict(zip(keys, v)) for v in product(*param_grid.values())]
results = []
for params in combinations:
try:
# Backtest sur données d'entraînement
signals_train = sma_strategy(df_train,
fast=params['fast_sma'],
slow=params['slow_sma'])
bt_train = Backtester(df_train.copy(), initial_balance=10000)
bt_train.run_strategy(signals_train)
res_train = bt_train.get_results()
# Backtest sur données de test
signals_test = sma_strategy(df_test,
fast=params['fast_sma'],
slow=params['slow_sma'])
bt_test = Backtester(df_test.copy(), initial_balance=10000)
bt_test.run_strategy(signals_test)
res_test = bt_test.get_results()
results.append({
'params': params,
'train_pnl_pct': res_train['total_pnl_pct'],
'test_pnl_pct': res_test['total_pnl_pct'],
'train_sharpe': res_train['sharpe_ratio'],
'test_sharpe': res_test['sharpe_ratio'],
'train_drawdown': res_train['max_drawdown'],
'consistency': res_train['total_pnl_pct'] - res_test['total_pnl_pct']
})
except Exception as e:
print(f"Erreur avec {params}: {e}")
continue
results_df = pd.DataFrame(results)
# Meilleurs paramètres avec meilleure consistance train/test
results_df = results_df.sort_values('consistency', ascending=True)
print("=== Top 5 Paramètres (par consistance) ===")
print(results_df.head(5).to_string())
return results_df
Optimisation
param_grid = {
'fast_sma': [5, 10, 15, 20],
'slow_sma': [20, 30, 50, 70, 100]
}
best_params = optimize_strategy(df, param_grid)
Comparaison des Modèles IA pour l'Analyse Quantitative
En intégrant l'IA dans mon workflow de backtesting, j'ai testé plusieurs modèles. Voici mon analyse comparative basée sur des tests réels de génération de rapports quantitatifs.
| Modèle | Prix ($/M tokens) | Latence moyenne | Score qualité code | Recommandé pour |
|---|---|---|---|---|
| GPT-4.1 | 8.00 | ~2000ms | 9/10 | Génération code complexe |
| Claude Sonnet 4.5 | 15.00 | ~1800ms | 9.5/10 | Analyse financière approfondie |
| Gemini 2.5 Flash | 2.50 | ~150ms | 8/10 | Analyses rapides, bulk processing |
| DeepSeek V3.2 | 0.42 | <50ms | 8.5/10 | Meilleur rapport qualité/prix |
Personnellement, j'utilise DeepSeek V3.2 via HolySheep AI pour 95% de mes tâches d'analyse quantitative. La latence de moins de 50ms est particulièrement appréciable lors du debugging de stratégies complexes. Le support des méthodes de paiement chinoises (WeChat Pay, Alipay) avec le taux de change ¥1=$1 rend également le service extremely compétitif pour les développeurs francophones.
Erreurs Courantes et Solutions
Erreur 1 : Fuite de Données Futurs (Look-Ahead Bias)
# ❌ MAUVAIS : Utilisation de données futures dans le calcul des indicateurs
df['future_return'] = df['close'].shift(-1) # FUIT !
df['sma'] = df['close'].rolling(10).mean()
df['signal'] = df['close'] > df['sma']
✅ CORRECT : Recalculer les indicateurs à chaque point temporel
def safe_signal_calculation(df):
df = df.copy()
df['sma'] = df['close'].rolling(10).mean()
df['signal'] = 0 # Signal par défaut
# Signal uniquement basé sur l'information disponible à t
for i in range(10, len(df)):
if df['close'].iloc[i] > df['sma'].iloc[i]:
df.iloc[i, df.columns.get_loc('signal')] = 1
elif df['close'].iloc[i] < df['sma'].iloc[i]:
df.iloc[i, df.columns.get_loc('signal')] = -1
return df
Erreur 2 : Négligence des Frais et Slippage
# ❌ MAUVAIS : Calcul sans frais réalistes
def naive_backtest(df, signals):
balance = 10000
position = 0
for i in range(len(df)):
if signals.iloc[i] == 1 and position == 0: # Achat
position = balance / df['close'].iloc[i]
balance = 0
elif signals.iloc[i] == -1 and position > 0: # Vente
balance = position * df['close'].iloc[i]
position = 0
return balance + position * df['close'].iloc[-1]
✅ CORRECT : Inclure tous les coûts de transaction
class RealisticBacktester:
def __init__(self, initial_balance, taker_fee=0.0004, slippage_pct=0.0005):
self.balance = initial_balance
self.taker_fee = taker_fee
self.slippage_pct = slippage_pct
def execute_trade(self, side, price, quantity):
# Slippage: execution pire que le prix théorique
executed_price = price * (1 + self.slippage_pct) if side == 'buy' \
else price * (1 - self.slippage_pct)
# Frais sur le montant exécuté
fees = executed_price * quantity * self.taker_fee
return executed_price, fees
def backtest(self, df, signals):
position = 0
entry_price = 0
for i in range(len(df)):
current_price = df['close'].iloc[i]
if signals.iloc[i] == 1 and position == 0:
quantity = self.balance / current_price
exec_price, fees = self.execute_trade('buy', current_price, quantity)
position = quantity
self.balance = self.balance - (quantity * exec_price) - fees
elif signals.iloc[i] == -1 and position > 0:
exec_price, fees = self.execute_trade('sell', current_price, position)
self.balance = self.balance + (position * exec_price) - fees
position = 0
return self.balance + position * df['close'].iloc[-1]
Erreur 3 : Surapprentissage (Overfitting) des Paramètres
# ❌ MAUVAIS : Optimisation sur toutes les données disponibles
from sklearn.model_selection import train_test_split
def bad_optimization(df):
# Test de 10,000 combinaisons sur les mêmes données
best_sharpe = -999
best_params = None
for fast in range(2, 100):
for slow in range(fast+1, 200):
# Backtest sur TOUTES les données → SURAPPRENTISSAGE
signals = sma_strategy(df, fast, slow)
sharpe = calculate_sharpe(df, signals) # Énorme surapprentissage!
if sharpe > best_sharpe:
best_sharpe = sharpe
best_params = (fast, slow)
return best_params # Inutilisable en production!
✅ CORRECT : Walk-Forward Optimization avec hors-échantillon
def walk_forward_optimization(df, param_grid, n_splits=5):
"""
Validation croisée temporelle pour éviter le surapprentissage
"""
train_size = int(len(df) * 0.7) # 70% entraînement
window_size = int(len(df) * 0.15) # 15% validation
all_results = []
for i in range(n_splits):
train_end = train_size + i * window_size
val_start = train_end
val_end = min(val_start + window_size, len(df))
df_train = df.iloc[:train_end]
df_val = df.iloc[val_start:val_end]
# Trouver les meilleurs params sur train
best_train_sharpe = -999
best_params = None
for fast in param_grid['fast']:
for slow in param_grid['slow']:
if fast >= slow:
continue
signals = sma_strategy(df_train, fast, slow)
sharpe = calculate_sharpe(df_train, signals)
if sharpe > best_train_sharpe:
best_train_sharpe = sharpe
best_params = (fast, slow)
# Tester sur validation
signals_val = sma_strategy(df_val, *best_params)
val_sharpe = calculate_sharpe(df_val, signals_val)
all_results.append({
'fold': i,
'train_sharpe': best_train_sharpe,
'val_sharpe': val_sharpe,
'params': best_params
})
print(f"Fold {i}: Train Sharpe={best_train_sharpe:.3f}, "
f"Val Sharpe={val_sharpe:.3f}, Params={best_params}")
# Paramètres robustes : médiane de tous les folds
median_fast = int(np.median([r['params'][0] for r in all_results]))
median_slow = int(np.median([r['params'][1] for r in all_results]))
return (median_fast, median_slow)
Erreur 4 : Gestion Incorrecte des Gaps de Marché
# ❌ MAUVAIS : Ignorer les gaps
def naive_position_check(position, price):
if position > 0 and price < stop_loss:
# Stop-loss déclenché au prix théorique
return price * 0.95 # Perte de 5% magically!
✅ CORRECT : Simulation du prix de remplissage réel
def realistic_stop_execution(position_size, entry_price, stop_price,
current_price, volatility):
"""
Simule l'exécution d'un stop-loss avec slippage basé sur la volatilité
"""
# Prix de marché au moment du stop
market_price = current_price
# Slippage proportionnel à la volatilité
# En marché volatil, le slippage est plus important
slippage_multiplier = 1 + (volatility * 2) # Plus volatile = plus de slippage
# Prix d'exécution du stop (pire que le stop théorique)
execution_price = stop_price * slippage_multiplier
# Pour un long: stop price sous le marché, exécution encore plus basse
execution_price = min(execution_price, market_price * 0.995)
return execution_price
Pour Qui / Pour Qui Ce N'est Pas Fait
| ✅ Ce tutoriel est fait pour vous si : | ❌ Ce tutoriel n'est pas fait pour vous si : |
|---|---|
| Vous avez des bases en Python et souhaitez vous initier au trading algorithmique | Vous cherchez des signaux de trading garantis ou des "graals" de trading |