Introduction

Le backtesting de stratégies de trading sur les contrats perpetuels OKX nécessite des données tick fiables et une infrastructure capable de les ingérer efficacement. Tardis Machine est devenu une référence pour les traders quantitatifs qui nécessitent un accès historique de haute qualité aux données de marché des exchanges de cryptomonnaies. Dans ce guide complet, nous explorerons comment configurer, extraire et utiliser les données tick OKX via l'API Tardis pour construire un système de backtesting robuste. ---

Prérequis

Avant de commencer, vous aurez besoin de : - Un compte Tardis Machine avec un abonnement actif - Python 3.9+ installé - La bibliothèque tardis installée (pip install tardis-client) - Connaissances de base en trading algorithmique ---

Installation et Configuration

# Installation des dépendances
pip install tardis-client pandas numpy

Configuration de l'API Tardis

import asyncio from tardis_client import TardisClient, Channel

Connexion à l'API

client = TardisClient(api_key="YOUR_TARDIS_API_KEY")

Vérification de la connexion

async def verify_connection(): exchanges = await client.exchanges() print(f"Exchanges disponibles : {[e.name for e in exchanges]}") asyncio.run(verify_connection())
---

Extraction des Données Tick OKX Perpetual

from datetime import datetime, timedelta

Configuration pour OKX perpetual (USDT-M)

exchange_name = "okx" channel_name = "trade" # Données de transaction tick par tick symbol = "BTC-USDT-SWAP"

Période de test : 30 derniers jours

start_date = datetime(2026, 4, 1) end_date = datetime(2026, 4, 30)

Récupération des données

async def fetch_ohlcv_data(): messages = client.replay( exchange=exchange_name, channels=[Channel(name=channel_name, symbols=[symbol])], from_date=start_date, to_date=end_date ) trades = [] async for message in messages: if message.type == "trade": trades.append({ 'timestamp': message.timestamp, 'side': message.side, 'price': float(message.price), 'size': float(message.size), 'symbol': message.symbol }) return trades trades = asyncio.run(fetch_ohlcv_data()) print(f"Nombre de trades récupérés : {len(trades)}")
---

Construction du Moteur de Backtesting

import pandas as pd
import numpy as np

class BacktestEngine:
    def __init__(self, initial_balance=10000):
        self.initial_balance = initial_balance
        self.balance = initial_balance
        self.position = 0
        self.trades = []
        self.equity_curve = []
        
    def execute_trade(self, timestamp, price, side, size):
        """Exécution d'un trade"""
        if side == "buy" and self.balance >= price * size:
            self.balance -= price * size
            self.position += size
        elif side == "sell" and self.position >= size:
            self.balance += price * size
            self.position -= size
            
        self.trades.append({
            'timestamp': timestamp,
            'price': price,
            'side': side,
            'size': size,
            'balance': self.balance,
            'position': self.position
        })
        
    def calculate_returns(self):
        """Calcul des métriques de performance"""
        df = pd.DataFrame(self.trades)
        
        # Rendement total
        total_return = (self.balance - self.initial_balance) / self.initial_balance
        
        # Sharpe Ratio (simplifié)
        if len(df) > 1:
            returns = df['balance'].pct_change().dropna()
            sharpe = np.sqrt(252) * returns.mean() / returns.std()
        else:
            sharpe = 0
            
        # Drawdown maximum
        df['cummax'] = df['balance'].cummax()
        df['drawdown'] = (df['balance'] - df['cummax']) / df['cummax']
        max_drawdown = df['drawdown'].min()
        
        return {
            'total_return': total_return,
            'sharpe_ratio': sharpe,
            'max_drawdown': max_drawdown,
            'total_trades': len(df),
            'final_balance': self.balance
        }

Exécution du backtest

engine = BacktestEngine(initial_balance=10000)

Stratégie simple : achat sur support, vente sur résistance

def simple_strategy(trades_df, lookback=20, tp_pct=0.02, sl_pct=0.01): engine = BacktestEngine(initial_balance=10000) for i, trade in trades_df.iterrows(): # Logique de stratégie (exemple simplifié) if i > lookback: engine.execute_trade( trade['timestamp'], trade['price'], 'buy', 0.01 # Taille fixe ) return engine result = simple_strategy(pd.DataFrame(trades)) metrics = result.calculate_returns() print("=== Résultats du Backtest ===") print(f"Rendement total : {metrics['total_return']:.2%}") print(f"Sharpe Ratio : {metrics['sharpe_ratio']:.2f}") print(f"Drawdown maximum : {metrics['max_drawdown']:.2%}") print(f"Nombre de trades : {metrics['total_trades']}")
---

Optimisation et Validation

from itertools import product

def optimize_strategy(trades_df, param_grid):
    """Optimisation des paramètres de stratégie"""
    best_params = None
    best_sharpe = -np.inf
    results = []
    
    # Grid search
    for lookback, tp, sl in product(
        param_grid['lookback'],
        param_grid['take_profit'],
        param_grid['stop_loss']
    ):
        engine = BacktestEngine(initial_balance=10000)
        
        # Test avec ces paramètres
        for i, trade in trades_df.iterrows():
            if i > lookback:
                engine.execute_trade(
                    trade['timestamp'],
                    trade['price'],
                    'buy',
                    0.01
                )
                
        metrics = engine.calculate_returns()
        
        if metrics['sharpe_ratio'] > best_sharpe:
            best_sharpe = metrics['sharpe_ratio']
            best_params = {'lookback': lookback, 'tp': tp, 'sl': sl}
            
        results.append({
            'params': {'lookback': lookback, 'tp': tp, 'sl': sl},
            'sharpe': metrics['sharpe_ratio'],
            'return': metrics['total_return']
        })
    
    return best_params, results

Définition de la grille de paramètres

param_grid = { 'lookback': [10, 20, 50, 100], 'take_profit': [0.01, 0.02, 0.03, 0.05], 'stop_loss': [0.005, 0.01, 0.015, 0.02] } best_params, all_results = optimize_strategy(pd.DataFrame(trades), param_grid) print(f"Meilleurs paramètres : {best_params}") print(f"Meilleur Sharpe Ratio : {max(r['sharpe'] for r in all_results):.2f}")
---

Considérations de Latence et Performance

Pour un backtesting réaliste, gardez à l'esprit : | Aspect | Valeur typique | Impact | |--------|----------------|--------| | Latence API Tardis | 50-200ms | Affecte la réplication des ordres | | Fréquence des données | Tick par tick | Nécessite un historique complet | | Frais de transaction | 0.05-0.10% | Impact significatif sur la stratégie | | Slippage | 0.01-0.05% | Variable selon la liquidité | ---

Erreurs Courantes et Solutions

Erreur 1 : Limite de requêtes dépassée (429 Too Many Requests)

# Solution : Implémentation du rate limiting
import time
from functools import wraps

def rate_limit(calls_per_second=10):
    """Décorateur pour limiter les appels API"""
    min_interval = 1.0 / calls_per_second
    last_called = [0.0]
    
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            elapsed = time.time() - last_called[0]
            if elapsed < min_interval:
                time.sleep(min_interval - elapsed)
            last_called[0] = time.time()
            return func(*args, **kwargs)
        return wrapper
    return decorator

Utilisation

@rate_limit(calls_per_second=5) async def fetch_data_safe(): messages = client.replay(...) return messages

Erreur 2 : Symboles non reconnus

# Solution : Vérification préalable des symboles disponibles
async def get_available_symbols():
    """Liste tous les symboles OKX disponibles"""
    symbols = await client.symbols(exchange="okx")
    
    # Filtrer pour les perpetual
    perpetual_symbols = [
        s for s in symbols 
        if "-SWAP" in s.name and "USDT" in s.name
    ]
    
    print("Symboles perpetual USDT disponibles :")
    for s in perpetual_symbols[:10]:
        print(f"  - {s.name}")
    
    return perpetual_symbols

Symbols valides pour OKX perpetual :

BTC-USDT-SWAP, ETH-USDT-SWAP, SOL-USDT-SWAP, etc.

Erreur 3 : Données incomplètes ou lacunaires

# Solution : Vérification et remplissage des données
def validate_and_fill_data(trades_df):
    """Valide et complète les données de marché"""
    
    # Conversion des timestamps
    trades_df['timestamp'] = pd.to_datetime(trades_df['timestamp'])
    trades_df = trades_df.sort_values('timestamp')
    
    # Détection des lacunes
    trades_df['time_diff'] = trades_df['timestamp'].diff()
    gaps = trades_df[trades_df['time_diff'] > pd.Timedelta(minutes=5)]
    
    if len(gaps) > 0:
        print(f"Attention : {len(gaps)} lacunes détectées")
        print(gaps[['timestamp', 'time_diff']].head())
        
    # Interpolation des prix manquants (si nécessaire)
    trades_df['price'] = trades_df['price'].interpolate(method='linear')
    
    return trades_df

Appliquer la validation

trades_df = validate_and_fill_data(pd.DataFrame(trades))
---

Conclusion et Prochaines Étapes

L'utilisation de Tardis API pour le backtesting de stratégies OKX perpetual offre une base solide pour développer et valider vos systèmes de trading. Les données tick par tick permettent une précision maximale dans la simulation des conditions réelles de marché.

Points clés à retenir :

1. **Qualité des données** : Tardis offre des données historiques vérifiées et complète 2. **Flexibilité** : L'API permet une personnalisation complète des stratégies 3. **Optimisation** : Le grid search permet de trouver les paramètres optimaux 4. **Validation** : Toujours tester sur plusieurs périodes pour éviter le surapprentissage Pour aller plus loin, explorez l'ajout de données de order book pour une simulation plus réaliste des slips et de la liquidité.