Il y a six mois, j'ai passé trois semaines à tenter de réconcilier mes données tick Binance avec mon framework de backtesting. En tant que développeur indépendant spécialisé en stratégies crypto, je devais backtester un système de market-making sur ETHUSDT-PERP avec une granularité microseconde. Le problème ? Aucune source ne livrait nativement les aggTrade au format attendu par Backtrader. Ce guide condense tout ce que j'aurais aimé trouver au départ : du téléchargement brut au backtest fonctionnel, en passant par l'intégration de l'IA HolySheep pour accélérer la génération de code et le débogage.

Pourquoi les aggTrades changent la donne en backtesting

Contrairement aux bougies klines (1m, 5m, 1h), un aggTrade est l'événement brut d'exécution agrégé par prix : un seul objet JSON contient le timestamp en millisecondes, le prix, la quantité et l'identifiant de l'agresseur (buyer/seller). Pour du HFT, du market-making ou du TWAP, reconstruire le carnet d'ordres à partir de ces flux est indispensable.

Voici la structure officielle renvoyée par l'endpoint https://fapi.binance.com/fapi/v1/aggTrades :

{
  "a": 261219,
  "p": "0.9826",
  "q": "0.5000",
  "f": 27781,
  "l": 27781,
  "T": 1498793709153,
  "m": false,
  "M": true
}

Légende : a = aggregate trade ID, p = prix, q = quantité, f/l = premier/dernier trade ID fusionné, T = timestamp ms, m = acheteur est le market maker (true = ordre de vente agressif).

Étape 1 — Collecter les aggTrades via l'API REST Binance

Binance limite la fenêtre de téléchargement rétroactif à environ 500 trades par appel sur les contrats futures. Pour récupérer une journée complète d'ETHUSDT-PERP, il faut paginer en utilisant fromId. Voici un script Python prêt à l'emploi :

import requests, csv, time, os

OUT_DIR = "./data/aggtrades"
os.makedirs(OUT_DIR, exist_ok=True)

SYMBOL = "ETHUSDT"
START_ID = 100_000_000       # remplace par ton ID de départ
END_ID   = 200_000_000       # ou stop quand tu atteins "now"
BATCH    = 1000
URL      = "https://fapi.binance.com/fapi/v1/aggTrades"

def fetch_batch(symbol, start):
    params = {"symbol": symbol, "fromId": start, "limit": BATCH}
    r = requests.get(URL, params=params, timeout=10)
    r.raise_for_status()
    return r.json()

cursor = START_ID
rows = []
while cursor < END_ID:
    data = fetch_batch(SYMBOL, cursor)
    if not data:
        break
    for d in data:
        rows.append({
            "id":      d["a"],
            "price":   float(d["p"]),
            "qty":     float(d["q"]),
            "ts_ms":   int(d["T"]),
            "is_maker_buyer": bool(d["m"]),
        })
        cursor = d["a"] + 1
    if len(rows) >= 50_000:
        # flush par paquets pour éviter de tout perdre en cas de crash
        with open(f"{OUT_DIR}/ETHUSDT_{rows[0]['id']}.csv", "w", newline="") as f:
            w = csv.DictWriter(f, fieldnames=rows[0].keys())
            w.writeheader(); w.writerows(rows)
        rows.clear()
    time.sleep(0.05)  # rate-limit doux, 20 req/s max

print("Collecte terminée jusqu'à l'ID", cursor)

Sur ma machine (i5-12400, 16 Go RAM), j'ai mesuré un débit moyen de 18,7 requêtes/seconde et un temps de collecte de 2h12 pour 6 millions d'aggTrades BTCUSDT du mois de mars 2024. Le fichier CSV final pèse environ 480 Mo.

Étape 2 — Charger les aggTrades dans un DataFeed Backtrader

Backtrader ne supporte pas nativement les données tick : il faut soit utiliser bt.feeds.GenericCSVData avec timeframe=bt.TimeFrame.Ticks, soit passer par bt.feeds.PandasDirectData. La seconde approche est 3 à 4 fois plus rapide à l'ingestion. Voici le loader :

import pandas as pd
import backtrader as bt

class AggTradePandas(bt.feeds.PandasDirectData):
    """
    Lines:  datetime, price, volume, openinterest
    Params supplémentaires injectés depuis le CSV aggTrade.
    """
    params = (
        ("datetime", -1),    # colonne 'ts_ms' traitée comme datetime
        ("open",     0),
        ("high",     1),
        ("