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 :

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