Dans l'univers du trading algorithmique et de la recherche quantitative, disposer de données de marché fiables constitue la fondation de toute stratégie performante. Le carnet d'ordres (order book) représente l'état instantané du marché : qui veut acheter, à quel prix, et qui souhaite vendre. Ce tutoriel technique vous guidera paso a paso dans l'utilisation de l'API Tardis.dev avec Python pour récupérer les données tick par tick des échanges Binance, puis les restructurer pour le backtesting de stratégies market making.
Qu'est-ce que Tardis.dev et pourquoi l'utiliser ?
Tardis.dev se positionne comme un fournisseur de données de marché cryptocurrency de haute qualité, avec une couverture multi-échanges et une latence minimale. Avant de plonger dans le code, examinons comment cette solution se compare aux alternatives courantes pour la récupération de données de carnet d'ordres.
Tableau comparatif des fournisseurs de données marché cryptocurrency
| Critère | Tardis.dev | Binance API officielle | CCXT (multi-exchange) |
|---|---|---|---|
| Format des données | Normalisé, parquet/CSV | Brut, nécessite parsing | JSON structuré |
| Latence de livraison | <100ms (webhook/WS) | Variable selon serveur | Dépend du exchange |
| Historique order book | Full depth, 1ms résolution | Limité à 1000 niveaux | Aucun (temps réel only) |
| Prix indicatif | Gratuit (tier starter) / $99/mois | Gratuit (rate limited) | Gratuit (librairie open-source) |
| Frais de données | $0.0009/Mo tick données | Gratuit avec limites | N/A (requiert exchange account) |
Configuration initiale de l'environnement
Avant de commencer, asegúrese de tener Python 3.9+ instalado y un entorno virtual configurado. Las dependencias necesarias se instalarán a continuación.
# Création de l'environnement virtuel
python -m venv tardis-env
source tardis-env/bin/activate # Linux/Mac
tardis-env\Scripts\activate # Windows
Installation des dépendances
pip install tardis-client aiohttp pandas pyarrow websocket-client
La librería tardis-client est l'SDK officiel Python maintenu par l'équipe Tardis.dev. Elle offre une interface async natif compatible avec les flux de données haute fréquence.
Connexion à l'API Tardis.dev
Obtention des credentials API
Après inscription sur la plateforme Tardis.dev, vous recevrez une clé API dans votre tableau de bord. Cette clé sera utilisée pour l'authentification lors des requêtes.
# Configuration des credentials
import os
Option 1 : Variable d'environnement (recommandé pour production)
os.environ['TARDIS_API_KEY'] = 'your_tardis_api_key_here'
Option 2 : Chargement depuis fichier .env
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.getenv('TARDIS_API_KEY')
BASE_URL = 'https://api.tardis.dev/v1'
Téléchargement des données historiques
Tardis.dev propose une API REST pour télécharger des datasets historiques. Pour le carnet d'ordres Binance, nous allons récupérer les données de niveau 2 (full depth) avec une résolution milliseconde.
import requests
import pandas as pd
from datetime import datetime, timedelta
class TardisDataDownloader:
"""Classe pour télécharger les données de marché via Tardis.dev"""
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = 'https://api.tardis.dev/v1'
self.headers = {
'Authorization': f'Bearer {self.api_key}',
'Content-Type': 'application/json'
}
def list_available_exchanges(self) -> list:
"""Récupère la liste des échanges disponibles"""
response = requests.get(
f'{self.base_url}/exchanges',
headers=self.headers
)
response.raise_for_status()
return response.json()
def get_binance_book_ticker(self, date: str, symbol: str = 'btcusdt'):
"""
Télécharge les données book ticker pour une date donnée
Args:
date: Format 'YYYY-MM-DD'
symbol: Symbole de trading en minuscules
"""
url = (
f'{self.base_url}/feeds/binance:f{ symbol}-perp '
f'?date={date}&type=book_ticker'
)
# Note: Pour les perpetual futures, utiliser binance:f{symbol}-perp
# Pour spot: binance:{symbol}
response = requests.get(url, headers=self.headers)
if response.status_code == 200:
return response.json()
elif response.status_code == 404:
raise ValueError(f"Aucune donnée disponible pour {date}")
else:
raise Exception(f"Erreur API: {response.status_code}")
def download_orderbook_snapshots(
self,
symbol: str,
start_date: str,
end_date: str
) -> pd.DataFrame:
"""
Télécharge les snapshots de carnet d'ordres pour backtesting
Args:
symbol: Symbole (ex: 'btcusdt')
start_date: Date début 'YYYY-MM-DD'
end_date: Date fin 'YYYY-MM-DD'
"""
all_snapshots = []
# Conversion des dates en range
start = datetime.strptime(start_date, '%Y-%m-%d')
end = datetime.strptime(end_date, '%Y-%m-%d')
current = start
while current <= end:
date_str = current.strftime('%Y-%m-%d')
try:
data = self.get_binance_book_ticker(date_str, symbol)
for record in data:
all_snapshots.append({
'timestamp': record['timestamp'],
'bid_price': record.get('bidPrice'),
'bid_qty': record.get('bidQty'),
'ask_price': record.get('askPrice'),
'ask_qty': record.get('askQty'),
'symbol': symbol
})
except ValueError as e:
print(f"跳过 {date_str}: {e}")
current += timedelta(days=1)
df = pd.DataFrame(all_snapshots)
if not df.empty:
df['timestamp'] = pd.to_datetime(df['timestamp'])
df = df.sort_values('timestamp').reset_index(drop=True)
return df
Utilisation
downloader = TardisDataDownloader(API_KEY)
df_bookticker = downloader.download_orderbook_snapshots(
symbol='btcusdt',
start_date='2024-03-01',
end_date='2024-03-02'
)
Connexion WebSocket pour données temps réel
Pour le trading en direct ou la collecte continue de données, la connexion WebSocket offre une latence minimale. Le protocole de Tardis.dev est compatible avec les flux Binance tout en ajoutant une normalisation des données.
import asyncio
import json
from tardis_client import TardisClient, MessageType
class OrderBookReplayer:
"""
Récepteur WebSocket pour les données de carnet d'ordres
avec capacités de replay pour backtesting
"""
def __init__(self, exchange: str, symbol: str):
self.exchange = exchange
self.symbol = symbol
self.orderbook = {
'bids': {}, # {price: quantity}
'asks': {}
}
self.message_count = 0
async def on_book_ticker(self, data: dict):
"""Callback pour chaque mise à jour du book ticker"""
self.message_count += 1
# Mise à jour du best bid/ask
bid_price = float(data['bidPrice'])
bid_qty = float(data['bidQty'])
ask_price = float(data['askPrice'])
ask_qty = float(data['askQty'])
# Calcul du spread
spread = ask_price - bid_price
spread_bps = (spread / bid_price) * 10000
# Affichage toutes les 100 messages
if self.message_count % 100 == 0:
print(
f"[{data['timestamp']}] "
f"Bid: {bid_price:.2f} x {bid_qty} | "
f"Ask: {ask_price:.2f} x {ask_qty} | "
f"Spread: {spread_bps:.2f} bps"
)
async def on_orderbook_update(self, data: dict):
"""Callback pour les mises à jour full depth"""
if data.get('type') == 'snapshot':
# Initialisation du carnet
self.orderbook['bids'] = {
float(p): float(q) for p, q in data.get('bids', [])
}
self.orderbook['asks'] = {
float(p): float(q) for p, q in data.get('asks', [])
}
elif data.get('type') == 'update':
# Application des deltas
for price, qty in data.get('bids', []):
price, qty = float(price), float(qty)
if qty == 0:
self.orderbook['bids'].pop(price, None)
else:
self.orderbook['bids'][price] = qty
for price, qty in data.get('asks', []):
price, qty = float(price), float(qty)
if qty == 0:
self.orderbook['asks'].pop(price, None)
else:
self.orderbook['asks'][price] = qty
async def subscribe_realtime(self, api_key: str):
"""Connexion au flux temps réel"""
tardis = TardisClient(api_key=api_key)
# Connexion au flux Binance pour perpetual BTCUSDT
exchange_name = f"binance:f{self.symbol}-perp"
await tardis.subscribe(
exchanges=[exchange_name],
channels=['book_ticker'], # ou 'orderbook' pour full depth
on_message=self._handle_message
)
async def _handle_message(self, message: dict, message_type: MessageType):
"""Gestionnaire central des messages"""
if message_type == MessageType.BookTicker:
await self.on_book_ticker(message)
elif message_type == MessageType.OrderbookUpdate:
await self.on_orderbook_update(message)
def get_mid_price(self) -> float:
"""Retourne le prix moyen du meilleur bid/ask"""
if not self.orderbook['bids'] or not self.orderbook['asks']:
return None
best_bid = max(self.orderbook['bids'].keys())
best_ask = min(self.orderbook['asks'].keys())
return (best_bid + best_ask) / 2
def get_top_levels(self, depth: int = 10) -> dict:
"""Retourne les N meilleurs niveaux du carnet"""
sorted_bids = sorted(
self.orderbook['bids'].items(),
key=lambda x: x[0],
reverse=True
)[:depth]
sorted_asks = sorted(
self.orderbook['asks'].items(),
key=lambda x: x[0]
)[:depth]
return {
'bids': sorted_bids,
'asks': sorted_asks
}
Exécution du récepteur temps réel
async def main():
replayer = OrderBookReplayer('binance', 'btcusdt')
await replayer.subscribe_realtime(API_KEY)
if __name__ == '__main__':
asyncio.run(main())
回放撮合盘口 : Système de matching pour backtesting
Le回放 (replay) du carnet d'ordres permet de simuler l'exécution d'ordres sur des données historiques. Cette technique est essentielle pour tester les stratégies market making avant déploiement en production.
import heapq
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple
from datetime import datetime
from enum import Enum
class OrderSide(Enum):
BUY = 1
SELL = -1
@dataclass(order=True)
class Order:
price: float
quantity: float = field(compare=False)
timestamp: datetime = field(compare=False)
order_id: str = field(compare=False)
side: OrderSide = field(compare=False)
class OrderBookSimulator:
"""
Simulateur de carnet d'ordres pour backtesting
Implémente le matching algorithm standard price-time priority
"""
def __init__(self, maker_fee: float = 0.0002, taker_fee: float = 0.0004):
self.maker_fee = maker_fee
self.taker_fee = taker_fee
# Carnets d'ordres séparés par side
self.bids: List[Tuple[float, datetime, Order]] = [] # Max heap (prix décroissant)
self.asks: List[Tuple[float, datetime, Order]] = [] # Min heap (prix croissant)
# Index pour suppression rapide
self.orders: Dict[str, Order] = {}
self.order_locations: Dict[str, Tuple[str, int]] = {} # order_id -> (side, index)
# Statistiques
self.trade_history: List[dict] = []
self.position = 0.0
self.cash = 0.0
self.realized_pnl = 0.0
# Book ticker pour calculs de spread
self.last_best_bid = 0.0
self.last_best_ask = float('inf')
def update_book_ticker(self, timestamp: datetime, bid: float, bid_qty: float,
ask: float, ask_qty: float):
"""Met à jour avec les données book ticker de Tardis"""
self.last_best_bid = bid
self.last_best_ask = ask
def submit_order(self, order_id: str, side: OrderSide, price: float,
quantity: float, timestamp: datetime) -> List[dict]:
"""
Soumet un ordre et retourne les trades exécutés
Args:
order_id: Identifiant unique de l'ordre
side: BUY ou SELL
price: Prix limite
quantity: Quantité
timestamp: Horodatage
Returns:
Liste des trades exécutés avec détails
"""
new_order = Order(
price=price,
quantity=quantity,
timestamp=timestamp,
order_id=order_id,
side=side
)
trades = []
if side == OrderSide.BUY:
# Cherche les vendeurrs à prix <= prix limite
while self.asks and new_order.quantity > 0:
best_ask_price, _, best_ask_order = self.asks[0]
if best_ask_price > price:
break # Pas de liquidité au prix souhaité
# Calcul de la quantité à exécuter
exec_qty = min(new_order.quantity, best_ask_order.quantity)
exec_price = best_ask_order.price
# Frais taker pour l'acheteur
fees = exec_qty * exec_price * self.taker_fee
net_cost = exec_qty * exec_price + fees
# Mise à jour position et cash
self.position += exec_qty
self.cash -= net_cost
self.realized_pnl += exec_qty * exec_price
# Enregistrement du trade
trade = {
'timestamp': timestamp,
'order_id': order_id,
'counterparty_id': best_ask_order.order_id,
'side': 'BUY',
'price': exec_price,
'quantity': exec_qty,
'fees': fees,
'is_taker': True
}
trades.append(trade)
self.trade_history.append(trade)
# Mise à jour de l'ordre contrepartie
new_order.quantity -= exec_qty
best_ask_order.quantity -= exec_qty
# Nettoyage si ordre épuisé
if best_ask_order.quantity <= 1e-10:
heapq.heappop(self.asks)
self.orders.pop(best_ask_order.order_id, None)
# Ordre résiduel -> book en tant que maker
if new_order.quantity > 0:
heapq.heappush(self.bids, (-price, timestamp, new_order))
self.orders[order_id] = new_order
else: # SELL
# Cherche les acheteurrs à prix >= prix limite
while self.bids and new_order.quantity > 0:
best_bid_price, _, best_bid_order = self.bids[0]
best_bid_price = -best_bid_price # Inversion car max heap
if best_bid_price < price:
break
exec_qty = min(new_order.quantity, best_bid_order.quantity)
exec_price = best_bid_order.price
fees = exec_qty * exec_price * self.taker_fee
net_proceed = exec_qty * exec_price - fees
self.position -= exec_qty
self.cash += net_proceed
self.realized_pnl -= exec_qty * exec_price
trade = {
'timestamp': timestamp,
'order_id': order_id,
'counterparty_id': best_bid_order.order_id,
'side': 'SELL',
'price': exec_price,
'quantity': exec_qty,
'fees': fees,
'is_taker': True
}
trades.append(trade)
self.trade_history.append(trade)
new_order.quantity -= exec_qty
best_bid_order.quantity -= exec_qty
if best_bid_order.quantity <= 1e-10:
heapq.heappop(self.bids)
self.orders.pop(best_bid_order.order_id, None)
if new_order.quantity > 0:
heapq.heappush(self.asks, (price, timestamp, new_order))
self.orders[order_id] = new_order
return trades
def get_spread(self) -> Tuple[float, float]:
"""Retourne (spread absolu, spread en bps)"""
best_bid = -self.bids[0][0] if self.bids else 0
best_ask = self.asks[0][0] if self.asks else float('inf')
spread = best_ask - best_bid
spread_bps = (spread / best_bid) * 10000 if best_bid > 0 else 0
return spread, spread_bps
def get_orderbook_depth(self, levels: int = 10) -> dict:
"""Retourne les N niveaux de profondeur"""
top_bids = [
(-price, order.quantity)
for price, _, order in sorted(self.bids, key=lambda x: -x[0])[:levels]
]
top_asks = [
(price, order.quantity)
for price, _, order in sorted(self.asks, key=lambda x: x[0])[:levels]
]
return {'bids': top_bids, 'asks': top_asks}
def cancel_order(self, order_id: str) -> bool:
"""Annule un ordre existant"""
if order_id not in self.orders:
return False
order = self.orders[order_id]
if order.side == OrderSide.BUY:
# Recherche dans bids (stocké comme -price)
self.bids = [(p, t, o) for p, t, o in self.bids if o.order_id != order_id]
heapq.heapify(self.bids)
else:
self.asks = [(p, t, o) for p, t, o in self.asks if o.order_id != order_id]
heapq.heapify(self.asks)
del self.orders[order_id]
return True
Example de stratégie market making simple
class MarketMakerStrategy:
"""Stratégie market making basique avec gestion du inventory"""
def __init__(self, max_position: float = 1.0, target_spread_bps: float = 5.0):
self.max_position = max_position
self.target_spread_bps = target_spread_bps
self.order_book = OrderBookSimulator()
def on_book_ticker(self, timestamp: datetime, bid: float, bid_qty: float,
ask: float, ask_qty: float):
"""Callback appelé à chaque mise à jour book ticker"""
self.order_book.update_book_ticker(timestamp, bid, bid_qty, ask, ask_qty)
# Calcul du prix central
mid_price = (bid + ask) / 2
spread_bps = ((ask - bid) / mid_price) * 10000
# Ajustement du inventory skew
inventory_skew = self.order_book.position / self.max_position
# Prix limites avec skew
skew_factor = inventory_skew * 0.0001 # 1 bps par unité de position
buy_price = bid * (1 - skew_factor)
sell_price = ask * (1 + skew_factor)
# Annulation des ordres existants
existing_orders = list(self.order_book.orders.keys())
for order_id in existing_orders:
self.order_book.cancel_order(order_id)
# Placement de nouveaux ordres si dans les limites
if abs(self.order_book.position) < self.max_position:
self.order_book.submit_order(
f"MM_BUY_{timestamp.timestamp()}",
OrderSide.BUY,
buy_price,
0.01, # Taille fixe pour l'exemple
timestamp
)
if abs(self.order_book.position) < self.max_position:
self.order_book.submit_order(
f"MM_SELL_{timestamp.timestamp()}",
OrderSide.SELL,
sell_price,
0.01,
timestamp
)
print("回放撮合系统初始化完成!")
Intégration avec les données Tardis pour backtesting complet
import pandas as pd
from datetime import datetime, timedelta
def run_backtest(data: pd.DataFrame, strategy: MarketMakerStrategy) -> dict:
"""
Execute un backtest sur des données historiques
Args:
data: DataFrame avec colonnes timestamp, bid_price, bid_qty, ask_price, ask_qty
strategy: Instance de la stratégie à tester
"""
initial_position = strategy.order_book.position
initial_cash = strategy.order_book.cash
results = {
'trades': [],
'equity_curve': [],
'pnl': [],
'spread_history': []
}
for _, row in data.iterrows():
timestamp = pd.to_datetime(row['timestamp'])
# Mise à jour de la stratégie
strategy.on_book_ticker(
timestamp,
row['bid_price'],
row['bid_qty'],
row['ask_price'],
row['ask_qty']
)
# Enregistrement des métriques
spread = row['ask_price'] - row['bid_price']
mid = (row['ask_price'] + row['bid_price']) / 2
spread_bps = (spread / mid) * 10000 if mid > 0 else 0
results['spread_history'].append({
'timestamp': timestamp,
'spread_bps': spread_bps
})
# Calcul de l'equity
position_value = strategy.order_book.position * mid
equity = strategy.order_book.cash + position_value
results['equity_curve'].append({
'timestamp': timestamp,
'equity': equity,
'position': strategy.order_book.position,
'cash': strategy.order_book.cash
})
# Calcul des métriques finales
final_equity = results['equity_curve'][-1]['equity'] if results['equity_curve'] else 0
initial_equity = initial_cash + initial_position * data.iloc[0]['bid_price'] if len(data) > 0 else 0
total_pnl = final_equity - initial_equity
total_return = (total_pnl / initial_equity) * 100 if initial_equity != 0 else 0
# Calcul du Sharpe ratio (simplifié)
equity_series = pd.DataFrame(results['equity_curve'])
if len(equity_series) > 1:
returns = equity_series['equity'].pct_change().dropna()
sharpe = (returns.mean() / returns.std()) * (252 * 24 * 60) ** 0.5 if returns.std() > 0 else 0
else:
sharpe = 0
return {
'total_pnl': total_pnl,
'total_return_pct': total_return,
'sharpe_ratio': sharpe,
'num_trades': len(results['trades']),
'final_position': strategy.order_book.position,
'max_drawdown': calculate_max_drawdown(equity_series['equity'].tolist()) if len(equity_series) > 0 else 0,
'equity_curve': results['equity_curve']
}
def calculate_max_drawdown(equity_curve: list) -> float:
"""Calcule le drawdown maximum"""
peak = equity_curve[0]
max_dd = 0
for value in equity_curve:
if value > peak:
peak = value
dd = (peak - value) / peak if peak > 0 else 0
max_dd = max(max_dd, dd)
return max_dd * 100 # En pourcentage
Exécution du backtest
print("Démarrage du backtest avec données Tardis.dev...")
Téléchargement des données
downloader = TardisDataDownloader(API_KEY)
df = downloader.download_orderbook_snapshots(
symbol='btcusdt',
start_date='2024-03-01',
end_date='2024-03-02'
)
print(f"Données téléchargées: {len(df)} enregistrements")
Configuration de la stratégie
strategy = MarketMakerStrategy(
max_position=1.0,
target_spread_bps=5.0
)
Lancement du backtest
results = run_backtest(df, strategy)
print(f"\n=== Résultats du Backtest ===")
print(f"P&L Total: ${results['total_pnl']:.2f}")
print(f"Rendement: {results['total_return_pct']:.2f}%")
print(f"Sharpe Ratio: {results['sharpe_ratio']:.2f}")
print(f"Nombre de trades: {results['num_trades']}")
print(f"Drawdown Maximum: {results['max_drawdown']:.2f}%")
Structure des données et formats supportés
Tardis.dev propose plusieurs formats de données pour faciliter l'intégration avec vos pipelines d'analyse. Le format Parquet est particulièrement recommandé pour les datasets volumineux grâce à sa compression efficace.
| Format | Avantages | Cas d'usage | Taille typique |
|---|---|---|---|
| JSON Lines | Universel, lisible | Prototypage, debugging | 100% (référence) |
| CSV | Excel compatible | Analyse rapide | ~30% |
| Parquet | Compression, requêtes colonnes | Production, big data | ~15% |
| Arrow IPC | Streaming, zero-copy | Haute performance | ~20% |
Considérations de performance pour le trading haute fréquence
Pour les stratégies HFT (High Frequency Trading), la latence de traitement des données est critique. Voici les optimisations recommandées pour maximiser les performances avec les données Tardis.dev :
- Utilisation de NumPy/Cython : vectorisez les opérations sur les arrays plutôt que d'itérer sur les DataFrames
- Mode canal asynchrone : traitez les messages WebSocket dans un thread séparé pour éviter le blocage
- Pré-allocation mémoire : dimensionnez vos structures de données à l'avance pour éviter les réallocations
- FlatBuffers/Protobuf : utilisez le décodage binaire natif au lieu du parsing JSON
- Colocation serveur : déployez votre système dans le même datacenter que l'API
Erreurs courantes et solutions
Erreur 401 : Clé API invalide ou expirée
# Symptôme : requests.exceptions.HTTPError: 401 Client Error: Unauthorized
Cause : Clé API incorrecte ou tier gratuit expiré
Solution :
import os
Vérifier que la clé est correctement définie
print(f"API_KEY défini: {'TARDIS_API_KEY' in os.environ}")
Option 1 : Vérifier manuellement
API_KEY = os.environ.get('TARDIS_API_KEY')
if not API_KEY or len(API_KEY) < 20:
raise ValueError("Clé API invalide. Vérifiez votre dashboard Tardis.dev")
Option 2 : Utiliser un validación decorator
from functools import wraps
def validate_api_key(func):
@wraps(func)
def wrapper(*args, **kwargs):
key = os.environ.get('TARDIS_API_KEY')
if not key:
raise EnvironmentError(
"TARDIS_API_KEY non définie. "
"Configurez la variable d'environnement."
)
return func(*args, **kwargs)
return wrapper
@validate_api_key
def download_data():
# Votre logique ici
pass
Erreur 404 : Aucune donnée disponible pour la période demandée
# Symptôme : ValueError: Aucune donnée disponible pour 2024-01-01
Cause : Données historiques non disponibles pour cette date
ou symbole incorrect
Solution :
from datetime import datetime, timedelta
def get_available_date_range(exchange: str, symbol: str, api_key: str) -> dict:
"""
Vérifie la disponibilité des données pour un symbole
"""
base_url = 'https://api.tardis.dev/v1'
# Liste les échanges disponibles
response = requests.get(f'{base_url}/exchanges', headers={
'Authorization': f'Bearer {api_key}'
})
exchanges = response.json()
# Vérifie les symbols disponibles
symbol_info = next(
(s for s in exchanges.get('data', [])
if s['exchange'] == exchange and s['symbol'] == symbol),
None
)
if not symbol_info:
return {
'available': False,
'message': f"Symbole {exchange}:{symbol} non disponible",
'alternatives': []
}
return {
'available': True,
'start_date': symbol_info.get('dataStartDate'),
'end_date': symbol_info.get('dataEndDate'),
'message': 'Données disponibles'
}
Validation avant téléchargement
def safe_download(symbol: str, date: str, api_key: str) -> pd.DataFrame:
"""Télécharge avec validation préalable"""
# Validation du format de date
try:
datetime.strptime(date, '%Y-%m-%d')
except ValueError:
raise ValueError(f"Format de date invalide: {date}. Use YYYY-MM-DD")
# Vérifier les limites
range_info = get_available_date_range('binance', symbol, api_key)
if not range_info['available']:
raise ValueError(range_info['message'])
# Vérifier que la date demandée est dans la plage
requested = datetime.strptime(date, '%Y-%m-%d')
if range_info.get('start_date'):
start = datetime.strptime(range_info['start_date'], '%Y-%m-%d')
if requested < start:
raise ValueError(f"Date {date} antérieure à {range_info['start_date']}")
# Procéder au téléchargement
downloader = TardisDataDownloader(api_key)
return downloader.download_orderbook_snapshots(symbol, date, date)
Erreur de latence excessive en WebSocket
# Symptôme : Messages reçus avec délai