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 :
- Données tick-by-tick : chaque transaction capturée avec horodatage précis
- Orderbook snapshots : état complet du carnet à intervalles réguliers
- L2 orderbook deltas : changements incrémentaux pour reconstruire l'historique
- 60+ exchanges supportées dont Binance, Coinbase, Kraken
- Latence <100ms pour les données en temps réel
- Historique jusqu'à 2017 pour les principales paires
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 :
- 100 000 messages/mois
- Accès aux données démo
- 1 jour d'historique
- 3 connexions simultanées
É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ère | Tardis.dev | Binance API Direct | CCXT | Kaiko |
|---|---|---|---|---|
| Historique L2 | ✅ Oui (2017+) | ❌ Non | ❌ Non | ✅ Oui |
| Facilité d'usage | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| Prix / mois | À partir de $49 | Gratuit (limité) | Gratuit | À partir de $500 |
| Latence | <100ms | <50ms | Variable | <200ms |
| Exchanges | 60+ | 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 :
- Market making : Placement d'ordres limites des deux côtés
- Iceberg orders : Détection d'ordres volumineux cachés
- Arbitrage : Identification de différences de prix entre exchanges
- Impact de marché : Mesurer l'effet de gros ordres sur le prix
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 :
- Évaluer la liquidité avant d'exécuter de gros ordres
- Calculer le coût d'exécution anticipé (implementation shortfall)
- Surveiller la santé du marché en temps réel
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
| Plan | Prix/mois | Messages | Historique | Connexions |
|---|---|---|---|---|
| Free | $0 | 100K | 1 jour | 3 |
| Starter | $49 | 5M | 30 jours | 10 |
| Pro | $199 | 25M | 1 an | 25 |
| Enterprise | Sur devis | Illimité | 7+ ans | Illimité |
Calculateur de ROI approximatif :
- Un projet de backtesting typique utilise ~500K messages/mois
- Coût : $49/mois (Starter)
- Économie vs Kaiko : ~$400/mois (90% d'économie)
- Temps économisé : ~10h/mois de développement vs collecte manuelle
Pour qui / Pour qui ce n'est pas fait
✅ Idéal pour :
- Les chercheurs et data scientists en crypto
- Les développeurs de stratégies de trading algo
- Les desks de trading quantitatif avec budget limité
- Les startups fintech нуждающиеся en données de marché
- Les étudiants et académiques explorant la microstructure
❌ Moins adapté pour :
- Les entreprises nécessitant des données haute fréquence (>100GB/jour)
- Ceux qui ont besoin uniquement du temps réel (Binance API suffit)
- Les projets avec budget >$2000/mois (d'autres solutions peuvent convenir)
- Les cas d'usage non-crypto (Tardis est spécialisé exchange crypto)
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 :
- Commencez avec le plan gratuit pour tester et prototyper
- Utilisez le filtrage pour réduire le volume de données non nécessaires
- Mettez en cache les données localement pour les analyses répétées
- Implémentez toujours une gestion d'erreurs robuste avec retry et backoff
- Pour les besoins en IA générative ou NLP sur données crypto, pensez aussi à HolySheep AI qui offre des APIs IA avec une latence <50ms et des prix jusqu'à 85% inférieurs aux providers traditionnels.
Ressources Complémentaires
- Documentation officielle Tardis.dev
- SDK Python officiel sur GitHub
- Playground interactif — testez les requêtes directement dans le navigateur
- HolySheep AI — APIs IA abordables pour vos projets data