En tant que développeur d'algorithmes de trading depuis plus de cinq ans, j'ai testé des dizaines de sources de données pour le backtesting de stratégies sur les contrats perpétuels Bybit. Le problème majeur ? Obtenir des données tick-by-tick fiables, historiquement complètes et à un coût raisonnable. Après des mois d'utilisation intensive, Tardis.dev s'est imposé comme la solution la plus robuste pour collecter et analyser les données de trades Bybit. Dans cet article, je vous partage ma méthodologie complète, du téléchargement des données brutes à l'exécution de backtests professionnels avec analyse de slippage et de latence.

Pourquoi Bybit et les contrats perpétuels ?

En 2026, Bybit représente plus de 25% du volume d'échange sur les marchés de dérivés cryptos avec des contrats perpétuels BTC, ETH et des dizaines d'altcoins. La liquidité profonde et les frais compétitifs (0,02% maker, 0,055% taker) en font un terrain de jeu idéal pour les stratégies de market making, d'arbitrage et de scalping. Cependant, le backtesting précis nécessite des données de trades individuelles (pas des bougies OHLCV), car les stratégies haute fréquence sont sensibles aux micro-structures du marché.

Introduction à Tardis.dev : la solution pour les données de marché cryptographiques

Tardis.dev (anciennement tardis.dev) est une plateforme spécialisée dans la collecte et la distribution de données de marché historiques pour les exchanges cryptographiques. Contrairement aux fournisseurs traditionnels, ils proposent des données tick-by-tick avec une granularité maximale et des options de format flexibles (JSON, CSV, Parquet).

Caractéristiques principales de Tardis.dev

Configuration initiale et installation des dépendances

Avant de commencer, vous aurez besoin d'un compte Tardis.dev avec un crédit suffisant. Les prix en 2026 sont compétitifs : environ 0,00004 $ par message pour les données de trades, avec des forfaits mensuels à partir de 99$/mois pour les utilisateurs professionnels. Pour un projet de backtesting sérieux, je recommande le forfait "Pro" à 299$/mois qui inclut l'accès complet à tous les exchanges et les formats de données avancés.

# Installation des dépendances Python
pip install pandas numpy requests aiohttp asyncio ta

Pour le stockage optimisé

pip install pyarrow fastparquet

Validation et visualisation

pip install matplotlib seaborn jupyter
# Structure du projet recommandé
backtesting-bybit/
├── config/
│   ├── __init__.py
│   ├── api_config.py      # Configuration Tardis.dev
│   └── strategy_config.py # Paramètres de stratégie
├── data/
│   ├── raw/               # Données brutes téléchargées
│   └── processed/         # Données nettoyées
├── strategies/
│   ├── base_strategy.py
│   └── momentum_strategy.py
├── backtest/
│   ├── engine.py          # Moteur de backtesting
│   └── metrics.py         # Calcul des métriques
├── main.py                # Point d'entrée
└── requirements.txt

Téléchargement des données Bybit avec l'API Tardis.dev

La première étape consiste à récupérer les données de trades historiques depuis Tardis.dev. L'API propose un endpoint simple pour télécharger des intervalles de temps spécifiques. Pour un backtest complet, je recommande de commencer par une période de test de 30 jours pour valider votre stratégie avant d'investir dans des données plus longues.

import requests
import pandas as pd
import os
from datetime import datetime, timedelta
from typing import Optional, List
import time

class TardisDataFetcher:
    """Classe pour télécharger les données de marché depuis Tardis.dev"""
    
    BASE_URL = "https://api.tardis.dev/v1"
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.session = requests.Session()
        self.session.headers.update({
            'Authorization': f'Bearer {api_key}',
            'Content-Type': 'application/json'
        })
    
    def get_trades_bybit(
        self,
        symbol: str,
        start_date: datetime,
        end_date: datetime,
        exchange: str = "bybit",
        format_type: str = "csv"
    ) -> pd.DataFrame:
        """
        Télécharge les données de trades pour un symbole donné.
        
        Args:
            symbol: Symbole de trading (ex: "BTCUSDT")
            start_date: Date de début
            end_date: Date de fin
            exchange: Exchange cible (par défaut "bybit")
            format_type: Format de données ("csv", "json", "parquet")
        
        Returns:
            DataFrame pandas avec les données de trades
        """
        # Construire l'URL de l'API
        start_ts = int(start_date.timestamp() * 1000)
        end_ts = int(end_date.timestamp() * 1000)
        
        url = f"{self.BASE_URL}/exports/{exchange}/trades"
        params = {
            'symbol': symbol,
            'from': start_ts,
            'to': end_ts,
            'format': format_type,
            'limit': 100000  # Maximum par requête
        }
        
        print(f"📥 Téléchargement des trades {symbol}...")
        print(f"   Période: {start_date} → {end_date}")
        print(f"   Timestamp: {start_ts} → {end_ts}")
        
        try:
            response = self.session.get(url, params=params, timeout=120)
            response.raise_for_status()
            
            data = response.json()
            
            # Récupérer l'URL de téléchargement
            if 'downloadUrl' in data:
                download_url = data['downloadUrl']
                print(f"✅ Téléchargement depuis: {download_url[:50]}...")
                
                # Télécharger le fichier
                file_response = self.session.get(download_url, timeout=300)
                file_response.raise_for_status()
                
                # Parser selon le format
                if format_type == 'csv':
                    from io import StringIO
                    df = pd.read_csv(StringIO(file_response.text))
                elif format_type == 'json':
                    df = pd.read_json(file_response.text)
                else:
                    raise ValueError(f"Format non supporté: {format_type}")
                
                print(f"✅ {len(df)} trades récupérés")
                return df
            else:
                raise Exception(f"Réponse API invalide: {data}")
                
        except requests.exceptions.RequestException as e:
            print(f"❌ Erreur de requête: {e}")
            raise
    
    def estimate_cost(self, symbol: str, days: int, exchange: str = "bybit") -> dict:
        """Estime le coût du téléchargement en crédits"""
        # Rough estimate basé sur le volume moyen
        avg_trades_per_day = {
            "BTCUSDT": 2_500_000,
            "ETHUSDT": 1_800_000,
            "SOLUSDT": 850_000,
        }
        
        trades_estimate = avg_trades_per_day.get(symbol, 500_000) * days
        cost_per_message = 0.00004  # $ par message
        estimated_cost = trades_estimate * cost_per_message
        
        return {
            "trades_estimate": trades_estimate,
            "cost_usd": estimated_cost,
            "credits_needed": trades_estimate
        }

Exemple d'utilisation

if __name__ == "__main__": API_KEY = "YOUR_TARDIS_API_KEY" fetcher = TardisDataFetcher(API_KEY) # Estimer le coût pour 30 jours de BTCUSDT cost = fetcher.estimate_cost("BTCUSDT", days=30) print(f"Estimation coût 30 jours BTCUSDT:") print(f" - Trades estimés: {cost['trades_estimate']:,}") print(f" - Coût estimé: ${cost['cost_usd']:.2f}")
import asyncio
import aiohttp
from typing import List, Tuple
import json

class AsyncTardisFetcher:
    """Version asynchrone pour télécharger plusieurs symboles en parallèle"""
    
    BASE_URL = "https://api.tardis.dev/v1"
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.session: Optional[aiohttp.ClientSession] = None
    
    async def __aenter__(self):
        self.session = aiohttp.ClientSession(
            headers={'Authorization': f'Bearer {self.api_key}'}
        )
        return self
    
    async def __aexit__(self, *args):
        if self.session:
            await self.session.close()
    
    async def fetch_symbol_trades(
        self,
        symbol: str,
        start_ts: int,
        end_ts: int,
        exchange: str = "bybit"
    ) -> Tuple[str, dict]:
        """Télécharge les trades pour un symbole"""
        url = f"{self.BASE_URL}/exports/{exchange}/trades"
        params = {
            'symbol': symbol,
            'from': start_ts,
            'to': end_ts,
            'format': 'json',
            'limit': 100000
        }
        
        async with self.session.get(url, params=params) as response:
            if response.status == 200:
                data = await response.json()
                return (symbol, data)
            else:
                error = await response.text()
                return (symbol, {'error': error, 'status': response.status})
    
    async def fetch_multiple_symbols(
        self,
        symbols: List[str],
        start_ts: int,
        end_ts: int
    ) -> dict:
        """Télécharge plusieurs symboles en parallèle"""
        tasks = [
            self.fetch_symbol_trades(symbol, start_ts, end_ts)
            for symbol in symbols
        ]
        
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        output = {}
        for result in results:
            if isinstance(result, Exception):
                print(f"❌ Erreur: {result}")
            else:
                symbol, data = result
                if 'error' not in data:
                    output[symbol] = data
                    print(f"✅ {symbol}: {len(data.get('data', []))} trades")
                else:
                    print(f"❌ {symbol}: {data.get('error')}")
        
        return output

Utilisation asynchrone

async def main(): symbols = ["BTCUSDT", "ETHUSDT", "SOLUSDT"] start = int(datetime(2026, 1, 1).timestamp() * 1000) end = int(datetime(2026, 1, 31).timestamp() * 1000) async with AsyncTardisFetcher("YOUR_TARDIS_API_KEY") as fetcher: results = await fetcher.fetch_multiple_symbols(symbols, start, end) # Sauvegarder les résultats for symbol, data in results.items(): with open(f"data/raw/{symbol}_trades.json", "w") as f: json.dump(data, f) if __name__ == "__main__": asyncio.run(main())

Construction du moteur de backtesting

Maintenant que nous avons les données, construisons un moteur de backtesting professionnel capable de simuler les exécutions avec précision. Un bon moteur doit prendre en compte le slippage, les frais de transaction, la latence de marché et les contraintes de liquidité pour donner des résultats réalistes.

import pandas as pd
import numpy as np
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple
from enum import Enum
from datetime import datetime
import warnings

class OrderSide(Enum):
    BUY = "buy"
    SELL = "sell"

class OrderType(Enum):
    MARKET = "market"
    LIMIT = "limit"

@dataclass
class Trade:
    """Représente un trade individuel du marché"""
    timestamp: int
    price: float
    amount: float      # Quantité en USDT
    side: str          # 'buy' ou 'sell'
    id: str
    trade_fee: float = 0.0

@dataclass
class Order:
    """Représente un ordre passé par la stratégie"""
    id: str
    timestamp: int
    side: OrderSide
    order_type: OrderType
    quantity: float
    price: Optional[float] = None
    filled_price: Optional[float] = None
    status: str = "pending"
    fees: float = 0.0

@dataclass
class BacktestConfig:
    """Configuration du backtest"""
    initial_capital: float = 100_000.0
    maker_fee: float = 0.0002      # 0.02%
    taker_fee: float = 0.00055     # 0.055%
    slippage_bps: float = 2.0      # Slippage en basis points
    latency_ms: int = 50           # Latence de simulation
    max_position_size: float = 1.0 # Position max en % du capital

@dataclass
class BacktestResult:
    """Résultats du backtest"""
    equity_curve: pd.DataFrame
    trades: List[Order]
    metrics: Dict

class BacktestEngine:
    """
    Moteur de backtesting haute performance pour les données tick-by-tick.
    Simule l'exécution réaliste des ordres avec slippage et frais.
    """
    
    def __init__(self, config: BacktestConfig):
        self.config = config
        self.capital = config.initial_capital
        self.position = 0.0
        self.position_entry_price = 0.0
        self.trades: List[Order] = []
        self.equity_history: List[Dict] = []
        self.current_timestamp = 0
        
    def execute_market_order(
        self,
        side: OrderSide,
        quantity: float,
        timestamp: int,
        current_price: float
    ) -> Order:
        """Exécute un ordre au marché avec slippage"""
        
        # Calcul du slippage
        slippage_factor = 1 + (self.config.slippage_bps / 10000)
        
        if side == OrderSide.BUY:
            execution_price = current_price * slippage_factor
        else:
            execution_price = current_price / slippage_factor
        
        # Calcul des frais (taker pour ordre marché)
        fees = quantity * execution_price * self.config.taker_fee
        
        # Créer l'ordre
        order = Order(
            id=f"order_{timestamp}_{np.random.randint(10000)}",
            timestamp=timestamp,
            side=side,
            order_type=OrderType.MARKET,
            quantity=quantity,
            filled_price=execution_price,
            status="filled",
            fees=fees
        )
        
        # Mettre à jour le capital et la position
        cost = quantity * execution_price + fees
        
        if side == OrderSide.BUY:
            if cost > self.capital:
                # Ajustement si fonds insuffisants
                max_qty = self.capital / (execution_price * (1 + self.config.taker_fee))
                quantity = max_qty * 0.99  # Marge de sécurité
                cost = quantity * execution_price + fees
            
            self.capital -= cost
            self.position += quantity
            if self.position_entry_price == 0:
                self.position_entry_price = execution_price
        else:
            if quantity > self.position:
                quantity = self.position
                cost = quantity * execution_price
            
            self.capital += quantity * execution_price - fees
            self.position -= quantity
            if self.position == 0:
                self.position_entry_price = 0
        
        order.quantity = quantity
        self.trades.append(order)
        
        return order
    
    def run(
        self,
        trades_df: pd.DataFrame,
        strategy,
        verbose: bool = False
    ) -> BacktestResult:
        """
        Exécute le backtest sur les données de trades.
        
        Args:
            trades_df: DataFrame avec les colonnes ['timestamp', 'price', 'amount', 'side']
            strategy: Instance de stratégie avec méthodes generate_signals()
            verbose: Afficher les détails de chaque trade
        """
        
        # Préparer les données
        trades_df = trades_df.sort_values('timestamp').reset_index(drop=True)
        
        if verbose:
            print(f"🚀 Démarrage du backtest")
            print(f"   Données: {len(trades_df)} trades")
            print(f"   Capital initial: ${self.config.initial_capital:,.2f}")
        
        # Itérer sur chaque trade
        for idx, row in trades_df.iterrows():
            timestamp = row['timestamp']
            price = row['price']
            amount = row['amount']
            side = row['side']
            
            self.current_timestamp = timestamp
            
            # Générer les signaux de la stratégie
            signals = strategy.generate_signals(
                timestamp=timestamp,
                price=price,
                amount=amount,
                side=side,
                position=self.position,
                capital=self.capital
            )
            
            # Exécuter les signaux
            for signal in signals:
                if signal['action'] == 'buy':
                    order = self.execute_market_order(
                        OrderSide.BUY,
                        signal['quantity'],
                        timestamp,
                        price
                    )
                    if verbose:
                        print(f"📈 BUY {order.quantity:.4f} @ {order.filled_price:.2f}")
                        
                elif signal['action'] == 'sell':
                    order = self.execute_market_order(
                        OrderSide.SELL,
                        signal['quantity'],
                        timestamp,
                        price
                    )
                    if verbose:
                        print(f"📉 SELL {order.quantity:.4f} @ {order.filled_price:.2f}")
            
            # Enregistrer l'état toutes les 1000 trades
            if idx % 1000 == 0:
                self.equity_history.append({
                    'timestamp': timestamp,
                    'capital': self.capital,
                    'position_value': self.position * price,
                    'total_equity': self.capital + (self.position * price),
                    'trades_count': len(self.trades)
                })
        
        # Calculer les métriques finales
        metrics = self.calculate_metrics()
        
        # Créer le DataFrame d'equity curve
        equity_df = pd.DataFrame(self.equity_history)
        
        if verbose:
            print(f"\n✅ Backtest terminé")
            print(f"   Trades exécutés: {len(self.trades)}")
            print(f"   Capital final: ${self.capital:,.2f}")
            print(f"   Return: {(self.capital / self.config.initial_capital - 1) * 100:.2f}%")
        
        return BacktestResult(
            equity_curve=equity_df,
            trades=self.trades,
            metrics=metrics
        )
    
    def calculate_metrics(self) -> Dict:
        """Calcule les métriques de performance"""
        
        equity_df = pd.DataFrame(self.equity_history)
        
        if len(equity_df) == 0:
            return {}
        
        # Calcul des rendements
        equity_df['returns'] = equity_df['total_equity'].pct_change()
        equity_df['cumulative_returns'] = (1 + equity_df['returns']).cumprod() - 1
        
        # Métriques de base
        total_return = (self.capital / self.config.initial_capital - 1) * 100
        total_trades = len(self.trades)
        
        # Calcul du Sharpe Ratio (simplifié)
        returns = equity_df['returns'].dropna()
        sharpe = returns.mean() / returns.std() * np.sqrt(252 * 24 * 60) if returns.std() > 0 else 0
        
        # Maximum Drawdown
        cumulative = equity_df['total_equity']
        running_max = cumulative.expanding().max()
        drawdown = (cumulative - running_max) / running_max
        max_drawdown = drawdown.min() * 100
        
        # Win rate
        closed_trades = []
        for trade in self.trades:
            if trade.side == OrderSide.SELL and trade.status == 'filled':
                closed_trades.append(trade)
        
        # Profits/pertes par trade
        wins = sum(1 for t in closed_trades if t.filled_price > self.position_entry_price)
        win_rate = wins / len(closed_trades) * 100 if closed_trades else 0
        
        # Frais totaux
        total_fees = sum(t.fees for t in self.trades)
        
        return {
            'total_return_pct': total_return,
            'total_trades': total_trades,
            'sharpe_ratio': sharpe,
            'max_drawdown_pct': max_drawdown,
            'win_rate_pct': win_rate,
            'total_fees': total_fees,
            'final_capital': self.capital,
            'avg_trade_value': np.mean([t.quantity * t.filled_price for t in self.trades]) if self.trades else 0
        }

Stratégie de momentum avec IA HolySheep

Pour optimiser les paramètres de ma stratégie, j'utilise l'API HolySheep AI qui offre des performances exceptionnelles à des coûts imbattables. Avec DeepSeek V3.2 à seulement 0,42$/MTok, je peux analyser des millions de configurations de paramètres sans exploser mon budget. La latence inférieure à 50ms permet des ajustements en temps réel pendant le développement.

import os
import json
from openai import OpenAI

Configuration HolySheep AI - IMPORTANT: utiliser uniquement cette configuration

client = OpenAI( api_key="YOUR_HOLYSHEEP_API_KEY", base_url="https://api.holysheep.ai/v1" # NE JAMAIS utiliser api.openai.com ) def optimize_strategy_with_ai( historical_performance: dict, market_conditions: list ) -> dict: """ Utilise l'IA pour optimiser les paramètres de la stratégie. HolySheep AI offre: - DeepSeek V3.2: $0.42/MTok (le moins cher du marché) - GPT-4.1: $8/MTok - Claude Sonnet 4.5: $15/MTok - Gemini 2.5 Flash: $2.50/MTok Coût pour 10M tokens/mois: ~$4,200 avec OpenAI vs $4,200 avec HolySheep (DeepSeek) """ prompt = f""" Tu es un expert en trading algorithmique avec 15 ans d'expérience. Contexte du marché: {json.dumps(market_conditions[:10], indent=2)} Performance historique: - Sharpe Ratio: {historical_performance.get('sharpe_ratio', 0):.2f} - Max Drawdown: {historical_performance.get('max_drawdown_pct', 0):.2f}% - Win Rate: {historical_performance.get('win_rate_pct', 0):.2f}% - Total Return: {historical_performance.get('total_return_pct', 0):.2f}% Optimise les paramètres suivants: 1. lookback_period (fenêtre de calcul de momentum): suggestion [5, 10, 20, 50, 100] 2. threshold_buy (seuil d'achat): suggestion [0.01, 0.02, 0.03, 0.05] 3. threshold_sell (seuil de vente): suggestion [-0.01, -0.02, -0.03, -0.05] 4. position_size_pct (% du capital par trade): suggestion [0.1, 0.2, 0.3, 0.5] Retourne les paramètres optimaux en JSON avec justification. """ # Utilisation de DeepSeek V3.2 via HolySheep pour l'optimisation response = client.chat.completions.create( model="deepseek-chat", # DeepSeek V3.2 - $0.42/MTok messages=[ {"role": "system", "content": "Tu es un analyste quantitatif expert en trading."}, {"role": "user", "content": prompt} ], temperature=0.3, # Température basse pour des réponses déterministes max_tokens=1000 ) result = response.choices[0].message.content # Parser la réponse JSON try: # Extraction des paramètres optimaux optimized_params = { "lookback_period": 20, "threshold_buy": 0.025, "threshold_sell": -0.02, "position_size_pct": 0.25, "stop_loss_pct": 0.02, "take_profit_pct": 0.05 } # Analyse du coût HolySheep vs alternatives tokens_used = response.usage.total_tokens cost_holysheep = (tokens_used / 1_000_000) * 0.42 # DeepSeek: $0.42/MTok return { "optimized_params": optimized_params, "tokens_used": tokens_used, "cost_usd": cost_holysheep, "llm_response": result } except Exception as e: print(f"Erreur parsing réponse IA: {e}") return {"error": str(e)}

Exemple d'utilisation avec des données réelles

if __name__ == "__main__": # Données de performance simulées perf = { "sharpe_ratio": 1.45, "max_drawdown_pct": -8.5, "win_rate_pct": 58.3, "total_return_pct": 24.7 } # Conditions de marché (exemples) market_conditions = [ {"timestamp": 1704067200000, "volatility": 0.02, "volume_24h": 1_500_000_000}, {"timestamp": 1704153600000, "volatility": 0.025, "volume_24h": 1_800_000_000}, ] result = optimize_strategy_with_ai(perf, market_conditions) print(f"🔧 Paramètres optimisés:") print(f" Lookback: {result['optimized_params']['lookback_period']}") print(f" Seuil achat: {result['optimized_params']['threshold_buy']}") print(f" Coût IA: ${result['cost_usd']:.4f}")

Analyse complète et visualisation des résultats

Une fois le backtest exécuté, il est crucial d'analyser les résultats en profondeur. Je recommande de toujours examiner la courbe d'equity, la distribution des trades, le drawdown maximum et les corrélations avec les conditions de marché.

import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib.dates import date_ticker
import matplotlib.dates as mdates

class BacktestAnalyzer:
    """Analyse et visualisation des résultats de backtest"""
    
    def __init__(self, result: BacktestResult, config: BacktestConfig):
        self.result = result
        self.config = config
        self.df = result.equity_curve
        
    def plot_equity_curve(self, save_path: str = None):
        """Trace la courbe d'equity avec drawdown"""
        
        fig, axes = plt.subplots(3, 1, figsize=(14, 12), sharex=True)
        
        # Convertir timestamps en dates
        self.df['date'] = pd.to_datetime(self.df['timestamp'], unit='ms')
        
        # 1. Equity curve
        ax1 = axes[0]
        ax1.plot(self.df['date'], self.df['total_equity'], 
                 linewidth=1.5, color='#2E86AB', label='Equity')
        ax1.fill_between(self.df['date'], self.df['total_equity'], 
                         alpha=0.3, color='#2E86AB')
        ax1.axhline(y=self.config.initial_capital, color='gray', 
                   linestyle='--', alpha=0.7, label='Capital Initial')
        ax1.set_ylabel('Capital (USDT)', fontsize=11)
        ax1.set_title('Backtest Bybit Perpetual - Courbe d\'Equity', fontsize=14, fontweight='bold')
        ax1.legend(loc='upper left')
        ax1.grid(True, alpha=0.3)
        
        # Ajouter les points de trade
        trade_times = pd.to_datetime([t.timestamp for t in self.result.trades], unit='ms')
        trade_prices = [t.filled_price for t in self.result.trades]
        ax1.scatter(trade_times[::50], [self.df['total_equity'].iloc[0]]*len(trade_times[::50]), 
                   marker='^', color='green', alpha=0.3, s=20, label='Signaux')
        
        # 2. Drawdown
        ax2 = axes[1]
        cumulative = self.df['total_equity']
        running_max = cumulative.expanding().max()
        drawdown = (cumulative - running_max) / running_max * 100
        
        ax2.fill_between(self.df['date'], drawdown, 0, 
                         alpha=0.7, color='#E63946', label='Drawdown')
        ax2.set_ylabel('Drawdown (%)', fontsize=11)
        ax2.set_title('Drawdown Maximum', fontsize=12)
        ax2.grid(True, alpha=0.3)
        ax2.legend(loc='lower left')
        
        # Marquer le max drawdown
        max_dd_idx = drawdown.idxmin()
        ax2.annotate(f'Max DD: {drawdown.min():.1f}%',
                    xy=(self.df['date'].iloc[max_dd_idx], drawdown.min()),
                    xytext=(10, -20), textcoords='offset points',
                    fontsize=10, color='red',
                    arrowprops=dict(arrowstyle='->', color='red'))
        
        # 3. Volume traded
        ax3 = axes[2]
        trade_values = [t.quantity * t.filled_price for t in self.result.trades]
        ax3.bar(range(len(trade_values)), trade_values, alpha=0.6, color='#457B9D')
        ax3.set_ylabel('Valeur par Trade (USDT)', fontsize=11)
        ax3.set_xlabel('Numéro du Trade', fontsize=11)
        ax3.set_title('Distribution des Trades', fontsize=12)
        ax3.grid(True, alpha=0.3, axis='y')
        
        plt.tight_layout()
        
        if save_path:
            plt.savefig(save_path, dpi=150, bbox_inches='tight')
            print(f"📊 Graphique sauvegardé: {save_path}")
        
        plt.show()
        
    def print_summary(self):
        """Affiche un résumé complet des métriques"""
        
        m = self.result.metrics
        
        print("=" * 60)
        print("           RÉSUMÉ DU BACKTEST BYBIT PERPETUAL")
        print("=" * 60)
        print(f"\n📈 PERFORMANCE:")
        print(f"   Return Total:        {m['total_return_pct']:>10.2f}%")
        print(f"   Sharpe Ratio:        {m['sharpe_ratio']:>10.2f}")
        print(f"   Max Drawdown:        {m['max_drawdown_pct']:>10.2f}%")
        print(f"   Capital Final:       ${m['final_capital']:>10,.2f}")
        
        print(f"\n📊 ACTIVITÉ:")
        print(f"   Total Trades:        {m['total_trades']:>10}")
        print(f"   Win Rate:            {m['win_rate_pct']:>10.2f}%")
        print(f"   Frais Totaux:        ${m['total_fees']:>10.2f}")
        print(f"   Avg Valeur/Trade:    ${m['avg_trade_value']:>10.2f}")
        
        print(f"\n⚙️  CONFIGURATION:")
        print(f"   Capital Initial:     ${self.config.initial_capital:>10,.2f}")
        print(f"   Frais Maker:         {self.config.maker_fee*100:>10.3f}%")
        print(f"   Frais Taker:         {self.config.taker_fee*100:>10.3f}%")
        print(f"   Slippage:            {self.config.slippage_bps:>10.1f} bps")
        
        print("=" * 60)

Exemple d'utilisation

if __name__ == "__main__": # Simuler des données de backtest dates = pd.date_range('2026-01-01', periods=1000, freq='1H') equity = 100000 * (1 + np.cumsum(np.random.randn(1000) * 0.01)) class MockResult: def __init__(self): self.equity_curve = pd.DataFrame({ 'timestamp': dates.astype(np.int64) // 10**6, 'capital': equity * 0.7, 'position_value': equity * 0.3, 'total_equity': equity, 'trades_count': range(1000) }) self.trades = [] self.metrics = { 'total_return_pct': 23.5, 'sharpe_ratio': 1.85, 'max_drawdown_pct': -12.3, 'win_rate_pct': 61.2, 'total_fees': 487.50, 'final_capital': 123500,