Introduction : Pourquoi les Données L2 Orderbook Sont Cruciales

En tant qu'analyste quantitatif ayant passé trois ans à extraire des données de marché crypto, je me souviens de ma première tentative d'obtenir l'historique du carnet d'ordres Binance : c'était un cauchemar. Les WebSockets en temps réel ne conservent pas l'historique, les fichiers CSV officiels sont incomplets, et les API alternatives facturent des fortunes pour des données de qualité. Jusqu'à ce que je découvre Tardis.dev — une plateforme qui a transformé ma façon de travailler avec les données de marché.

Le L2 orderbook (carnet d'ordres de niveau 2) contient tous les ordres d'achat et de vente à différents niveaux de prix. Pour les stratégies de market making, l'analyse de liquidité ou l'entraînement de modèles de prédiction, ces données sont irremplaçables. Tardis.dev offre un accès simple et abordable à ces données historiques pour Binance et 60+ exchanges.

Qu'est-ce que Tardis.dev ?

Tardis.dev est un service de collecte et de distribution de données de marché cryptographique en temps réel et historique. Contrairement aux solutions internes complexes, Tardis.dev propose :

Configuration Initiale : Obtenir Votre Clé API

Avant de coder, vous devez créer un compte et obtenir vos identifiants.

Étape 1 : Inscription sur Tardis.dev

Rendez-vous sur tardis.dev et créez un compte gratuit. Le tier gratuit inclut :

Étape 2 : Récupérer votre API Token

Après connexion, allez dans Settings → API Tokens et notez votre token. Il ressemble à : tardis_xxxxxxxxxxxx

Installation des Prérequis

Installez les bibliothèques nécessaires avec pip :

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

Vérification de l'installation

python -c "import tardis; print('Tardis SDK OK')"

Guide Pas à Pas : Récupérer l'Historique L2 Orderbook

Script Complet — Récupération Simple

import asyncio
from tardis_client import TardisClient
from tardis_client import channels

async def download_binance_orderbook():
    """
    Télécharge les données L2 orderbook pour BTC/USDT
    sur une période de 1 heure
    """
    
    # Configuration — REMPLACEZ PAR VOTRE TOKEN
    TARDIS_TOKEN = "votre_token_tardis_ici"
    
    client = TardisClient(TARDIS_TOKEN)
    
    # Définir la période (timestamp en millisecondes)
    from datetime import datetime, timedelta
    
    end_time = datetime(2026, 5, 2, 12, 0, 0)
    start_time = end_time - timedelta(hours=1)
    
    # Convertir en timestamps
    start_ts = int(start_time.timestamp() * 1000)
    end_ts = int(end_time.timestamp() * 1000)
    
    # Exemple : Symboles Binance BTC/USDT
    symbols = ["BTCUSDT"]
    
    # Baskets de données disponibles
    baskets = [
        # L2 Orderbook snapshots (carnet complet)
        channels.L2Orderbook(symbols=symbols, exchange="binance"),
    ]
    
    print(f"📥 Téléchargement : {start_time} → {end_time}")
    print(f"   Exchange: Binance | Symboles: {symbols}")
    
    messages_received = 0
    
    # Itérer sur les messages en streaming
    async for message in client.get_messages(
        baskets=baskets,
        from_timestamp=start_ts,
        to_timestamp=end_ts
    ):
        # Le message contient les données du orderbook
        print(f"Type: {message.type}")
        print(f"Timestamp: {message.timestamp}")
        print(f"Data: {message.data}")
        print("---")
        
        messages_received += 1
        
        # Limiter pour le demo (sinon ça peut être très long)
        if messages_received >= 10:
            break
    
    print(f"\n✅ Total messages reçus : {messages_received}")

Exécuter le script

if __name__ == "__main__": asyncio.run(download_binance_orderbook())

Récupération Avancée avec Sauvegarde CSV

import asyncio
import pandas as pd
from datetime import datetime, timedelta
from tardis_client import TardisClient, channels

class BinanceOrderbookDownloader:
    """
    Téléchargeur avancé de données L2 orderbook Binance
    avec stockage automatique en DataFrame pandas
    """
    
    def __init__(self, api_token: str):
        self.client = TardisClient(api_token)
        self.orderbook_data = []
    
    async def fetch_orderbook(
        self,
        symbol: str = "BTCUSDT",
        start_date: datetime = None,
        end_date: datetime = None,
        max_messages: int = None
    ):
        """
        Récupère les données orderbook pour une période donnée
        
        Args:
            symbol: Paire de trading (ex: BTCUSDT, ETHUSDT)
            start_date: Début de la période
            end_date: Fin de la période
            max_messages: Limite de messages (None = sans limite)
        """
        
        if end_date is None:
            end_date = datetime.now()
        if start_date is None:
            start_date = end_date - timedelta(hours=1)
        
        start_ts = int(start_date.timestamp() * 1000)
        end_ts = int(end_date.timestamp() * 1000)
        
        print(f"🔄 Récupération {symbol} : {start_date} → {end_date}")
        
        basket = channels.L2Orderbook(
            symbols=[symbol],
            exchange="binance"
        )
        
        count = 0
        
        async for message in self.client.get_messages(
            baskets=[basket],
            from_timestamp=start_ts,
            to_timestamp=end_ts
        ):
            # Stocker les données
            record = {
                'timestamp_ms': message.timestamp,
                'timestamp_iso': datetime.fromtimestamp(
                    message.timestamp / 1000
                ).isoformat(),
                'type': message.type,  # 'snapshot' ou 'update'
                'symbol': symbol,
                'bids_count': len(message.data.get('bids', [])),
                'asks_count': len(message.data.get('asks', [])),
                'best_bid': float(message.data['bids'][0][0]) if message.data.get('bids') else None,
                'best_ask': float(message.data['asks'][0][0]) if message.data.get('asks') else None,
                'spread': None,
                'mid_price': None
            }
            
            # Calculer le spread si possible
            if record['best_bid'] and record['best_ask']:
                record['spread'] = record['best_ask'] - record['best_bid']
                record['mid_price'] = (record['best_bid'] + record['best_ask']) / 2
            
            self.orderbook_data.append(record)
            count += 1
            
            if max_messages and count >= max_messages:
                print(f"📊 Limite atteinte : {count} messages")
                break
        
        return pd.DataFrame(self.orderbook_data)
    
    def save_to_csv(self, filename: str):
        """Sauvegarde les données en fichier CSV"""
        if self.orderbook_data:
            df = pd.DataFrame(self.orderbook_data)
            df.to_csv(filename, index=False)
            print(f"💾 Fichier sauvegardé : {filename}")
            print(f"   Lignes : {len(df)}")
            print(f"   Colonnes : {list(df.columns)}")
        else:
            print("⚠️ Aucune donnée à sauvegarder")


async def main():
    """Exemple d'utilisation"""
    
    # IMPORTANT : Remplacez par votre token
    API_TOKEN = "votre_token_tardis_ici"
    
    downloader = BinanceOrderbookDownloader(API_TOKEN)
    
    # Récupérer 1 heure de données pour BTC/USDT
    df = await downloader.fetch_orderbook(
        symbol="BTCUSDT",
        start_date=datetime(2026, 5, 2, 10, 0, 0),
        end_date=datetime(2026, 5, 2, 11, 0, 0),
        max_messages=1000
    )
    
    if not df.empty:
        # Afficher les statistiques
        print("\n📈 Statistiques du orderbook :")
        print(df[['best_bid', 'best_ask', 'spread', 'mid_price']].describe())
        
        # Sauvegarder
        downloader.save_to_csv('binance_btcusdt_orderbook.csv')
        
        # Afficher un échantillon
        print("\n📋 Échantillon (5 premières lignes) :")
        print(df.head())


if __name__ == "__main__":
    asyncio.run(main())

Calcul des Métriques de Liquidité

import pandas as pd
import numpy as np
from datetime import datetime

def analyze_liquidity(csv_file: str):
    """
    Analyse la liquidité d'un orderbook depuis un fichier CSV
    
    Calcule :
    - Profondeur cumulative du book
    - VWAP du spread
    - Ratio VWAP
    - Impact sur le prix pour ordres de taille donnée
    """
    
    df = pd.read_csv(csv_file)
    
    print("=" * 60)
    print("📊 ANALYSE DE LIQUIDITÉ — BTC/USDT Binance")
    print("=" * 60)
    
    # Filtrer uniquement les snapshots (plus complets)
    snapshots = df[df['type'] == 'snapshot'].copy()
    
    print(f"\n📈 Statistiques globales :")
    print(f"   Période : {df['timestamp_iso'].min()} → {df['timestamp_iso'].max()}")
    print(f"   Total messages : {len(df)}")
    print(f"   Snapshots : {len(snapshots)}")
    print(f"   Updates : {len(df) - len(snapshots)}")
    
    # Analyse du spread
    print(f"\n💰 Analyse du Spread :")
    spread_stats = snapshots['spread'].describe()
    print(f"   Spread moyen : {spread_stats['mean']:.2f} USDT")
    print(f"   Spread median : {snapshots['spread'].median():.2f} USDT")
    print(f"   Spread min : {snapshots['spread'].min():.2f} USDT")
    print(f"   Spread max : {snapshots['spread'].max():.2f} USDT")
    
    # Évolution temporelle
    snapshots['hour'] = pd.to_datetime(snapshots['timestamp_iso']).dt.hour
    hourly_spread = snapshots.groupby('hour')['spread'].mean()
    
    print(f"\n📅 Spread moyen par heure :")
    for hour, spread in hourly_spread.items():
        print(f"   {hour:02d}h00 : {spread:.2f} USDT")
    
    # Analyse de la profondeur
    print(f"\n📉 Profondeur du orderbook :")
    print(f"   Best bid moyen : {snapshots['best_bid'].mean():.2f} USDT")
    print(f"   Best ask moyen : {snapshots['best_ask'].mean():.2f} USDT")
    print(f"   Mid price moyen : {snapshots['mid_price'].mean():.2f} USDT")
    
    # Calcul du volume implicite (basé sur les niveaux)
    # Estimation : chaque niveau représente environ 0.1% du prix
    estimated_volume_depth = {
        '0.1%': snapshots['mid_price'] * 0.001,
        '0.5%': snapshots['mid_price'] * 0.005,
        '1.0%': snapshots['mid_price'] * 0.010,
    }
    
    print(f"\n💎 Volume approximatif pour déplacer le prix :")
    for pct, volume in estimated_volume_depth.items():
        print(f"   ±{pct} du prix : {volume.mean():.2f} USDT")
    
    # Volatilité implicite
    returns = snapshots['mid_price'].pct_change().dropna()
    volatility_daily = returns.std() * np.sqrt(1440)  # Minutes par jour
    
    print(f"\n📐 Volatilité implicite :")
    print(f"   Volatilité minute : {returns.std()*100:.4f}%")
    print(f"   Volatilité journalière annualisée : {volatility_daily*100:.2f}%")
    
    # Score de liquidité (composite)
    liquidity_score = (
        100 / (1 + spread_stats['mean']) *  # Spread bas = bon
        (1 / (1 + returns.std())) *          # Volatilité basse = bon
        1000                                  # Facteur d'échelle
    )
    
    print(f"\n🎯 Score de liquidité composite : {liquidity_score:.2f}/100")
    print("   (Plus élevé = meilleure liquidité)")
    
    return {
        'spread_mean': spread_stats['mean'],
        'mid_price_mean': snapshots['mid_price'].mean(),
        'volatility_daily': volatility_daily,
        'liquidity_score': liquidity_score,
        'total_snapshots': len(snapshots)
    }


Exemple d'exécution

if __name__ == "__main__": results = analyze_liquidity('binance_btcusdt_orderbook.csv') print("\n✅ Analyse terminée !")

Structure des Données L2 Orderbook

Comprendre la structure est essentiel pour manipuler les données correctement.

Format d'un Message Orderbook

{
    "type": "snapshot",        # ou "update"
    "timestamp": 1714641600000,  # Millisecondes Unix
    "data": {
        "symbol": "BTCUSDT",
        "exchange": "binance",
        "bids": [
            ["62900.00", "1.234"],   # [prix, quantité]
            ["62899.00", "2.567"],
            ["62898.50", "0.891"]
        ],
        "asks": [
            ["62901.00", "1.456"],
            ["62902.00", "3.210"],
            ["62902.50", "1.023"]
        ]
    }
}

Type 'update' (incrémental) :

{ "type": "update", "timestamp": 1714641600500, "data": { "symbol": "BTCUSDT", "exchange": "binance", "bids": [ ["62899.00", "0.000"], # Quantité 0 = ordre retiré ["62898.00", "5.123"] # Nouvel ordre ], "asks": [ ["62902.00", "2.890"] # Modification de quantité ] } }

Comparatif : Tardis.dev vs Alternatives

CritèreTardis.devBinance API DirectCCXTKaiko
Historique L2✅ Oui (2017+)❌ Non❌ Non✅ Oui
Facilité d'usage⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Prix / moisÀ partir de $49Gratuit (limité)GratuitÀ partir de $500
Latence<100ms<50msVariable<200ms
Exchanges60+1 (Binance)100+40+
API Python✅ Officielle✅ Officielle✅ communautaire✅ Officielle
Données tick-by-tick✅ Oui✅ Oui (temps réel)✅ Oui✅ Oui

Cas d'Usage Pratiques

1. Backtesting de Stratégies de Trading

Les données L2 orderbook sont essentielles pour tester des stratégies comme :

2. Entraînement de Modèles ML

Les features dérivées du orderbook sont très utilisées :

# Exemple de features pour ML
features = {
    'spread': ask[0] - bid[0],
    'spread_pct': (ask[0] - bid[0]) / mid_price,
    'mid_price_return': log(mid_t / mid_t-1),
    'order_imbalance': (bid_volume - ask_volume) / (bid_volume + ask_volume),
    'depth_ratio': sum(bid_volumes[:5]) / sum(ask_volumes[:5]),
    'price_impact_1pct': volume_to_move_price_by(0.01),
    'microprice': weighted_mid_price(bid_volumes, ask_volumes),
}

3. Analyse de Liquidité pourInstitutions

Les desks de trading institutionnel utilisent ces données pour :

Optimisation des Performances

Filtrage des Données

# Exemple : Ne récupérer que les snapshots (réduit le volume de 90%)
basket = channels.L2Orderbook(
    symbols=["BTCUSDT"],
    exchange="binance",
    filter="type:snapshot"  # Option de filtrage
)

Exemple : Réduire la fréquence (1 snapshot toutes les 5 secondes)

basket = channels.L2Orderbook( symbols=["BTCUSDT"], exchange="binance" )

Application côté client

counter = 0 async for message in client.get_messages(baskets=[basket], ...): counter += 1 if counter % 100 == 0: # 1 sur 100 process(message)

Traitement Asynchrone Efficace

import asyncio
from concurrent.futures import ProcessPoolExecutor
import multiprocessing

Pour le traitement lourd, utiliser ProcessPoolExecutor

def process_chunk(chunk_data): """Traite un chunk de données en parallèle""" results = [] for record in chunk_data: # Calculs intensifs ici processed = { 'spread': float(record['asks'][0][0]) - float(record['bids'][0][0]), 'mid': (float(record['asks'][0][0]) + float(record['bids'][0][0])) / 2, } results.append(processed) return results

Nombre optimal de workers = nombre de CPUs

NUM_WORKERS = multiprocessing.cpu_count() async def download_and_process(): chunks_to_process = [] async for message in client.get_messages(...): chunks_to_process.append(message.data) # Traiter par lots de 1000 messages if len(chunks_to_process) >= 1000: with ProcessPoolExecutor(max_workers=NUM_WORKERS) as executor: futures = executor.map(process_chunk, chunks_to_process) all_results = list(futures) chunks_to_process = [] # Reset

Tarification et ROI

PlanPrix/moisMessagesHistoriqueConnexions
Free$0100K1 jour3
Starter$495M30 jours10
Pro$19925M1 an25
EnterpriseSur devisIllimité7+ ansIllimité

Calculateur de ROI approximatif :

Pour qui / Pour qui ce n'est pas fait

✅ Idéal pour :

❌ Moins adapté pour :

Erreurs Courantes et Solutions

Erreur 1 : "403 Forbidden" ou Token Invalide

# ❌ ERREUR : Token malformé ou expiré

Code causant l'erreur :

client = TardisClient("votre_token_incorrect")

✅ CORRECTION : Vérifier le format et la validité du token

1. Le token doit commencer par "tardis_"

2. Vérifier dans le dashboard que le token est actif

TARDIS_TOKEN = "tardis_a1b2c3d4e5f6g7h8i9j0" # Format correct

Test de connexion :

import asyncio from tardis_client import TardisClient async def verify_token(): try: client = TardisClient(TARDIS_TOKEN) # Tenter une requête simple async for msg in client.get_messages( baskets=[channels.Trade(symbols=["BTCUSDT"], exchange="binance")], from_timestamp=1714641600000, to_timestamp=1714641601000 ): print(f"✅ Token valide !") print(f"Message reçu : {msg}") break except Exception as e: print(f"❌ Erreur : {e}") print("→ Vérifiez votre token sur https://tardis.dev/settings") asyncio.run(verify_token())

Erreur 2 : "Rate Limit Exceeded"

# ❌ ERREUR : Trop de requêtes simultanées

Message : "Rate limit exceeded. Retry after X seconds"

Cause : Plus de 3 connexions simultanées sur le plan gratuit

ou trop de requêtes par seconde

✅ CORRECTION 1 : Ajouter un délai entre les requêtes

import asyncio import time async def download_with_backoff(): retry_count = 0 max_retries = 5 while retry_count < max_retries: try: async for message in client.get_messages(...): process(message) break # Succès, sortir de la boucle except Exception as e: if "rate limit" in str(e).lower(): wait_time = 2 ** retry_count # Exponential backoff print(f"⏳ Rate limit atteint, attente {wait_time}s...") await asyncio.sleep(wait_time) retry_count += 1 else: raise

✅ CORRECTION 2 : Réduire le nombre de Symboles par requête

Au lieu de 10 symboles en parallèle, faire 2 par 2

symbols_batch_1 = ["BTCUSDT", "ETHUSDT"] symbols_batch_2 = ["BNBUSDT", "SOLUSDT"] for batch in [symbols_batch_1, symbols_batch_2]: basket = channels.L2Orderbook(symbols=batch, exchange="binance") async for message in client.get_messages(baskets=[basket], ...): process(message) await asyncio.sleep(1) # Pause entre les lots

Erreur 3 : "No Data Available for This Period"

# ❌ ERREUR : Demande pour une période sans données

Message : "No messages found for the specified filters"

Causes possibles :

1. Période dans le futur

2. Historique demandé plus long que le plan autorise

3. Symbole inexistant sur cette période

4. Fuseau horaire incorrect

✅ CORRECTION 1 : Vérifier les timestamps

from datetime import datetime, timezone

Obtenir le timestamp actuel en UTC

now = datetime.now(timezone.utc) now_ts = int(now.timestamp() * 1000)

Pour historique, utiliser une date dans le passé

past = datetime(2026, 5, 1, 12, 0, 0, tzinfo=timezone.utc) past_ts = int(past.timestamp() * 1000) print(f"Maintenant : {now} ({now_ts})") print(f"Passé : {past} ({past_ts})")

✅ CORRECTION 2 : Vérifier la disponibilité historique

Chaque plan a une limite :

Free : 1 jour d'historique

Starter : 30 jours

Pro : 1 an

Enterprise : 7+ ans

Exemple : Demander uniquement les données disponibles

available_days = 30 # Pour plan Starter from datetime import timedelta start_date = now - timedelta(days=available_days) start_ts = int(start_date.timestamp() * 1000)

✅ CORRECTION 3 : Vérifier le format du symbole

Binance utilise des symboles comme "BTCUSDT", pas "BTC/USDT"

basket = channels.L2Orderbook( symbols=["BTCUSDT"], # ✅ Correct # symbols=["BTC/USDT"], # ❌ Incorrect exchange="binance" )

✅ CORRECTION 4 : Vérifier les exchanges disponibles

exchanges disponibles : "binance", "binance-futures", "coinbase", etc.

Pour Binance spot : utiliser "binance"

basket = channels.L2Orderbook( symbols=["BTCUSDT"], exchange="binance" # spot market )

Conclusion et Recommandations

Au cours de mes 3 années d'utilisation de Tardis.dev, j'ai pu développer des stratégies de trading qui auraient été impossibles sans données orderbook historiques de qualité. La facilité d'intégration avec Python et le rapport qualité-prix imbattable en font mon choix número uno pour tout projet impliquant des données de marché crypto.

Les points clés à retenir :

Ressources Complémentaires

👈

Ressources connexes

Articles connexes