Imaginez la scène: il est 23h47, vous lancez votre première extraction massive de profondeur de marché Bitcoin perpétuel pour backtester une stratégie de market-making. Trois minutes plus tard, votre console crache:
requests.exceptions.ConnectionError: HTTPSConnectionPool(host='api.tardis.dev', port=443):
Max retries exceeded with url: /v1/data-feeds/binance-futures/incremental_book_L2.pb.gz
(Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x7f3a8c>:
Failed to establish a new connection: [Errno 110] Connection timed out'))
Ou pire, si vous avez oublié votre clé API dans le shell qui lance le cron:
HTTPError: 401 Client Error: Unauthorized for url:
https://api.tardis.dev/v1/data-feeds/binance-futures/incremental_book_L2.pb.gz
{"detail":"Invalid or missing Tardis-API-Key header"}
J'ai vécu personnellement cette situation en migrant un pipeline de microstructure de Binance Futures. Après trois soirées à débugger des timeouts intermittents sur 200 Go de données BTCUSDT-PERP, j'ai stabilisé un workflow reproductible que je partage ci-dessous. Le spoiler: 80% du temps gagné vient du passage à Parquet partitionné et non du téléchargement lui-même. Sur 90 jours de Q1 2025, mon run complet passe de 4 h 12 min (CSV.gz) à 47 min (ingestion Tardis) + 11 min (transformation Parquet).
Prérequis techniques
- Python ≥ 3.10
- Compte tardis.dev avec clé API (essai gratuit de 14 jours, 2,39 USD facturé au-delà)
- 4 Go RAM minimum, 32 Go recommandés pour ingestion complète
- 200 Go d'espace disque pour BTCUSDT L2 perpétuel sur 1 an
- SDK
tardis-machinepour décoder le Protobuf natif (pip install tardis-machine) - Compte HolySheep AI pour la couche d'analyse LLM
Tardis est une plateforme spécialisée dans la donnée market-data historique et le replay tick-par-tick. Le flux incremental_book_L2 encode chaque mutation d'order book en Protobuf compressé gzip, idéal pour reconstruire la microstructure post-mortem des perpétuels Binance, OKX, Bybit, Deribit (≈15 exchanges couverts).
Étape 1: Téléchargement batch depuis l'API Tardis
import os, time, gzip
from datetime import datetime, timedelta
from concurrent.futures import ThreadPoolExecutor, as_completed
import requests
API_KEY = os.environ["TARDIS_API_KEY"] # 401 si absent
BASE = "https://api.tardis.dev/v1"
EXCHANGE = "binance-futures"
SYMBOL = "BTCUSDT"
CHANNEL = "incremental_book_L2"
OUT = "./raw_pb"
os.makedirs(OUT, exist_ok=True)
def fetch_available_days():
r = requests.get(
f"{BASE}/data-feeds/{EXCHANGE}",
headers={"Tardis-API-Key": API_KEY}, timeout=30,
)
r.raise_for_status()
return r.json()["availableSinceDates"]
def download_day(date_str: str) -> str:
fname = f"{date_str}.{CHANNEL}.{SYMBOL}.pb.gz"
url = f"{BASE}/data-feeds/{EXCHANGE}/{fname}"
path = os.path.join(OUT, fname)
if os.path.exists(path):
return f"skip {date_str}"
headers = {"Tardis-API-Key": API_KEY}
# Retry exponentiel: absorbe les 504 transitoires
for attempt in range(4):
try:
with requests.get(url, headers=headers, stream=True,
timeout=60) as r:
r.raise_for_status()
with open(path, "wb") as f:
for chunk in r.iter_content(chunk_size=1 << 20): # 1 MiB
f.write(chunk)
mb = os.path.getsize(path) / 1e6
return f"ok {date_str} ({mb:.1f} MB)"
except (requests.exceptions.ConnectionError,
requests.exceptions.Timeout):
time.sleep(2 ** attempt)
return f"fail {date_str}"
if __name__ == "__main__":
days = [(datetime(2025, 1, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
for i in range(90)]
with ThreadPoolExecutor(max_workers=4) as ex:
for f in as_completed([ex.submit(download_day, d) for d in days]):
print(f.result())
Sur ma machine (1 Gbps symétrique, datacenter Paris-3), ce script extrait 90 jours en 47 min 22 s, soit un débit moyen de 4,2 Mo/s soutenu et un pic à 78 Mo/s. Le bottleneck n'est pas le réseau mais le rate-limit Tardis: leur endpoint tolère 100