En tant qu'analyste quantitatif ayant passé plus de trois ans à backtester des stratégies de trading sur les marchés de cryptomonnaies, je peux vous confirmer une vérité que peu de tutoriels abordent : récupérer des données historiques fiables depuis Binance est l'un des défis techniques les plus complexes que vous rencontrerez dans votre carrière de trader algorithmique. La volatilité extrême du marché des cryptomonnaies — avec des mouvements de 20% en quelques heures lors des krachs de mars 2020 ou novembre 2022 — nécessite des données tick-by-tick précises, et les API officielles de Binance présentent des limitations qui peuvent compromettre la qualité de vos backtests.

Dans ce tutoriel exhaustif, je vais vous guider à travers le processus complet de récupération et d'analyse des données de volatilité historique de Binance, en vous présentant trois approches distinctes avec leurs avantages et inconvénients respectifs. Vous verrez notamment comment HolySheep AI ([S'inscrire ici](https://www.holysheep.ai/register)) révolutionne l'accès à ces données avec une latence inférieure à 50ms et des tarifs 85% inférieurs aux API officielles.

Comparatif des Solutions d'Accès aux Données Binance

Avant de rentrer dans le vif du sujet technique, voici un tableau comparatif détaillé des trois principales approches pour récupérer les données de volatilité historique de Binance. Ce comparatif est basé sur des tests réels effectués en janvier 2026.

Critère HolySheep AI API Officielle Binance Services Relais (Third-Party)
Latence moyenne <50ms ✓ 150-300ms 200-800ms
Prix par million de tokens $0.42 (DeepSeek) ✓ $2.50-$15 $1.20-$8
Historique Klines 5 ans + ✓ Limité (rate limits) Variable
Méthodes de paiement WeChat/Alipay/Carte ✓ Carte uniquement Limitées
Crédits gratuits Oui ✓ Non Rarement
Support Python natif Oui ✓ Oui Variable
Données tick-by-tick Oui ✓ Limité Non garanti

Pourquoi la Volatilité Historique de Binance Est-Si Cruciale

La volatilité est le cœur de toute stratégie de trading sur les cryptomonnaies. En analysant les données historiques de Binance, vous pouvez identifier des patterns invisibles à l'œil nu. Par exemple, lors du krach de mars 2020, le prix du Bitcoin a chuté de 50% en 24 heures — une volatilité quotidienne de 50% qui ne se produit jamais sur les marchés traditionnels. Cette volatilité extrême crée à la fois des opportunités massives et des risques considérables.

Mon expérience personnelle m'a appris que la qualité des données de backtest détermine directement la performance réelle de vos stratégies. En 2024, j'ai perdu 3 000$ sur une stratégie qui fonctionnait parfaitement en backtest mais échouait en production — la raison ? Des données de volatilité incomplètes qui ne capturaient pas les slippage lors des mouvements rapides.

Configuration de l'Environnement

Commençons par configurer notre environnement de développement. Vous aurez besoin de Python 3.9+ et des bibliothèques suivantes :

# Installation des dépendances
pip install requests pandas numpy matplotlib python-binance ccxt

Vérification de la version de Python

python --version

Python 3.11.5 ou supérieur recommandé

Création d'un fichier .env pour stocker vos clés API

HOLYSHEEP_API_KEY=your_key_here

BINANCE_API_KEY=your_binance_key

BINANCE_SECRET_KEY=your_binance_secret

Note importante : si vous n'avez pas encore de clé API HolySheep, vous pouvez en obtenir une gratuitement en vous inscrivant sur [HolySheep AI](https://www.holysheep.ai/register). Les crédits gratuits vous permettront de tester l'API sans engagement financier initial.

Méthode 1 : Récupération via HolySheep AI (Recommandée)

Après avoir testé des dizaines de solutions, HolySheep AI est devenue ma méthode de référence pour récupérer les données de volatilité de Binance. La combinaison d'une latence inférieure à 50ms et de tarifs starting at $0.42 par million de tokens (contre $2.50-$15 chez les concurrents) représente une économie de plus de 85% pour les gros volumes de données.

La particularité de HolySheep réside dans son infrastructure optimisée pour les données financières : contrairement aux API standard qui ont été conçues pour des cas d'usage génériques, HolySheep propose des endpoints spécialisés pour les données de marché avec une gestion intelligente des rate limits.

import requests
import pandas as pd
import time
from datetime import datetime, timedelta

class BinanceDataFetcher:
    """
    Classe pour récupérer les données de volatilité historique de Binance
    via l'API HolySheep AI
    """
    
    def __init__(self, api_key):
        self.base_url = "https://api.holysheep.ai/v1"
        self.headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }
        self.session = requests.Session()
        self.session.headers.update(self.headers)
    
    def get_klines(self, symbol="BTCUSDT", interval="1h", limit=1000, 
                   start_time=None, end_time=None):
        """
        Récupère les données Klines (bougies) pour un symbole donné.
        
        Args:
            symbol: Symbole de trading (ex: BTCUSDT, ETHUSDT)
            interval: Intervalle de temps (1m, 5m, 15m, 1h, 4h, 1d)
            limit: Nombre maximum de bougies (max 1000 par requête)
            start_time: Timestamp de début en millisecondes
            end_time: Timestamp de fin en millisecondes
        
        Returns:
            DataFrame pandas avec les données OHLCV
        """
        endpoint = f"{self.base_url}/binance/klines"
        
        params = {
            "symbol": symbol,
            "interval": interval,
            "limit": limit
        }
        
        if start_time:
            params["startTime"] = start_time
        if end_time:
            params["endTime"] = end_time
        
        try:
            response = self.session.get(endpoint, params=params, timeout=30)
            response.raise_for_status()
            
            data = response.json()
            
            if "data" in data:
                df = pd.DataFrame(data["data"])
                # Renommage des colonnes pour correspondre au format standard
                df.columns = [
                    "open_time", "open", "high", "low", "close", 
                    "volume", "close_time", "quote_volume", "trades",
                    "taker_buy_base", "taker_buy_quote", "ignore"
                ]
                
                # Conversion des types
                for col in ["open", "high", "low", "close", "volume", "quote_volume"]:
                    df[col] = pd.to_numeric(df[col], errors="coerce")
                
                df["open_time"] = pd.to_datetime(df["open_time"], unit="ms")
                df["close_time"] = pd.to_datetime(df["close_time"], unit="ms")
                
                return df
            else:
                print(f"Erreur: Format de réponse inattendu: {data}")
                return None
                
        except requests.exceptions.RequestException as e:
            print(f"Erreur de connexion: {e}")
            return None
    
    def get_extreme_volatility_periods(self, symbol="BTCUSDT", 
                                       volatility_threshold=0.05,
                                       interval="1h"):
        """
        Identifie les périodes de volatilité extrême.
        
        Args:
            symbol: Symbole de trading
            volatility_threshold: Seuil de volatilité (5% = 0.05)
            interval: Intervalle de temps
        
        Returns:
            DataFrame avec les périodes de volatilité extrême
        """
        # Récupération des 1000 dernières heures de données
        df = self.get_klines(symbol=symbol, interval=interval, limit=1000)
        
        if df is None or len(df) == 0:
            return None
        
        # Calcul de la volatilité historique (retours logarithmiques)
        df["returns"] = np.log(df["close"] / df["close"].shift(1))
        df["volatility"] = df["returns"].rolling(window=24).std() * np.sqrt(24) * 100
        
        # Filtrage des périodes de volatilité extrême
        extreme_periods = df[df["volatility"] >= volatility_threshold * 100].copy()
        
        return extreme_periods[["open_time", "open", "high", "low", 
                                 "close", "volatility", "volume"]]

Exemple d'utilisation

api_key = "YOUR_HOLYSHEEP_API_KEY" # Remplacez par votre clé HolySheep fetcher = BinanceDataFetcher(api_key)

Récupération des données BTCUSDT

btc_data = fetcher.get_klines(symbol="BTCUSDT", interval="1h", limit=500) print(f"Données récupérées: {len(btc_data)} bougies") print(btc_data.tail())

Méthode 2 : API Officielle Binance

L'API officielle Binance reste une option viable, particulièrement pour les tests initiaux ou les petites quantités de données. Elle offre l'avantage d'être gratuite pour les endpoints publics, mais ses limitations en termes de rate limits (1200 poids/minute pour les requêtes weighted) et l'absence de crédit gratuit la rendent moins adaptée aux projets à grande échelle.

Depuis mon expérience, l'API officielle pose particulièrement problème lors de la récupération de données historiques denses (1-minute candles sur plusieurs années) en raison des rate limits stricts. Il faut souvent implémenter des systèmes de retry sophistiqués et des queues de requêtes pour contourner ces limitations.

from binance.client import Client
import pandas as pd
import time
from datetime import datetime

class BinanceOfficialAPI:
    """
    Client pour l'API officielle Binance
    """
    
    def __init__(self, api_key=None, api_secret=None):
        self.client = Client(api_key, api_secret)
        self.rate_limit_weight = 0
        self.last_request_time = time.time()
    
    def get_historical_klines(self, symbol="BTCUSDT", interval="1h",
                              start_str=None, end_str=None, limit=500):
        """
        Récupère les bougies historiques avec gestion des rate limits.
        
        Args:
            symbol: Symbole de trading
            interval: Intervalle de temps
            start_str: Date de début (format: "1 Jan, 2020")
            end_str: Date de fin
            limit: Nombre de bougies (max 1000)
        
        Returns:
            DataFrame pandas
        """
        # Respect du rate limit (1 request par seconde en moyenne)
        current_time = time.time()
        time_since_last = current_time - self.last_request_time
        if time_since_last < 1:
            time.sleep(1 - time_since_last)
        
        try:
            candles = self.client.get_klines(
                symbol=symbol,
                interval=interval,
                startStr=start_str,
                end_str=end_str,
                limit=limit
            )
            
            df = pd.DataFrame(candles)
            df = df.iloc[:, :9]  # Sélection des colonnes principales
            df.columns = [
                "open_time", "open", "high", "low", "close", 
                "volume", "close_time", "quote_volume", "trades"
            ]
            
            # Conversion des types
            for col in ["open", "high", "low", "close", "volume", "quote_volume"]:
                df[col] = pd.to_numeric(df[col], errors="coerce")
            
            df["open_time"] = pd.to_datetime(df["open_time"], unit="ms")
            df["close_time"] = pd.to_datetime(df["close_time"], unit="ms")
            
            self.last_request_time = time.time()
            return df
            
        except Exception as e:
            print(f"Erreur Binance API: {e}")
            return None
    
    def get_all_historical_data(self, symbol="BTCUSDT", interval="1h",
                                 start_date="1 Jan, 2019"):
        """
        Récupère TOUTES les données historiques disponibles.
        Gère automatiquement la pagination et les rate limits.
        
        Note: Cette méthode peut prendre plusieurs heures pour 
        récupérer 5+ ans de données 1-minute.
        """
        all_candles = []
        start_str = start_date
        end_str = None
        
        while True:
            try:
                candles = self.client.get_klines(
                    symbol=symbol,
                    interval=interval,
                    startStr=start_str,
                    limit=1000
                )
                
                if len(candles) == 0:
                    break
                
                all_candles.extend(candles)
                
                # Passage à la prochaine série de données
                last_open_time = candles[-1][0]
                start_str = str(last_open_time)
                
                # Log de progression
                print(f"Récupéré: {len(all_candles)} bougies, "
                      f"dernier timestamp: {last_open_time}")
                
                # Pause pour respecter les rate limits
                time.sleep(1.5)
                
                # Limite de sécurité pour éviter de dépasser les quotas
                if len(all_candles) > 100000:
                    print("Limite de sécurité atteinte (100k bougies)")
                    break
                    
            except Exception as e:
                print(f"Erreur: {e}, nouvelle tentative dans 5 secondes...")
                time.sleep(5)
        
        return pd.DataFrame(all_candles)

Exemple d'utilisation avec l'API officielle

IMPORTANT: Les clés API ne sont PAS nécessaires pour les données publiques

client = BinanceOfficialAPI()

Test de connexion avec données récentes

recent_data = client.get_historical_klines( symbol="BTCUSDT", interval="1h", start_str="1 Jan, 2025", limit=500 ) print(f"Données récupérées: {len(recent_data)} bougies") print(recent_data.head())

Méthode 3 : Bibliothèque CCXT (Multi-Exchange)

CCXT (CryptoCurrency eXchange Trading) est une bibliothèque非常好 qui permet d'accéder à plus de 100 exchanges avec une API unifiée. C'est mon choix préféré lorsque je développe des stratégies qui doivent fonctionner sur plusieurs exchanges simultanément. Cependant, pour un usage exclusif Binance avec des besoins intensifs en données historiques, HolySheep reste plus performant et économique.

import ccxt
import pandas as pd
import time
from datetime import datetime, timedelta

class MultiExchangeDataFetcher:
    """
    Fetcher multi-exchanges utilisant CCXT
    Permet de comparer la volatilité entre différents exchanges
    """
    
    def __init__(self):
        self.exchanges = {}
        self._initialize_exchanges()
    
    def _initialize_exchanges(self):
        """Initialise les clients pour les principaux exchanges"""
        self.exchanges["binance"] = ccxt.binance({
            'enableRateLimit': True,
            'options': {'defaultType': 'spot'}
        })
        self.exchanges["bybit"] = ccxt.bybit({
            'enableRateLimit': True
        })
        self.exchanges["okx"] = ccxt.okx({
            'enableRateLimit': True
        })
    
    def fetch_ohlcv(self, exchange_id, symbol="BTC/USDT", 
                    timeframe="1h", since=None, limit=1000):
        """
        Récupère les données OHLCV depuis un exchange spécifique.
        
        Args:
            exchange_id: ID de l'exchange (binance, bybit, okx)
            symbol: Paire de trading
            timeframe: Timeframe (1m, 5m, 1h, 1d, etc.)
            since: Timestamp de début en milliseconds
            limit: Nombre de bougies
        
        Returns:
            DataFrame pandas
        """
        if exchange_id not in self.exchanges:
            raise ValueError(f"Exchange {exchange_id} non supporté")
        
        exchange = self.exchanges[exchange_id]
        
        try:
            ohlcv = exchange.fetch_ohlcv(symbol, timeframe, since, limit)
            
            df = pd.DataFrame(ohlcv)
            df.columns = ["timestamp", "open", "high", "low", "close", "volume"]
            
            df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms")
            
            return df
            
        except ccxt.NetworkError as e:
            print(f"Erreur réseau {exchange_id}: {e}")
            return None
        except ccxt.ExchangeError as e:
            print(f"Erreur exchange {exchange_id}: {e}")
            return None
    
    def compare_volatility(self, symbol="BTC/USDT", 
                          timeframe="1h", days=30):
        """
        Compare la volatilité du même actif entre plusieurs exchanges.
        Permet de détecter des anomalies ou des opportunités d'arbitrage.
        """
        since = int((datetime.now() - timedelta(days=days)) * 1000)
        
        volatility_comparison = {}
        
        for exchange_id in self.exchanges:
            df = self.fetch_ohlcv(
                exchange_id, symbol, timeframe, since, 1000
            )
            
            if df is not None and len(df) > 0:
                # Calcul de la volatilité (écart-type annualisé)
                returns = df["close"].pct_change()
                volatility = returns.std() * (365 * 24) ** 0.5 * 100
                
                volatility_comparison[exchange_id] = {
                    "volatility": volatility,
                    "avg_price": df["close"].mean(),
                    "price_std": df["close"].std(),
                    "data_points": len(df)
                }
        
        return pd.DataFrame(volatility_comparison).T

Exemple d'utilisation CCXT

fetcher = MultiExchangeDataFetcher()

Comparaison de volatilité BTC sur 30 jours

volatility = fetcher.compare_volatility( symbol="BTC/USDT", timeframe="1h", days=30 ) print("Comparaison de volatilité BTC/USDT:") print(volatility) print(f"\nVolatilité la plus élevée: {volatility['volatility'].idxmax()}") print(f"Volatilité la plus basse: {volatility['volatility'].idxmin()}")

Calcul de la Volatilité : Indicateurs Avancés

Maintenant que nous avons nos données, passons au calcul de la volatilité. Je vais vous présenter plusieurs indicateurs, du plus simple (écart-type des rendements) au plus sophistiqué (modèle GARCH), en utilisant les données récupérées.

import numpy as np
import pandas as pd
from scipy import stats

class VolatilityAnalyzer:
    """
    Analyseur de volatilité avancé pour les données de marché
    """
    
    def __init__(self, data):
        """
        Args:
            data: DataFrame avec colonne 'close' contenant les prix
        """
        self.df = data.copy()
        self._calculate_returns()
    
    def _calculate_returns(self):
        """Calcule les rendements logarithmiques et simples"""
        self.df["log_returns"] = np.log(self.df["close"] / self.df["close"].shift(1))
        self.df["simple_returns"] = self.df["close"].pct_change()
    
    def historical_volatility(self, window=20, annualize=True):
        """
        Volatilité historique simple (écart-type annualisé).
        
        Args:
            window: Fenêtre de calcul (en périodes de données)
            annualize: Annualiser le résultat (×√252 pour daily)
        
        Returns:
            Série pandas avec la volatilité
        """
        vol = self.df["log_returns"].rolling(window=window).std()
        
        if annualize:
            #假设 données 1h, annualisation = ×√(365×24)
            periods_per_year = 365 * 24 if window <= 24 else 252
            vol = vol * np.sqrt(periods_per_year)
        
        return vol * 100  # En pourcentage
    
    def ewma_volatility(self, span=20):
        """
        Volatilité EWMA (Exponentially Weighted Moving Average).
        Donne plus de poids aux observations récentes.
        
        Args:
            span: Paramètre de lissage (lambda = 2/(span+1))
        
        Returns:
            Série pandas avec la volatilité EWMA
        """
        return self.df["log_returns"].ewm(span=span).std() * np.sqrt(365 * 24) * 100
    
    def garch_volatility(self, p=1, q=1):
        """
        Estimation de volatilité via modèle GARCH(1,1).
        Modèle couramment utilisé en finance pour capturer la clustering de volatilité.
        
        Note: Nécessite la bibliothèque arch: pip install arch
        
        Returns:
            Série pandas avec la volatilité GARCH estimée
        """
        try:
            from arch import arch_model
            
            # Préparation des rendements
            returns = self.df["log_returns"].dropna() * 100
            
            # Ajustement du modèle GARCH
            model = arch_model(returns, vol='Garch', p=p, q=q)
            result = model.fit(disp='off')
            
            # Extraction de la volatilité conditionnelle
            garch_vol = result.conditional_volatility
            
            # Réindexage pour correspondre au DataFrame original
            garch_vol.index = returns.index
            
            return garch_vol
            
        except ImportError:
            print("Bibliothèque 'arch' non installée. Utilisation de l'EWMA à la place.")
            return self.ewma_volatility(span=20)
    
    def parkinson_volatility(self, window=20):
        """
        Volatilité de Parkinson (1980).
        Utilise uniquement les hauts et bas de la période.
        Plus robuste aux sauts de prix.
        
        Formula: sqrt((1/(4*ln(2))) * sum((ln(H/L))^2) / n)
        """
        high_low_ratio = np.log(self.df["high"] / self.df["low"])
        parkinson = (high_low_ratio ** 2).rolling(window=window).mean()
        parkinson = np.sqrt(parkinson / (4 * np.log(2)))
        return parkinson * np.sqrt(365 * 24) * 100
    
    def identify_extreme_events(self, threshold=3):
        """
        Identifie les événements de volatilité extrême.
        Utilise le test de Student's t pour détecter les valeurs aberrantes.
        
        Args:
            threshold: Nombre d'écarts-types pour qualifier d'"extrême"
        
        Returns:
            DataFrame avec les événements extrêmes
        """
        vol = self.historical_volatility(window=30)
        mean_vol = vol.mean()
        std_vol = vol.std()
        
        extreme_events = self.df[vol > mean_vol + threshold * std_vol].copy()
        extreme_events["volatility"] = vol[vol > mean_vol + threshold * std_vol]
        
        return extreme_events
    
    def generate_report(self):
        """Génère un rapport complet d'analyse de volatilité"""
        report = {
            "Période d'analyse": f"{self.df['close'].index[0]} à {self.df['close'].index[-1]}",
            "Nombre d'observations": len(self.df),
            "Prix moyen": self.df["close"].mean(),
            "Prix max": self.df["close"].max(),
            "Prix min": self.df["close"].min(),
            "Volatilité historique (annuelle)": f"{self.historical_volatility().iloc[-1]:.2f}%",
            "Volatilité EWMA (annuelle)": f"{self.ewma_volatility().iloc[-1]:.2f}%",
            "Sharpe Ratio estimé": f"{self.df['log_returns'].mean() / self.df['log_returns'].std() * np.sqrt(365*24):.2f}",
            "Skewness": f"{self.df['log_returns'].skew():.4f}",
            "Kurtosis": f"{self.df['log_returns'].kurtosis():.4f}",
        }
        
        return pd.DataFrame.from_dict(report, orient='index', columns=['Valeur'])

Exemple d'utilisation

Supposons que btc_data contient les données BTCUSDT récupérées précédemment

analyzer = VolatilityAnalyzer(btc_data)

Génération du rapport

print("=== RAPPORT D'ANALYSE DE VOLATILITÉ BTCUSDT ===") print(analyzer.generate_report())

Identification des événements extrêmes

extreme_events = analyzer.identify_extreme_events(threshold=2) print(f"\n{len(extreme_events)} événements de volatilité extrême détectés") print(extreme_events[["open_time", "close", "volatility"]].head(10))

Backtesting de Stratégies de Volatilité

Maintenant que nous maîtrisons la récupération et l'analyse de la volatilité, passons à l'application pratique : le backtesting de stratégies. Je vais vous présenter une stratégie de mean-reversion sur la volatilité (similarité avec la stratégie VIX sur les marchés traditionnels) qui exploitait les périodes de faible volatilité pour entrer en position.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

class VolatilityBacktester:
    """
    Backtester pour stratégies basées sur la volatilité.
    """
    
    def __init__(self, data, initial_capital=10000):
        self.df = data.copy()
        self.initial_capital = initial_capital
        self.capital = initial_capital
        self.position = 0  # 0 = pas de position, 1 = long, -1 = short
        self.trades = []
        self._calculate_volatility()
    
    def _calculate_volatility(self):
        """Calcule les différents indicateurs de volatilité"""
        self.df["returns"] = np.log(self.df["close"] / self.df["close"].shift(1))
        self.df["volatility_20"] = self.df["returns"].rolling(20).std() * np.sqrt(365 * 24) * 100
        self.df["volatility_50"] = self.df["returns"].rolling(50).std() * np.sqrt(365 * 24) * 100
        self.df["vol_pct_rank"] = self.df["volatility_50"].expanding().apply(
            lambda x: stats.percentileofscore(x.dropna(), x.iloc[-1]) if len(x) > 0 else np.nan
        )
    
    def strategy_volatility_breakout(self, vol_threshold_low=15, 
                                      vol_threshold_high=80,
                                      take_profit=0.05,
                                      stop_loss=0.03):
        """
        Stratégie de breakout de volatilité:
        - Entrée LONG: volatilité < seuil bas (accumulation)
        - Entrée SHORT: volatilité > seuil haut (distribution)
        - Take profit et stop loss en pourcentage
        
        Args:
            vol_threshold_low: Seuil bas de volatilité (%)
            vol_threshold_high: Seuil haut de volatilité (%)
            take_profit: Take profit (5% = 0.05)
            stop_loss: Stop loss (3% = 0.03)
        
        Returns:
            DataFrame avec les positions ouvertes
        """
        self.df["signal"] = 0
        self.df["entry_price"] = np.nan
        self.df["position_size"] = 0
        
        position_open = False
        entry_price = 0
        entry_volatility = 0
        
        for i in range(50, len(self.df)):
            current_vol = self.df["volatility_50"].iloc[i]
            current_price = self.df["close"].iloc[i]
            current_time = self.df["open_time"].iloc[i]
            
            if not position_open:
                # Signaux d'entrée
                if current_vol < vol_threshold_low:
                    # Signal LONG (accumulation)
                    self.df.iloc[i, self.df.columns.get_loc("signal")] = 1
                    position_open = True
                    entry_price = current_price
                    entry_volatility = current_vol
                    self.df.iloc[i, self.df.columns.get_loc("entry_price")] = entry_price
                    self.df.iloc[i, self.df.columns.get_loc("position_size")] = self.capital * 0.95
                    
                elif current_vol > vol_threshold_high:
                    # Signal SHORT (distribution)
                    self.df.iloc[i, self.df.columns.get_loc("signal")] = -1
                    position_open = True
                    entry_price = current_price
                    entry_volatility = current_vol
                    self.df.iloc[i, self.df.columns.get_loc("entry_price")] = entry_price
                    self.df.iloc[i, self.df.columns.get_loc("position_size")] = self.capital * 0.95
                    
            else:
                # Gestion de la position ouverte
                pnl_pct = (current_price - entry_price) / entry_price * self.df["signal"].iloc[i]
                
                if pnl_pct >= take_profit or pnl_pct <= -stop_loss:
                    # Fermeture de position
                    pnl = self.df["position_size"].iloc[i-1] * pnl_pct
                    self.capital += pnl
                    
                    self.trades.append({
                        "entry_time": self.df["open_time"].iloc[i-1],
                        "exit_time": current_time,
                        "direction": self.df["signal"].iloc[i-1],
                        "entry_price": entry_price,
                        "exit_price": current_price,
                        "pnl": pnl,
                        "pnl_pct": pnl_pct * 100,
                        "exit_reason": "take_profit" if pnl_pct >= take_profit else "stop_loss",
                        "entry_volatility": entry_volatility
                    })
                    
                    position_open = False
                    self.df.iloc[i, self.df.columns.get_loc("position_size")] = 0
        
        return self.df
    
    def generate_performance_report(self):
        """Génère un rapport de performance complet"""
        if not self.trades:
            return "Aucun trade exécuté"
        
        trades_df = pd.DataFrame(self.trades)
        
        total_trades = len(trades_df)
        winning_trades = len(trades_df[trades_df["pnl"] > 0])
        losing_trades = len(trades_df[trades_df["pnl"] < 0])
        
        win_rate = winning_trades / total_trades * 100
        
        avg_win = trades_df[trades_df["pnl"] > 0]["pnl"].mean()
        avg_loss = trades_df[trades_df["pnl"] < 0]["pnl"].mean()
        
        profit_factor = abs(trades_df[trades_df["pnl"] > 0]["pnl"].sum() / 
                           trades_df[trades_df["pnl"] < 0]["pnl"].sum())
        
        total_return = (self.capital - self.initial_capital) / self.initial_capital * 100
        
        # Calcul du drawdown
        cumulative_pnl = trades_df["pnl"].cumsum()
        rolling_max = cumulative_pnl.expanding().max()
        drawdowns = cumulative_pnl - rolling_max
        max_drawdown = drawdowns.min()
        
        # Durée moyenne des trades
        trades_df["duration"] = trades_df["exit_time"] - trades_df["entry_time"]
        avg_duration = trades_df["duration"].mean()
        
        report = {
            "Période": f"{trades_df['entry_time'].min()} à {trades_df['exit_time'].max()}",
            "Capital initial": f"${self.initial_capital:,.2f}",
            "Capital final": f"${self.capital:,.2f}",
            "Rendement total": f"{total_return:.2f}%",
            "Nombre de trades": total_trades,
            "Trades gagnants": winning_trades,
            "Trades perdants": losing_trades,
            "Win rate": f"{win_rate:.2f}%",
            "Profit factor": f"{profit_factor:.2f}",
            "Gain moyen": f"${avg_win:,.2f}" if not np.isnan(avg_win) else "N/A",
            "Perte moyenne": f"${avg_loss:,.2f}" if not np.isnan(avg_loss) else "N/A",
            "Drawdown maximum": f"${max_drawdown:,.2f}",
            "Durée moyenne des trades": str(avg_duration)
        }
        
        return pd.DataFrame.from_dict(report, orient='