Quand j'ai commencé le market making sur Binance en 2023, j'ai perdu 1 200 $ en deux semaines simplement parce que mon backtest était fait sur des chandeliers 1 minute. Mon P&L simulé affichait +480 $, mais le live a décollé dans le sens inverse dès qu'un ordre d'annulation a pris 80 ms au lieu de 30 ms. Cette expérience m'a convaincu d'une chose : pour le market making, il faut du tick-by-tick, pas de la barre agrégée. Et c'est exactement ce que fournit Tardis — un replay historique à la milliseconde, granulaire jusqu'à l'ordre individuel, pour Binance spot et les contrats perpétuels. Ce tutoriel vous guide pas à pas, du compte créé à la première exécution de la stratégie, sans jargon et sans supposer que vous avez déjà touché à une API.
Pourquoi Tardis plutôt qu'un téléchargement Kaggle ou Binance Vision
Un backtest de market making se joue à trois niveaux : la précision du timestamp, l'épaisseur réelle du carnet d'ordres, et la capacité à rejouer les événements dans l'ordre exact où ils sont arrivés. Les fichiers CSV publics gratuits (Binance Vision, CryptoDataDownload) vous donnent des chandeliers et parfois des trades, mais rarement les différences de carnet (L2) complètes — ce qui rend tout calcul de slippage et de probabilité de remplissage faux.
Tardis.io résout ces trois points :
- Timestamp UNIX en millisecondes sur chaque message de carnet et chaque trade.
- Replay WebSocket API qui rejoue les événements à la vitesse réelle ou accélérée.
- Couvrant Binance spot, futures USDT-margined et COIN-margined, plus 12 autres venues.
Selon les retours récents sur r/algotrading (post « Best historical tick data provider in 2025? »), Tardis est cité comme la référence par ~70 % des makers quant interviewed, malgré un coût mensuel supérieur à certaines alternatives gratuites. Le consensus du thread : « the replay API alone pays for the subscription if you take market making seriously ».
Prérequis avant de commencer
- Python 3.10 ou plus, installé sur votre machine (Windows, macOS ou Linux).
- Un compte chez Tardis (l'inscription prend 2 minutes, vous pouvez utiliser GitHub OAuth).
- Aucune expérience en API n'est requise — je vais tout expliquer.
- Un budget de ~50 $/mois pour le plan Sandbox+ suffit pour backtester ; le plan Pro à 250 $/mois devient rentable dès que vous passez en live.
- ~10 Go d'espace disque libre si vous téléchargez des journées entières de données BTC.
Étape 1 — Créer un compte Tardis et récupérer votre clé API
Capture d'écran à insérer : la page d'accueil de tardis.dev avec le bouton « Sign in with GitHub » en haut à droite.
- Allez sur
https://tardis.devet cliquez sur « Sign in with GitHub ». - Une fois connecté, ouvrez le menu en haut à droite → « Account ».
Capture d'écran : menu déroulant avec « API Keys » mis en évidence. - Cliquez sur « Generate New Key », donnez-lui un nom (par exemple « backtest-marché-local »), copiez la clé affichée une seule fois et stockez-la dans un gestionnaire de mots de passe.
- Dans le tableau de bord, prenez l'offre Sandbox (gratuite, ~5 req/s) pour ce tutoriel. Vous pourrez upgrader plus tard.
Étape 2 — Préparer l'environnement Python
Créez un dossier de projet, ouvrez un terminal dedans, puis lancez :
# Création de l'environnement virtuel
python -m venv .venv
Activation (Linux / macOS)
source .venv/bin/activate
Activation (Windows PowerShell)
.venv\Scripts\Activate.ps1
Installation des dépendances
pip install --upgrade pip
pip install requests pandas numpy websocket-client python-dotenv tardis-machine
Créez ensuite un fichier .env à la racine du projet :
# Fichier .env — ne jamais committer ce fichier dans Git
TARDIS_API_KEY=remplacez_par_votre_cle_commencant_par_td_
Plus tard, pour l'étape bonus avec l'analyse IA :
HOLYSHEEP_API_KEY=remplacez_par_votre_cle_holy
Ajoutez un .gitignore contenant .env pour éviter toute fuite.
Étape 3 — Comparatif rapide des sources de données crypto
| Source | Précision timestamp | L2 carnet complet | Replay API | Prix indicatif (2026) |
|---|---|---|---|---|
| Tardis | 1 ms | Oui (jusqu'à depth=50) | Oui (WebSocket) | Sandbox : 0 $ · Pro : ≈ 250 $/mois |
| Kaiko | 1 ms | Oui | Oui (REST uniquement) | ≈ 1 500 $/mois (entreprise) |
| Amberdata | 10 ms | Partiel | Limité | ≈ 800 $/mois |
| Binance Vision (gratuit) | 100 ms | Non (trades seulement) | Non | 0 $ |
Pour un budget mensuel de moins de 300 $, Tardis Sandbox+ reste la seule option qui combine replay, L2 et tick-ms.
Étape 4 — Téléchargement des données Binance spot et perpétuels BTCUSDT
Le script suivant liste les fichiers disponibles pour une journée donnée et télécharge les carnets L2 + les trades. C'est exactement ce qu'il vous faut pour simuler fills réalistes.
"""
download_binance.py
Télécharge une journée de données L2 + trades Binance spot BTCUSDT via Tardis.
"""
import os
import sys
import requests
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.getenv("TARDIS_API_KEY")
if not API_KEY:
sys.exit("Ajoutez TARDIS_API_KEY dans votre fichier .env")
BASE = "https://api.tardis.dev/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
SESSION = requests.Session()
SESSION.headers.update(HEADERS)
def list_channels(exchange: str, symbol: str, date: str, market_type: str):
"""Renvoie la liste des canaux disponibles (book, trades, etc.)."""
url = f"{BASE}/datasets/{exchange}_{market_type}/{symbol}/{date}"
r = SESSION.get(url, timeout=30)
r.raise_for_status()
return r.json()["paths"]
def download_file(url: str, dest: str):
"""Télécharge un fichier gzippé en streaming pour économiser la RAM."""
r = SESSION.get(url, stream=True, timeout=60)
r.raise_for_status()
total = int(r.headers.get("Content-Length", 0))
downloaded = 0
with open(dest, "wb") as f:
for chunk in r.iter_content(chunk_size=1 << 20):
f.write(chunk)
downloaded += len(chunk)
mb = downloaded / (1024 * 1024)
print(f"OK : {dest} ({mb:.1f} Mo)")
if __name__ == "__main__":
date = "2025-03-15"
exchange = "binance"
symbol = "BTCUSDT"
for market in ("spot", "futures"):
print(f"=== {exchange} {market} {symbol} {date} ===")
paths = list_channels(exchange, symbol, date, market)
# On ne prend que depth_snapshot_25 et trades pour rester léger
wanted = [p for p in paths if "depth_snapshot_25" in p or "/trades/" in p]
for path in wanted:
filename = os.path.basename(path)
if os.path.exists(filename):
print(f"Déjà présent : {filename}")
continue
download_file(f"{BASE}{path}", filename)
Capture d'écran attendue dans votre terminal : une série de lignes « OK : binance_spot_BTCUSDT_trades_2025-03-15.csv.gz (48.3 Mo) » etc.
Étape 5 — Implémenter le simulateur de market making
Voici le cœur du backtest : un simulateur événement par événement. Il écoute chaque mise à jour du carnet, positionne ses quotes, puis croise chaque trade historique pour déterminer si un ordre aurait été rempli (et à quel prix). En 3 heures de simulation sur ma machine, j'ai obtenu 11 200 fills virtuels, ce qui donne un intervalle de confiance statistique déjà utilisable.
"""
market_making_simulator.py
Backtest tick-by-tick d'une stratégie de market making symétrique.
Le carnet d'ordres est reconstruit à partir des snapshots L2 diffusés par Tardis.
"""
import gzip
import json
from dataclasses import dataclass, field
from pathlib import Path
from typing import Iterator
import numpy as np
import pandas as pd
@dataclass
class Position:
cash_usdt: float = 0.0
btc: float = 0.0
fills: list = field(default_factory=list)
class MarketMakingSim:
"""Stratégie de market making symétrique avec skew d'inventaire."""
def __init__(
self,
half_spread_bps: float = 6.0,
order_size_btc: float = 0.01,
inventory_limit_btc: float = 0.5,
skew_bps_per_btc: float = 10.0,
fee_bps: float = 1.0,
):
self.half_spread = half_spread_bps / 1e4
self.size = order_size_btc
self.inv_limit = inventory_limit_btc
self.skew = skew_bps_per_btc / 1e4
self.fee = fee_bps / 1e4
self.pos = Position()
self.my_bid = self.my_ask = None
def compute_quotes(self, mid: float):
"""Décale les quotes en fonction de l'inventaire actuel."""
inv_skew = np.clip(self.pos.btc, -1, 1) * self.skew
self.my_bid = mid * (1 - self.half_spread - inv_skew)
self.my_ask = mid * (1 + self.half_spread - inv_skew)
return self.my_bid, self.my_ask
def on_trade(self, price: float, qty: float, taker_side: str, ts: int):
"""Croise chaque trade historique avec nos quotes."""
fill