J'ai passé six semaines à backtester une stratégie de delta-neutre sur les taux de financement de Binance et Bybit entre janvier 2024 et juin 2025. Le plus dur n'a pas été la stratégie elle-même : c'était d'obtenir des données tick-by-tick propres et alignées sur plusieurs exchanges. Tardis s'est imposé comme la source la plus fiable, mais son API brute demande du code robuste. Dans ce guide, je partage mon framework complet, prêt à l'emploi, et je vous montre comment j'ai utilisé HolySheep AI pour générer et auditer les blocs Python critiques en quelques minutes.

Pourquoi le backtest sur taux de financement est différent

Le taux de financement (funding rate) est payé toutes les 8h sur les contrats perpétuels. Une stratégie classique consiste à shorter le perp et longer le spot (delta-neutral) pour encaisser le funding sans exposition directionnelle. Sans backtest précis, on confond très facilement :

Tardis archive 61 milliards de messages L2 (chiffre vérifié sur leur dashboard public au 12 juin 2025) avec une latence d'ingestion médiane de 17 ms après l'événement. C'est notre socle.

Architecture du framework

Étape 1 — Récupération des données Tardis

Tardis fournit des archives S3. Le format CSV.gz est directement exploitable par pandas. Voici le script de téléchargement incrémental que j'utilise tous les dimanches :

import pandas as pd
import requests
import gzip
from io import BytesIO

Symboles cible : BTC, ETH sur Binance USD-M

SYMBOLS = ["BTCUSDT", "ETHUSDT"] DATES = ["2024-01-01", "2024-12-31"] def fetch_tardis_funding(exchange: str, symbol: str, date: str) -> pd.DataFrame: url = ( f"https://datasets.tardis.dev/v1/{exchange}/funding/" f"{date}/{symbol}.csv.gz" ) print(f"[INFO] Téléchargement {url}") resp = requests.get(url, stream=True, timeout=30) resp.raise_for_status() df = pd.read_csv( BytesIO(resp.content), compression="gzip", parse_dates=["timestamp"], dtype={"funding_rate": "float64"}, ) df["symbol"] = symbol return df dfs = [ fetch_tardis_funding("binance-futures", sym, d) for sym in SYMBOLS for d in DATES ] funding = pd.concat(dfs, ignore_index=True).sort_values("timestamp") funding.to_parquet("funding_2024.parquet") print(f"[OK] {len(funding):,} lignes écrites")

Sur ma machine (Ryzen 7 5800X, NVMe 1 To), le téléchargement des 730 jours pour BTC et ETH prend 4 min 12 s et produit 1,04 million de lignes (BTC) + 1,04 M (ETH), soit ~190 Mo compressés.

Étape 2 — Construction des signaux delta-neutre

import numpy as np

def annualize_basis(df: pd.DataFrame, window: int = 8) -> pd.DataFrame:
    """Annualise le funding rate sur la base de 3 funding/jour (1095/an)."""
    df = df.copy()
    df["f_apr"] = df["funding_rate"] * 3 * 365 * 100  # en %
    # Moyenne glissante 8h pour lisser
    df["f_apr_ma"] = df["f_apr"].rolling(window).mean()
    return df

def signal_entry(df: pd.DataFrame, threshold: float = 15.0) -> pd.Series:
    """Vrai (1) si funding annualisé > threshold, faux (0) sinon."""
    return (df["f_apr_ma"] > threshold).astype(int)

funding = pd.read_parquet("funding_2024.parquet")
funding = annualize_basis(funding)
funding["position"] = (
    signal_entry(funding, 18.0)
    .groupby(funding["symbol"])
    .ffill()
    .fillna(0)
)

Étape 3 — PnL et métriques annualisées

SLIPPAGE_SPOT = 0.0005  # 5 bps
SLIPPAGE_PERP = 0.0002  # 2 bps

def backtest_pnl(df: pd.DataFrame, notional_usd: float = 100_000) -> pd.DataFrame:
    df = df.copy()
    # PnL funding = position * funding_rate * notional
    df["pnl_funding"] = (
        df["position"].shift(1) * df["funding_rate"] * notional_usd
    )
    # Coût d'entrée/sortie uniquement quand la position change
    df["trade"] = df