Bienvenue dans ce tutoriel complet sur la manipulation des données de carnet d'ordres (order book) niveau 2 de Binance via l'API Tardis.dev. Si vous êtes trader algorithmique, chercheur en finance quantitative ou développeur de systèmes de trading haute fréquence, ce guide vous permettra de reconstituer l'historique complet des carnets d'ordres avec une précision tick-by-tick.
Introduction : Pourquoi le Order Book L2 est Crucial
Le carnet d'ordres niveau 2 contient TOUS les ordres passés à chaque niveau de prix, contrairement au L1 qui ne montre que le meilleur bid/ask. Avec des données L2, vous pouvez analyser :
- La profondeur du marché et la liquidité visible
- Les patterns de wall orders et leur impact sur le prix
- Les trades d'information privilégiée (frontrunning detection)
- La microstructure du marché et le spread dynamique
- Les liquidations massives et leur propagation
Configuration de l'Environnement
# Installation des dépendances requise
pip install tardis-client aiohttp pandas numpy asyncio aiofiles
Structure de projet recommandée
project/
├── config.py
├── orderbook_replayer.py
├── data_analyzer.py
├── main.py
└── data/
└── book_levels/
# config.py - Configuration centralisée
import os
from dataclasses import dataclass
from typing import Optional
@dataclass
class TardisConfig:
"""Configuration de l'API Tardis.dev"""
api_key: str = os.getenv("TARDIS_API_KEY", "YOUR_TARDIS_KEY")
exchange: str = "binance"
market: str = "btcusdt"
channels: list = None
def __post_init__(self):
self.channels = ["book"] # L2 order book
self.book_levels: int = 1000 # Profondeur maximale
@dataclass
class HolySheepConfig:
"""Configuration HolySheep pour l'analyse IA des données"""
api_key: str = os.getenv("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY")
base_url: str = "https://api.holysheep.ai/v1"
model: str = "gpt-4.1" # $8/MTok - optimal pour analyse financière
max_tokens: int = 4000
def headers(self) -> dict:
return {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
Connexion et Authentification Tardis.dev
# orderbook_replayer.py - Module principal de réplication
import asyncio
import json
import aiohttp
import pandas as pd
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Callable
from dataclasses import dataclass, field
from collections import defaultdict
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@dataclass
class OrderBookLevel:
"""Représente un niveau de prix dans le carnet d'ordres"""
price: float
quantity: float
side: str # 'bid' ou 'ask'
def __repr__(self):
return f"{self.side.upper()}: {self.price} @ {self.quantity}"
@dataclass
class OrderBookSnapshot:
"""Snapshot complet du order book à un instant T"""
timestamp: datetime
exchange: str
symbol: str
bids: Dict[float, float] = field(default_factory=dict) # price -> quantity
asks: Dict[float, float] = field(default_factory=dict)
seq_id: int = 0
@property
def best_bid(self) -> Optional[float]:
if self.bids:
return max(self.bids.keys())
return None
@property
def best_ask(self) -> Optional[float]:
if self.asks:
return min(self.asks.keys())
return None
@property
def mid_price(self) -> Optional[float]:
if self.best_bid and self.best_ask:
return (self.best_bid + self.best_ask) / 2
return None
@property
def spread(self) -> Optional[float]:
if self.best_bid and self.best_ask:
return self.best_ask - self.best_bid
return None
def to_dataframe(self) -> pd.DataFrame:
"""Convertit le carnet en DataFrame pandas"""
rows = []
for price, qty in self.bids.items():
rows.append({"price": price, "quantity": qty, "side": "bid", "depth": 0})
for price, qty in self.asks.items():
rows.append({"price": price, "quantity": qty, "side": "ask", "depth": 0})
return pd.DataFrame(rows)
class BinanceOrderBookReplayer:
"""
Réplicateur tick-by-tick du carnet d'ordres Binance.
Gère la connexion WebSocket à l'API Tardis.dev et la reconstruction
incrémentale du order book L2.
"""
# URL de l'API REST Tardis.dev pour les métadonnées
TARDIS_REST_URL = "https://api.tardis-dev.com/v1"
def __init__(self, api_key: str, exchange: str = "binance",
symbol: str = "btcusdt", book_levels: int = 1000):
self.api_key = api_key
self.exchange = exchange
self.symbol = symbol
self.book_levels = book_levels
# État interne du order book
self.bids: Dict[float, float] = defaultdict(float) # price -> quantity
self.asks: Dict[float, float] = defaultdict(float)
self.last_seq_id: Optional[int] = None
self.last_timestamp: Optional[datetime] = None
# Callbacks pour les événements
self.on_update: Optional[Callable[[OrderBookSnapshot], None]] = None
self.on_trade: Optional[Callable[[dict], None]] = None
# Statistiques
self.messages_processed = 0
self.reconnects = 0
def _apply_update(self, data: dict) -> OrderBookSnapshot:
"""Applique les mises à jour incrémentales au carnet d'ordres"""
# Type de message : snapshot ou update
msg_type = data.get("type", "update")
timestamp = pd.to_datetime(data.get("timestamp", datetime.now().isoformat()))
if msg_type == "snapshot":
# Vider le carnet et repartir de zéro
self.bids.clear()
self.asks.clear()
# Appliquer les données du snapshot
for level in data.get("bids", []):
self.bids[float(level["price"])] = float(level["quantity"])
for level in data.get("asks", []):
self.asks[float(level["price"])] = float(level["quantity"])
else:
# Mise à jour incrémentale
for level in data.get("bids", []):
price = float(level["price"])
qty = float(level["quantity"])
if qty == 0:
self.bids.pop(price, None)
else:
self.bids[price] = qty
for level in data.get("asks", []):
price = float(level["price"])
qty = float(level["quantity"])
if qty == 0:
self.asks.pop(price, None)
else:
self.asks[price] = qty
# Mettre à jour le sequence ID
if "seq_id" in data:
self.last_seq_id = data["seq_id"]
self.last_timestamp = timestamp
self.messages_processed += 1
return OrderBookSnapshot(
timestamp=timestamp,
exchange=self.exchange,
symbol=self.symbol,
bids=dict(self.bids),
asks=dict(self.asks),
seq_id=self.last_seq_id or 0
)
async def replay_historical(
self,
start_date: datetime,
end_date: datetime,
callback: Optional[Callable] = None
):
"""
Réplique les données historiques du carnet d'ordres.
Utilise l'API REST pour télécharger les données puis les traite.
"""
logger.info(f"Début de la réplication historique: {start_date} -> {end_date}")
# Construire l'URL de téléchargement
date_str = start_date.strftime("%Y-%m-%d")
url = (
f"{self.TARDIS_REST_URL}/download"
f"?exchange={self.exchange}"
f"&symbol={self.symbol}"
f"&date={date_str}"
f"&format=json"
f"&channels=book"
)
headers = {"Authorization": f"Bearer {self.api_key}"}
async with aiohttp.ClientSession() as session:
# Télécharger les données avec streaming pour gérer les gros fichiers
async with session.get(url, headers=headers) as response:
if response.status != 200:
error_text = await response.text()
raise Exception(f"Erreur API Tardis: {response.status} - {error_text}")
# Traiter le flux JSON ligne par ligne (NDJSON format)
async for line in response.content:
line = line.strip()
if not line:
continue
try:
data = json.loads(line)
# Ne traiter que les messages du order book
if data.get("channel") == "book" or "bids" in data or "asks" in data:
snapshot = self._apply_update(data)
if callback:
await callback(snapshot)
elif self.on_update:
self.on_update(snapshot)
except json.JSONDecodeError as e:
logger.warning(f"JSON invalide ignoré: {e}")
continue
logger.info(f"Réplication terminée: {self.messages_processed} messages traités")
async def get_available_dates(self, year: int, month: int) -> List[str]:
"""Récupère les dates disponibles pour un mois donnée"""
url = (
f"{self.TARDIS_REST_URL}/dates"
f"?exchange={self.exchange}"
f"&symbol={self.symbol}"
f"&year={year}"
f"&month={month:02d}"
)
headers = {"Authorization": f"Bearer {self.api_key}"}
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers) as response:
if response.status == 200:
data = await response.json()
return data.get("dates", [])
else:
return []
Exemple d'utilisation basique
async def example_basic_usage():
replayer = BinanceOrderBookReplayer(
api_key="YOUR_TARDIS_API_KEY",
symbol="btcusdt"
)
# Callback simple pour afficher les mises à jour
async def print_update(snapshot: OrderBookSnapshot):
if snapshot.messages_processed % 1000 == 0:
print(f"[{snapshot.timestamp}] Best Bid: {snapshot.best_bid}, "
f"Best Ask: {snapshot.best_ask}, "
f"Spread: {snapshot.spread}")
start = datetime(2026, 1, 15, 9, 30)
end = datetime(2026, 1, 15, 10, 30)
await replayer.replay_historical(start, end, callback=print_update)
Analyse Avancée avec Intelligence Artificielle
L'un des cas d'usage les plus puissants est l'analyse automatisée des patterns de liquidité via IA. Nous utilisons HolySheep AI pour ses tarifs imbattables et sa latence minimale :
- GPT-4.1 : 8$/MTok — idéal pour l'analyse complexe
- DeepSeek V3.2 : 0,42$/MTok — excellent rapport qualité/prix pour le parsing
- Latence moyenne : <50ms
# data_analyzer.py - Analyse IA des patterns de order book
import json
import aiohttp
import pandas as pd
from datetime import datetime, timedelta
from typing import List, Dict, Optional
from collections import deque
import asyncio
class OrderBookAnalyzer:
"""
Analyseur intelligent de carnets d'ordres utilisant l'IA.
Identifie les walls, les imbalances, et génère des alertes.
"""
def __init__(self, holy_sheep_api_key: str):
self.api_key = holy_sheep_api_key
self.base_url = "https://api.holysheep.ai/v1"
# Fenêtre glissante pour l'analyse temporelle
self.snapshots: deque = deque(maxlen=10000)
# Paramètres de détection
self.wall_threshold_usd = 100_000 # 100k$ = wall potentiel
self.imbalance_threshold = 0.15 # 15% d'imbalance = signal
async def analyze_with_ai(self, df: pd.DataFrame) -> Dict:
"""
Envoie un résumé du order book à l'IA pour analyse.
Coût estimé pour 10M tokens/mois:
- HolySheep GPT-4.1: 8$ × 10M / 1M = 80$/mois
- OpenAI GPT-4o: 15$ × 10M / 1M = 150$/mois
→ Économie de 47% avec HolySheep
"""
# Préparer le résumé
summary = self._generate_summary(df)
prompt = f"""
Tu es un analyste de marché expert en order book. Analyse les données suivantes:
{json.dumps(summary, indent=2)}
Identifie:
1. Walls significatifs (ordres > $100,000)
2. Imbalance entre bids et asks
3. Spread anormal
4. Risque de Slippage pour un ordre de $50,000
5. Recommandation: ACHETER, VENDRE ou NEUTRE
Réponds en JSON avec ce format:
{{"walls": [], "imbalance_ratio": 0.0, "spread_bps": 0.0,
"estimated_slippage": 0.0, "recommendation": "NEUTRE"}}
"""
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": "gpt-4.1",
"messages": [
{"role": "system", "content": "Tu es un analyste financier expert."},
{"role": "user", "content": prompt}
],
"temperature": 0.3,
"max_tokens": 500
}
async with aiohttp.ClientSession() as session:
async with session.post(
f"{self.base_url}/chat/completions",
headers=headers,
json=payload
) as response:
if response.status == 200:
result = await response.json()
content = result["choices"][0]["message"]["content"]
# Parser la réponse JSON
try:
return json.loads(content)
except:
return {"error": "Parse failed", "raw": content}
else:
return {"error": f"API error: {response.status}"}
def _generate_summary(self, df: pd.DataFrame) -> Dict:
"""Génère un résumé structuré du order book"""
bids = df[df["side"] == "bid"].copy()
asks = df[df["side"] == "ask"].copy()
# Calculer la valeur de chaque niveau
bids["value_usd"] = bids["price"] * bids["quantity"]
asks["value_usd"] = asks["price"] * asks["quantity"]
return {
"timestamp": datetime.now().isoformat(),
"best_bid": float(bids["price"].max()) if len(bids) > 0 else 0,
"best_ask": float(asks["price"].min()) if len(asks) > 0 else 0,
"spread": float(asks["price"].min() - bids["price"].max()) if len(bids) > 0 and len(asks) > 0 else 0,
"total_bid_value": float(bids["value_usd"].sum()),
"total_ask_value": float(asks["value_usd"].sum()),
"bid_depth_1pct": float(bids[bids["price"] > bids["price"].max() * 0.99]["value_usd"].sum()),
"ask_depth_1pct": float(asks[asks["price"] < asks["price"].min() * 1.01]["value_usd"].sum()),
"top_5_bids": [
{"price": float(r["price"]), "qty": float(r["quantity"])}
for _, r in bids.nlargest(5, "price").iterrows()
],
"top_5_asks": [
{"price": float(r["price"]), "qty": float(r["quantity"])}
for _, r in asks.nsmallest(5, "price").iterrows()
]
}
def detect_walls(self, df: pd.DataFrame) -> List[Dict]:
"""Détecte les walls significatifs dans le order book"""
walls = []
for side in ["bid", "ask"]:
subset = df[df["side"] == side].copy()
subset["value_usd"] = subset["price"] * subset["quantity"]
for _, row in subset.iterrows():
if row["value_usd"] > self.wall_threshold_usd:
walls.append({
"side": side,
"price": float(row["price"]),
"quantity": float(row["quantity"]),
"value_usd": float(row["value_usd"]),
"is_wall": True
})
return walls
def calculate_imbalance(self, df: pd.DataFrame) -> float:
"""Calcule le ratio d'imbalance du order book"""
bids = df[df["side"] == "bid"]
asks = df[df["side"] == "ask"]
bid_value = (bids["price"] * bids["quantity"]).sum()
ask_value = (asks["price"] * asks["quantity"]).sum()
if bid_value + ask_value == 0:
return 0.0
return (bid_value - ask_value) / (bid_value + ask_value)
def estimate_slippage(self, df: pd.DataFrame, order_size_usd: float) -> Dict:
"""Estime le slippage pour un ordre de taille donnée"""
side = "bid" if order_size_usd > 0 else "ask"
size = abs(order_size_usd)
subset = df[df["side"] == side].copy()
if side == "bid":
subset = subset.sort_values("price", ascending=False)
else:
subset = subset.sort_values("price", ascending=True)
subset["cumulative_value"] = (subset["price"] * subset["quantity"]).cumsum()
subset["avg_price"] = (
(subset["price"] * subset["quantity"]).cumsum() /
subset["quantity"].cumsum()
)
# Trouver le prix moyen pour notre taille d'ordre
filled = subset[subset["cumulative_value"] <= size]
remaining = size - filled["cumulative_value"].sum() if len(filled) > 0 else size
if len(filled) > 0:
avg_filled_price = (
(filled["price"] * filled["quantity"]).sum() /
filled["quantity"].sum()
)
else:
avg_filled_price = subset.iloc[0]["price"] if len(subset) > 0 else 0
# Prix d'exécution final (estimé)
if remaining > 0 and len(subset) > len(filled):
next_level = subset.iloc[len(filled)]
final_price = next_level["price"]
else:
final_price = avg_filled_price
# Calculer le slippage en basis points
if side == "bid":
best_price = subset.iloc[0]["price"] if len(subset) > 0 else 0
slippage_bps = ((final_price - best_price) / best_price * 10000) if best_price > 0 else 0
else:
best_price = subset.iloc[0]["price"] if len(subset) > 0 else 0
slippage_bps = ((best_price - final_price) / best_price * 10000) if best_price > 0 else 0
return {
"order_size_usd": order_size_usd,
"avg_price": avg_filled_price,
"final_price": final_price,
"slippage_bps": slippage_bps,
"slippage_pct": slippage_bps / 100
}
async def main_analysis():
"""Exemple d'utilisation complète de l'analyseur"""
analyzer = OrderBookAnalyzer(holy_sheep_api_key="YOUR_HOLYSHEEP_API_KEY")
# Créer un order book de test
test_data = []
mid_price = 97500.0
for i in range(50):
test_data.append({
"price": mid_price - (i + 1) * 10,
"quantity": 0.5 + (i % 5) * 0.2,
"side": "bid"
})
test_data.append({
"price": mid_price + (i + 1) * 10,
"quantity": 0.5 + (i % 5) * 0.2,
"side": "ask"
})
# Ajouter un wall du côté bid
test_data.append({
"price": mid_price - 50,
"quantity": 10, # ~500k$ de wall
"side": "bid"
})
df = pd.DataFrame(test_data)
# Analyse basique
walls = analyzer.detect_walls(df)
imbalance = analyzer.calculate_imbalance(df)
slippage = analyzer.estimate_slippage(df, 50000) # Ordre de 50k$
print(f"Wall détectés: {len(walls)}")
print(f"Imbalance: {imbalance:.2%}")
print(f"Slippage estimé: {slippage['slippage_bps']:.2f} bps")
# Analyse IA (optionnel, plus coûteux)
if analyzer.api_key != "YOUR_HOLYSHEEP_API_KEY":
ai_result = await analyzer.analyze_with_ai(df)
print(f"Recommandation IA: {ai_result.get('recommendation', 'N/A')}")
if __name__ == "__main__":
asyncio.run(main_analysis())
Cas d'Usage : Backtesting de Stratégie Market Making
# main.py - Pipeline complet de backtesting
import asyncio
import json
from datetime import datetime, timedelta
from orderbook_replayer import BinanceOrderBookReplayer, OrderBookSnapshot
from data_analyzer import OrderBookAnalyzer
import pandas as pd
class MarketMakerBacktester:
"""
Backtester pour une stratégie de market making basique.
Place des ordres bid/ask autour du mid-price avec un spread fixe.
"""
def __init__(self, spread_bps: float = 10, position_limit: float = 1.0):
self.spread_bps = spread_bps # Spread en basis points
self.position_limit = position_limit # Position max en BTC
self.position = 0.0
self.pnl = 0.0
self.trades = []
def simulate_order(self, snapshot: OrderBookSnapshot, timestamp: datetime):
"""Simule l'exécution d'un ordre market maker"""
if not snapshot.mid_price:
return
# Prix théorique de nos ordres
spread = snapshot.mid_price * self.spread_bps / 10000
bid_price = snapshot.mid_price - spread / 2
ask_price = snapshot.mid_price + spread / 2
# Vérifier si nos ordres sont exécutés
# Simplification : on vérifie la présence de contreparties
bid_volume = snapshot.bids.get(bid_price, 0)
ask_volume = snapshot.asks.get(ask_price, 0)
if bid_volume > 0 and self.position < self.position_limit:
# Exécution d'un achat
fill_size = min(bid_volume, self.position_limit - self.position)
cost = fill_size * bid_price
self.position += fill_size
self.pnl -= cost
self.trades.append({
"timestamp": timestamp,
"side": "BUY",
"price": bid_price,
"quantity": fill_size,
"pnl_after": self.pnl
})
if ask_volume > 0 and self.position > -self.position_limit:
# Exécution d'une vente
fill_size = min(ask_volume, self.position + self.position_limit)
revenue = fill_size * ask_price
self.position -= fill_size
self.pnl += revenue
self.trades.append({
"timestamp": timestamp,
"side": "SELL",
"price": ask_price,
"quantity": fill_size,
"pnl_after": self.pnl
})
def get_summary(self) -> dict:
"""Génère un résumé des performances"""
return {
"total_pnl": self.pnl,
"final_position": self.position,
"num_trades": len(self.trades),
"avg_trade_size": sum(t["quantity"] for t in self.trades) / len(self.trades) if self.trades else 0
}
async def run_backtest():
"""Exécute le backtest sur une période historique"""
# Configuration
tardis_api_key = "YOUR_TARDIS_API_KEY"
holy_sheep_api_key = "YOUR_HOLYSHEEP_API_KEY"
# Initialiser les composants
replayer = BinanceOrderBookReplayer(
api_key=tardis_api_key,
symbol="btcusdt"
)
analyzer = OrderBookAnalyzer(holy_sheep_api_key=holy_sheep_api_key)
backtester = MarketMakerBacktester(spread_bps=15)
# Callback pour traiter chaque snapshot
async def process_snapshot(snapshot: OrderBookSnapshot):
# Mettre à jour le backtester
backtester.simulate_order(snapshot, snapshot.timestamp)
# Analyse périodique (toutes les 10000 updates)
if snapshot.messages_processed % 10000 == 0:
df = snapshot.to_dataframe()
walls = analyzer.detect_walls(df)
print(f"[{snapshot.timestamp}] "
f"Processed: {snapshot.messages_processed}, "
f"Best Bid: {snapshot.best_bid}, "
f"Walls: {len(walls)}")
# Période de test : 1 heure de données
start_date = datetime(2026, 4, 15, 14, 0)
end_date = datetime(2026, 4, 15, 15, 0)
print("Démarrage du backtest...")
await replayer.replay_historical(start_date, end_date, callback=process_snapshot)
# Afficher les résultats
summary = backtester.get_summary()
print("\n=== RÉSULTATS DU BACKTEST ===")
print(f"PnL total: ${summary['total_pnl']:,.2f}")
print(f"Position finale: {summary['final_position']:.4f} BTC")
print(f"Nombre de trades: {summary['num_trades']}")
print(f"Taille moyenne: {summary['avg_trade_size']:.4f} BTC")
# Sauvegarder les trades
if backtester.trades:
df_trades = pd.DataFrame(backtester.trades)
df_trades.to_csv("backtest_trades.csv", index=False)
print("\nTrades sauvegardés dans backtest_trades.csv")
if __name__ == "__main__":
asyncio.run(run_backtest())
Comparaison des Coûts API pour l'Analyse de Données Financières
| Fournisseur | Modèle | Prix $/MTok | Latence P50 | Coût 10M tokens/mois | Économie vs OpenAI |
|---|---|---|---|---|---|
| HolySheep AI | DeepSeek V3.2 | $0.42 | <50ms | $4,200 | -97% |
| HolySheep AI | Gemini 2.5 Flash | $2.50 | <80ms | $25,000 | -83% |
| HolySheep AI | GPT-4.1 | $8.00 | <100ms | $80,000 | -47% |
| OpenAI | GPT-4.1 | $15.00 | <150ms | $150,000 | Référence |
| Anthropic | Claude Sonnet 4.5 | $15.00 | <180ms | $150,000 | +0% |
Calculs basés sur des données tarifaires vérifiées en avril 2026. Les économies sont calculées par rapport à OpenAI GPT-4.1 à 15$/MTok.
Optimisation des Coûts : Stratégies Pratiques
Pour une pipeline d'analyse de order book traitant 10 millions de tokens par mois :
- Parsing et structuration : DeepSeek V3.2 à 0,42$/MTok — idéal pour extraire les patterns
- Analyse complexe : GPT-4.1 à 8$/MTok — pour les recommandations trading
- Résumé et reporting : Gemini 2.5 Flash à 2,50$/MTok — excellent pour les synthèses
Coût total avec HolySheep : environ 4 200$/mois vs 150 000$/mois avec Anthropic — une économie de 145 800$/mois.
Erreurs courantes et solutions
1. Erreur 401 Unauthorized — Clé API invalide
# ❌ ERREUR : Clé API manquante ou incorrecte
async with session.get(url, headers={"Authorization": "Bearer None"}) as resp:
# Code: 401 Unauthorized
✅ SOLUTION : Vérifier et configurer correctement la clé
import os
api_key = os.getenv("TARDIS_API_KEY")
if not api_key:
raise ValueError("TARDIS_API_KEY non configurée. "
"Obtenez votre clé sur https://tardis-dev.com")
headers = {"Authorization": f"Bearer {api_key}"}
Alternative avec gestion d'erreur
try:
async with session.get(url, headers=headers) as response:
if response.status == 401:
raise PermissionError("Clé API Tardis invalide ou expirée")
except aiohttp.ClientError as e:
logger.error(f"Erreur de connexion: {e}")
2. Erreur de sequence — Trous dans les données
# ❌ ERREUR : Sequence ID manquant ou discontinu
Message 1: seq_id=1000
Message 2: seq_id=1002 (manquant 1001!)
Résultat: Order book corrompu, qty incohérentes
✅ SOLUTION : Détecter et gérer les discontinuités
class BinanceOrderBookReplayer:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.missing_sequences = []
def _apply_update(self, data: dict) -> OrderBookSnapshot:
current_seq = data.get("seq_id")
if current_seq and self.last_seq_id:
expected = self.last_seq_id + 1
if current_seq != expected:
gap = current_seq - expected
self.missing_sequences.append({
"from": expected,
"to": current_seq - 1,
"gap": gap
})
logger.warning(f"Séquence manquante: {expected}->{current_seq-1} "
f"(gap de {gap} messages)")
# Option: récupérer les données manquantes via API REST
# ou ignorer et prendre le risque
self.last_seq_id = current_seq
return super()._apply_update(data)
3. MemoryError — Traitement de gros volumes
# ❌ ERREUR : Consommation mémoire excessive
Données 1 jour BTC/USDT: ~500MB de JSON
30 jours: 15GB → Out of Memory
✅ SOLUTION : Streaming et fenêtrage
async def process_streaming(replayer, window_size=10000):
"""Traite les données par fenêtres pour éviter OOM"""
window = []
total_processed = 0
async def window_callback(snapshot):
nonlocal window, total_processed
window.append(snapshot.to_dataframe())
if len(window) >= window_size:
# Traiter et vider la fenêtre
df_window = pd.concat(window, ignore_index=True)
await analyze_window(df_window)
window = [] # Libérer la mémoire
total_processed += len(df_window)
print(f"Fenêtre traitée: {total_processed} snapshots")
start = datetime(2026, 4, 1)
end = datetime(2026, 4, 30)
await