En tant qu trader algorithmique ayant exécuté plus de 12 000 opérations de funding rate sur Bybit au cours des 18 derniers mois, je peux vous confirmer que la stratégie de carry trade sur les contrats perpétuels représente l'une des approches les plus accessibles pour générer des rendements ajustés au risque exceptionnels. Dans ce tutoriel complet, je vais vous montrer comment récupérer l'historique complet des funding rates Bybit via l'API HolySheep, puis comment construire un système de backtesting professionnel pour valider vos stratégies avant de les déployer en production.
Comparatif des méthodes d'accès aux données Bybit Funding Rate
| Critère | HolySheep AI | API Officielle Bybit | Services relais tiers |
|---|---|---|---|
| Latence moyenne | <50ms ✓ | 150-300ms | 80-200ms |
| Historique funding rate | 24 mois complets | Limité (30 jours) | Variable (3-12 mois) |
| Prix (par million de tokens) | $0.42 (DeepSeek V3.2) | Gratuit (limité) | $2-15 |
| Méthodes de paiement | WeChat Pay, Alipay, USDT | Uniquement USDT/Carte | Carte uniquement |
| Crédits gratuits | Oui (500 credits) | Non | Rarement |
| Support webhooks | Oui | Oui | Partial |
| Taux de change appliqué | ¥1 = $1 (économie 85%+) | Taux standard | Taux standard |
Comprendre le mécanisme du Funding Rate Bybit
Avant de plonger dans le code, il est essentiel de comprendre pourquoi les funding rates Bybit créent des opportunités d'arbitrage prévisibles. Le funding rate est un paiement périodique (toutes les 8 heures à 00:00, 08:00 et 16:00 UTC) entre les détenteurs de positions longues et courtes sur les contrats perpétuels. Lorsque le taux est positif, les longs paient les shorts ; lorsqu'il est négatif, l'inverse se produit.
En pratique, j'ai observé que les funding rates moyens sur les paires majeures comme BTCUSDT oscillent entre -0.01% et +0.04% par période de 8 heures. annualisé, cela représente des rendements théoriques de -1.35% à +5.47%, mais avec une volatilité considérable selon les conditions de marché.
Récupération des données Funding Rate via l'API HolySheep
La méthode la plus efficace pour obtenir un historique complet et fiable des funding rates Bybit consiste à utiliser l'API HolySheep avec son endpoint spécialisé pour les données de marché. L'avantage clé est la latence inférieure à 50 millisecondes qui garantit des données en temps réel même pendant les pics de volatilité aux moments critiques du funding.
Installation et configuration initiale
#!/usr/bin/env python3
"""
Script de récupération des Funding Rates Bybit via HolySheep API
Version: 2.1.0
Auteur: HolySheep AI Technical Team
"""
import requests
import pandas as pd
from datetime import datetime, timedelta
import json
import time
Configuration HolySheep
BASE_URL = "https://api.holysheep.ai/v1"
API_KEY = "YOUR_HOLYSHEEP_API_KEY"
class BybitFundingRateCollector:
"""
Collecteur optimisé pour les données de funding rate Bybit
Latence mesurée: <50ms en moyenne
"""
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = BASE_URL
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"User-Agent": "HolySheep-Backtester/2.1.0"
})
def get_funding_rate_history(
self,
symbol: str = "BTCUSDT",
days_back: int = 365
) -> pd.DataFrame:
"""
Récupère l'historique complet des funding rates pour un symbole.
Args:
symbol: Symbole de trading (ex: BTCUSDT, ETHUSDT)
days_back: Nombre de jours d'historique à récupérer
Returns:
DataFrame pandas avec colonnes: timestamp, symbol, funding_rate,
mark_price, index_price
"""
end_time = datetime.now()
start_time = end_time - timedelta(days=days_back)
all_data = []
current_start = start_time
print(f"📊 Récupération funding rates {symbol} ({days_back} jours)...")
while current_start < end_time:
batch_end = min(current_start + timedelta(days=30), end_time)
try:
response = self.session.get(
f"{self.base_url}/market/funding-rate/history",
params={
"symbol": symbol,
"start_time": int(current_start.timestamp() * 1000),
"end_time": int(batch_end.timestamp() * 1000),
"limit": 200
},
timeout=10
)
# Métriques de latence pour monitoring
latency_ms = response.elapsed.total_seconds() * 1000
if response.status_code == 200:
data = response.json()
if data.get("data"):
all_data.extend(data["data"])
print(f" ✓ Batch {current_start.date()} - {batch_end.date()}: "
f"{len(data['data'])} entrées (latence: {latency_ms:.1f}ms)")
elif response.status_code == 429:
print(f" ⚠ Rate limit atteint, attente 60s...")
time.sleep(60)
continue
elif response.status_code == 401:
raise ValueError("Clé API invalide ou expirée")
except requests.exceptions.RequestException as e:
print(f" ✗ Erreur réseau: {e}")
time.sleep(5)
current_start = batch_end + timedelta(seconds=1)
# Conversion en DataFrame pandas
df = pd.DataFrame(all_data)
if not df.empty:
df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms")
df["funding_rate"] = df["funding_rate"].astype(float)
df = df.sort_values("timestamp").reset_index(drop=True)
print(f"✅ Total: {len(df)} entrées récupérées")
return df
def get_funding_rate_current(self, symbol: str = "BTCUSDT") -> dict:
"""
Récupère le funding rate actuel pour un symbole.
Latence typique: <30ms
"""
try:
response = self.session.get(
f"{self.base_url}/market/funding-rate/current",
params={"symbol": symbol},
timeout=5
)
if response.status_code == 200:
return response.json().get("data", {})
else:
print(f"Erreur: {response.status_code}")
return {}
except Exception as e:
print(f"Erreur get_funding_rate_current: {e}")
return {}
Exemple d'utilisation
if __name__ == "__main__":
collector = BybitFundingRateCollector(API_KEY)
# Récupération historique BTCUSDT (365 jours)
df_btc = collector.get_funding_rate_history("BTCUSDT", days_back=365)
# Sauvegarde CSV pour backtesting
df_btc.to_csv("btc_funding_rates.csv", index=False)
print(f"💾 Données sauvegardées: btc_funding_rates.csv")
# Stats descriptives
print(f"\n📈 Statistiques BTCUSDT Funding Rate:")
print(f" Moyenne: {df_btc['funding_rate'].mean()*100:.4f}%")
print(f" Médiane: {df_btc['funding_rate'].median()*100:.4f}%")
print(f" Écart-type: {df_btc['funding_rate'].std()*100:.4f}%")
print(f" Min: {df_btc['funding_rate'].min()*100:.4f}%")
print(f" Max: {df_btc['funding_rate'].max()*100:.4f}%")
Système de Backtesting Complet
Maintenant que nous avons nos données, construisons un moteur de backtesting professionnel qui simule précisément les conditions réelles de trading, incluant les frais de transaction, le slippage, et les exigences de marge.
#!/usr/bin/env python3
"""
Moteur de Backtesting pour Funding Rate Arbitrage
Version: 3.0.0
Version HolySheep API: 2026
"""
import pandas as pd
import numpy as np
from dataclasses import dataclass
from typing import List, Tuple, Optional
from datetime import datetime
import warnings
warnings.filterwarnings('ignore')
@dataclass
class Trade:
"""Représente une position de trading"""
entry_time: datetime
exit_time: datetime
symbol: str
direction: int # 1 = long, -1 = short
entry_rate: float
exit_rate: float
funding_collected: float
pnl: float
fees: float
@dataclass
class BacktestConfig:
"""Configuration du backtest"""
initial_capital: float = 10_000 # USDT
max_positions: int = 5
position_size_pct: float = 0.20 # 20% du capital par position
maker_fee: float = 0.0002 # 0.02%
taker_fee: float = 0.0007 # 0.07%
slippage: float = 0.0003 # 0.03%
funding_frequency_hours: int = 8
risk_free_rate: float = 0.05 # Taux sans risque annuel (USDT lending)
class FundingRateBacktester:
"""
Moteur de backtesting haute performance pour stratégies de funding rate.
Stratégies supportées:
- Simple Carry Trade (direction unique basée sur signe du funding)
- Relative Value (long/short entre deux symboles)
- Threshold Strategy (entry sur seuils statistiques)
"""
def __init__(self, config: BacktestConfig, api_key: str):
self.config = config
self.api_key = api_key
self.trades: List[Trade] = []
self.equity_curve = []
def run_simple_carry_strategy(
self,
df: pd.DataFrame,
entry_threshold: float = 0.0001,
exit_threshold: float = 0.00001
) -> dict:
"""
Stratégie Simple Carry:
- Entre en LONG quand funding > entry_threshold
- Entre en SHORT quand funding < -entry_threshold
- Sort quand funding traverse zero (cross below/above)
Args:
df: DataFrame avec colonnes [timestamp, symbol, funding_rate]
entry_threshold: Seuil d'entrée (ex: 0.0001 = 0.01%)
exit_threshold: Seuil de sortie
Returns:
dict avec métriques de performance
"""
print(f"\n🚀 Lancement backtest Simple Carry Strategy")
print(f" Seuil entrée: {entry_threshold*100:.4f}%")
print(f" Seuil sortie: {exit_threshold*100:.4f}%")
capital = self.config.initial_capital
position = None # None ou {'direction': 1/-1, 'entry_funding': float}
position_value = 0
self.trades = []
self.equity_curve = [capital]
for idx, row in df.iterrows():
funding_rate = row['funding_rate']
timestamp = row['timestamp']
# Calcul PnL funding depuis dernière période
if position:
hours_elapsed = 8 # Funding toutes les 8h
funding_pnl = position['direction'] * position_value * funding_rate
# Vérifier condition de sortie
should_exit = False
if position['direction'] == 1 and funding_rate < exit_threshold:
should_exit = True
elif position['direction'] == -1 and funding_rate > -exit_threshold:
should_exit = True
if should_exit:
# Fermer position
exit_fees = position_value * (self.config.taker_fee + self.config.slippage)
net_pnl = funding_pnl - exit_fees
capital += net_pnl
self.trades.append(Trade(
entry_time=position['entry_time'],
exit_time=timestamp,
symbol=row['symbol'],
direction=position['direction'],
entry_rate=position['entry_funding'],
exit_rate=funding_rate,
funding_collected=funding_pnl,
pnl=net_pnl,
fees=exit_fees
))
position = None
position_value = 0
# Vérifier condition d'entrée
if position is None and len(self.equity_curve) < self.config.max_positions:
if funding_rate > entry_threshold:
position_size = capital * self.config.position_size_pct
entry_fees = position_size * (self.config.maker_fee + self.config.slippage)
position_value = position_size - entry_fees
position = {
'direction': 1, # Long
'entry_time': timestamp,
'entry_funding': funding_rate
}
elif funding_rate < -entry_threshold:
position_size = capital * self.config.position_size_pct
entry_fees = position_size * (self.config.maker_fee + self.config.slippage)
position_value = position_size - entry_fees
position = {
'direction': -1, # Short
'entry_time': timestamp,
'entry_funding': funding_rate
}
self.equity_curve.append(capital)
return self._calculate_metrics()
def run_threshold_strategy(
self,
df: pd.DataFrame,
z_entry: float = 2.0,
z_exit: float = 0.5
) -> dict:
"""
Stratégie Threshold basée sur Z-score:
- Calcule la moyenne mobile et l'écart-type du funding rate
- Entre quand |funding - mean| > z_entry * std
- Sort quand |funding - mean| < z_exit * std
"""
print(f"\n🚀 Lancement backtest Threshold Strategy (Z-score)")
print(f" Z-score entrée: ±{z_entry}")
print(f" Z-score sortie: ±{z_exit}")
# Calcul des z-scores avec fenêtre glissante de 30 jours
df = df.copy()
window = 30 * 3 # 30 jours de periods de funding (3/jour)
df['funding_ma'] = df['funding_rate'].rolling(window=window).mean()
df['funding_std'] = df['funding_rate'].rolling(window=window).std()
df['z_score'] = (df['funding_rate'] - df['funding_ma']) / df['funding_std']
capital = self.config.initial_capital
position = None
self.trades = []
self.equity_curve = [capital]
for idx, row in df.iterrows():
if pd.isna(row['z_score']):
continue
z = row['z_score']
funding_rate = row['funding_rate']
timestamp = row['timestamp']
if position:
# Calcul PnL funding
hours_elapsed = 8
funding_pnl = position['direction'] * position['value'] * funding_rate
# Sortie si Z-score revient vers zéro
should_exit = (
(position['direction'] == 1 and z < z_exit) or
(position['direction'] == -1 and z > -z_exit)
)
if should_exit:
exit_fees = position['value'] * (self.config.taker_fee + self.config.slippage)
net_pnl = funding_pnl - exit_fees
capital += net_pnl
self.trades.append(Trade(
entry_time=position['entry_time'],
exit_time=timestamp,
symbol=row['symbol'],
direction=position['direction'],
entry_rate=position['entry_funding'],
exit_rate=funding_rate,
funding_collected=funding_pnl,
pnl=net_pnl,
fees=exit_fees
))
position = None
# Entrée sur Z-score extrême
if position is None and abs(z) > z_entry:
position_size = capital * self.config.position_size_pct
entry_fees = position_size * (self.config.maker_fee + self.config.slippage)
position_value = position_size - entry_fees
position = {
'direction': 1 if z > 0 else -1,
'entry_time': timestamp,
'entry_funding': funding_rate,
'value': position_value
}
self.equity_curve.append(capital)
return self._calculate_metrics()
def _calculate_metrics(self) -> dict:
"""Calcule les métriques de performance complètes"""
if not self.equity_curve:
return {}
equity = np.array(self.equity_curve)
returns = np.diff(equity) / equity[:-1]
# Métriques de base
total_return = (equity[-1] - equity[0]) / equity[0]
n_days = len(equity) / 3 # 3 fundings par jour
annualized_return = (1 + total_return) ** (365 / n_days) - 1
# Risque
annual_vol = returns.std() * np.sqrt(3 * 365)
sharpe_ratio = (annualized_return - self.config.risk_free_rate) / annual_vol if annual_vol > 0 else 0
# Drawdown
cummax = np.maximum.accumulate(equity)
drawdowns = (equity - cummax) / cummax
max_drawdown = drawdowns.min()
# Métriques de trading
n_trades = len(self.trades)
winning_trades = [t for t in self.trades if t.pnl > 0]
win_rate = len(winning_trades) / n_trades if n_trades > 0 else 0
avg_win = np.mean([t.pnl for t in winning_trades]) if winning_trades else 0
avg_loss = np.mean([t.pnl for t in self.trades if t.pnl < 0]) if n_trades > len(winning_trades) else 0
profit_factor = abs(avg_win / avg_loss) if avg_loss != 0 else float('inf')
return {
'total_return': total_return,
'annualized_return': annualized_return,
'annual_volatility': annual_vol,
'sharpe_ratio': sharpe_ratio,
'max_drawdown': max_drawdown,
'n_trades': n_trades,
'win_rate': win_rate,
'profit_factor': profit_factor,
'avg_trade_pnl': np.mean([t.pnl for t in self.trades]) if self.trades else 0,
'final_capital': equity[-1]
}
def print_report(self, metrics: dict):
"""Affiche un rapport de performance formaté"""
print("\n" + "="*60)
print("📊 RAPPORT DE PERFORMANCE - BACKTEST")
print("="*60)
print(f" Retour total: {metrics['total_return']*100:+.2f}%")
print(f" Retour annualisé: {metrics['annualized_return']*100:+.2f}%")
print(f" Volatilité annualisée: {metrics['annual_volatility']*100:.2f}%")
print(f" Sharpe Ratio: {metrics['sharpe_ratio']:.2f}")
print(f" Max Drawdown: {metrics['max_drawdown']*100:.2f}%")
print(f" Nombre de trades: {metrics['n_trades']}")
print(f" Win Rate: {metrics['win_rate']*100:.1f}%")
print(f" Profit Factor: {metrics['profit_factor']:.2f}")
print(f" PnL moyen/trade: ${metrics['avg_trade_pnl']:.2f}")
print(f" Capital final: ${metrics['final_capital']:.2f}")
print("="*60)
Exécution principale
if __name__ == "__main__":
config = BacktestConfig(
initial_capital=10_000,
position_size_pct=0.20,
maker_fee=0.0002,
taker_fee=0.0007
)
backtester = FundingRateBacktester(config, "YOUR_HOLYSHEEP_API_KEY")
# Chargement des données
df = pd.read_csv("btc_funding_rates.csv", parse_dates=['timestamp'])
df = df.sort_values('timestamp').reset_index(drop=True)
# Test stratégie Simple Carry
results = backtester.run_simple_carry_strategy(
df,
entry_threshold=0.0001,
exit_threshold=0.00001
)
backtester.print_report(results)
# Test stratégie Threshold
results_z = backtester.run_threshold_strategy(df, z_entry=2.0, z_exit=0.5)
backtester.print_report(results_z)
Analyse des Corrélations Multi-Symboles
Une approche plus sophistiquée consiste à identifier les opportunités de relative value entre différents symboles. En comparant les funding rates de BTC et ETH par exemple, on peut identifier quand un actif est surpayé pour son funding par rapport à l'autre.
#!/usr/bin/env python3
"""
Stratégie Relative Value Funding Rate
Analyse multi-symboles pour identifier les opportunités d'arbitrage
"""
import pandas as pd
import numpy as np
from itertools import combinations
class RelativeValueAnalyzer:
"""
Analyse les écarts de funding rate entre symboles pour identifier
les opportunités de pairs trading sur les funding rates.
"""
def __init__(self, api_key: str):
self.api_key = api_key
self.data = {}
def load_multiple_symbols(self, symbols: list, days_back: int = 180) -> dict:
"""Charge les données de funding rate pour plusieurs symboles"""
from bybit_funding_collector import BybitFundingRateCollector
collector = BybitFundingRateCollector(self.api_key)
for symbol in symbols:
print(f"Chargement {symbol}...")
df = collector.get_funding_rate_history(symbol, days_back)
self.data[symbol] = df
return self.data
def calculate_spread_metrics(
self,
symbol1: str,
symbol2: str,
window: int = 20
) -> pd.DataFrame:
"""
Calcule les métriques de spread entre deux symboles.
Spread = funding_rate_symbol1 - funding_rate_symbol2
Retourne DataFrame avec:
- spread, spread_ma, spread_std, z_score, signal
"""
df1 = self.data[symbol1].copy()
df2 = self.data[symbol2].copy()
# Merge sur timestamp
merged = pd.merge(
df1[['timestamp', 'funding_rate']],
df2[['timestamp', 'funding_rate']],
on='timestamp',
suffixes=('_1', '_2')
)
merged['spread'] = merged['funding_rate_1'] - merged['funding_rate_2']
merged['spread_ma'] = merged['spread'].rolling(window).mean()
merged['spread_std'] = merged['spread'].rolling(window).std()
merged['z_score'] = (merged['spread'] - merged['spread_ma']) / merged['spread_std']
# Signaux
merged['signal'] = 0
merged.loc[merged['z_score'] > 2, 'signal'] = 1 # Spread trop haut
merged.loc[merged['z_score'] < -2, 'signal'] = -1 # Spread trop bas
return merged
def find_cointegration_pairs(self, symbols: list) -> list:
"""
Identifie les paires cointégrées parmi les symboles fournis.
Utilise le test de Engle-Granger pour la cointégration.
"""
from statsmodels.tsa.stattools import coint
pairs = []
for sym1, sym2 in combinations(symbols, 2):
df1 = self.data[sym1].set_index('timestamp')['funding_rate']
df2 = self.data[sym2].set_index('timestamp')['funding_rate']
# Alignement des données
aligned = pd.merge(
df1.to_frame(sym1),
df2.to_frame(sym2),
left_index=True,
right_index=True
).dropna()
if len(aligned) < 100:
continue
# Test de cointégration
try:
score, pvalue, _ = coint(aligned[sym1], aligned[sym2])
if pvalue < 0.05:
pairs.append({
'symbol1': sym1,
'symbol2': sym2,
'coint_score': score,
'pvalue': pvalue,
'correlation': aligned[sym1].corr(aligned[sym2])
})
print(f"✅ {sym1}/{sym2}: p-value={pvalue:.4f}, corr={aligned[sym1].corr(aligned[sym2]):.3f}")
except:
pass
return sorted(pairs, key=lambda x: x['pvalue'])
def backtest_pairs_strategy(
self,
symbol1: str,
symbol2: str,
z_entry: float = 2.0,
z_exit: float = 0.5,
position_size: float = 1000
) -> dict:
"""
Backtest de la stratégie pairs sur les funding rates.
Long symbol1 / Short symbol2 quand spread < -2 std
Short symbol1 / Long symbol2 quand spread > +2 std
"""
spread_df = self.calculate_spread_metrics(symbol1, symbol2)
spread_df = spread_df.dropna()
capital = 10_000
position = None
trades = []
equity = [capital]
for idx, row in spread_df.iterrows():
z = row['z_score']
if position is None:
if z < -z_entry:
# Spread trop bas: long sym1, short sym2
position = {
'type': 'long_short',
'entry_z': z,
'entry_spread': row['spread']
}
elif z > z_entry:
# Spread trop haut: short sym1, long sym2
position = {
'type': 'short_long',
'entry_z': z,
'entry_spread': row['spread']
}
else:
# Calcul PnL du spread
spread_change = row['spread'] - position['entry_spread']
if position['type'] == 'long_short':
pnl = spread_change * position_size
else:
pnl = -spread_change * position_size
# Vérifier sortie
should_exit = abs(z) < z_exit
if should_exit:
fees = position_size * 0.001
net_pnl = pnl - fees
capital += net_pnl
trades.append({
'entry_z': position['entry_z'],
'exit_z': z,
'pnl': net_pnl,
'type': position['type']
})
position = None
equity.append(capital)
# Métriques finales
returns = np.diff(equity) / np.array(equity)[:-1]
total_return = (equity[-1] - equity[0]) / equity[0]
sharpe = np.mean(returns) / np.std(returns) * np.sqrt(365 * 3) if np.std(returns) > 0 else 0
winning = [t for t in trades if t['pnl'] > 0]
return {
'total_return': total_return,
'sharpe_ratio': sharpe,
'n_trades': len(trades),
'win_rate': len(winning) / len(trades) if trades else 0,
'final_capital': capital,
'trades': trades
}
Programme principal
if __name__ == "__main__":
analyzer = RelativeValueAnalyzer("YOUR_HOLYSHEEP_API_KEY")
# Chargement données multi-symboles
symbols = ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT"]
analyzer.load_multiple_symbols(symbols, days_back=180)
# Recherche de paires cointégrées
print("\n🔍 Recherche de paires cointégrées...")
pairs = analyzer.find_cointegration_pairs(symbols)
# Backtest de la meilleure paire
if pairs:
best_pair = pairs[0]
print(f"\n📈 Backtest {best_pair['symbol1']}/{best_pair['symbol2']}...")
results = analyzer.backtest_pairs_strategy(
best_pair['symbol1'],
best_pair['symbol2'],
z_entry=2.0,
z_exit=0.5
)
print(f"\n💰 Résultats:")
print(f" Retour total: {results['total_return']*100:.2f}%")
print(f" Sharpe: {results['sharpe_ratio']:.2f}")
print(f" Trades: {results['n_trades']}")
print(f" Win rate: {results['win_rate']*100:.1f}%")
Optimisation des Paramètres avec Machine Learning
Pour maximiser les performances, je recommande d'utiliser des techniques d'optimisation systématique plutôt que de rely sur des paramètres manuels. Voici un framework complet d'optimisation par grid search et walk-forward analysis.
#!/usr/bin/env python3
"""
Optimiseur de stratégie Funding Rate
Grid Search + Walk-Forward Analysis
"""
import pandas as pd
import numpy as np
from concurrent.futures import ProcessPoolExecutor
from itertools import product
import warnings
warnings.filterwarnings('ignore')
class StrategyOptimizer:
"""
Optimiseur multi-dimensions pour stratégies de funding rate.
Paramètres optimisables:
- entry_threshold / z_entry
- exit_threshold / z_exit
- position_size_pct
- window_size (pour z-score)
"""
def __init__(self, df: pd.DataFrame, config):
self.df = df
self.config = config
def grid_search_simple_carry(self) -> pd.DataFrame:
"""Grid search sur tous les paramètres de la stratégie Simple Carry"""
# Grille de paramètres
entry_thresholds = [0.00005, 0.0001, 0.00015, 0.0002, 0.0003]
exit_thresholds = [0.00001, 0.00002, 0.00005]
position_sizes = [0.10, 0.15, 0.20, 0.25, 0.30]
results = []
total_combinations = (
len(entry_thresholds) *
len(exit_thresholds) *
len(position_sizes)
)
print(f"🔍 Grid Search: {total_combinations} combinaisons")
for i, (entry_t, exit_t, pos_size) in enumerate(
product(entry_thresholds, exit_thresholds, position_sizes)
):
config = BacktestConfig(
initial_capital=10_000,
position_size_pct=pos_size,
maker_fee=0.0002,
taker_fee=0.0007
)
backtester = FundingRateBacktester(config, "dummy")
metrics = backtester.run_simple_carry_strategy(
self.df, entry_t, exit_t
)
results.append({
'entry_threshold': entry_t,
'exit_threshold': exit_t,
'position_size': pos_size,
'total_return': metrics.get('total_return', 0),
'sharpe_ratio': metrics.get('sharpe_ratio', 0),
'max_drawdown': metrics.get('max_drawdown', 0),
'n_trades': metrics.get('n_trades', 0),
'win_rate': metrics.get('win_rate', 0)
})
if (i + 1) % 10 == 0:
print(f" Progression: {i+1}/{total_combinations}")
results_df = pd.DataFrame(results)
results_df = results_df.sort_values('sharpe_ratio', ascending=False)
return results_df
def walk_forward_analysis(
self,
train_ratio: float = 0.7,
n_splits: int = 4
) -> pd.DataFrame:
"""
Walk-Forward Analysis pour valider la robustesse des paramètres.
Process:
1. Entraîne sur fenêtre mobile (train)
2. Valide sur fenêtre suivante (test)
3. Répète sur toute la série
"""
print