Jeudi 13 juin 2024, 14h32 UTC. Mon script cron lance la mise à jour quotidienne de la surface IV BTC. Et là, écran rouge dans le conteneur Docker :

ConnectionError: HTTPSConnectionPool(host='api.tardis.dev', port=443):
  Max retries exceeded with url: /v1/replay?exchange=deribit&from=2024-06-13
  Caused by NewConnectionError: Failed to establish a new connection:
  [Errno 110] Connection timed out

Le lendemain, après bascule du proxy sur une autre région, rebelote :

HTTPError 401: Unauthorized — invalid_api_key
{ "error": "Tardis API key rejected for plan 'free_hobbyist' on Deribit options replay" }

En reprenant le pipeline à zéro — auth Tardis, parsing book snapshot, inversion Black-Scholes, calibration SVI, puis synthèse via API HolySheep — j'ai stabilisé toute la chaîne tick historique → chaîne d'options Deribit → surface IV → mémo interprétatif. Ce guide condense 9 mois d'itérations et toutes les erreurs à éviter. Pour tester la dernière étape gratuitement, S'inscrire ici — vous recevez des crédits de départ sur HolySheep et la facturation se fait en ¥1 = $1 (WeChat / Alipay / CB), avec une latence p50 sous 50 ms.

Pourquoi reconstruire une surface IV en 2026 ?

Architecture du pipeline

  1. Tardis replay → snapshots book Deribit Options (BTC, ETH) tous les 100 ms.
  2. Mid-quote + agrégation minute → dataframe strikes × maturités.
  3. Inversion Black-Scholes-Merton → IV par (K, T).
  4. Calibration SVI paramétrique → 5 paramètres par maturité.
  5. HolySheep API (DeepSeek V3.2) → mémo en français sur la skew, le risque de repricing et la couverture delta recommandée.

Étape 1 — Récupérer les ticks Deribit via Tardis

import asyncio, json
from tardis_client import TardisClient, Channel

TARDIS_KEY = "YOUR_TARDIS_API_KEY"        # à remplacer
tardis = TardisClient(api_key=TARDIS_KEY)

async def collect_deribit_options(date_str: str, underlying="BTC"):
    """Replay du book depth 10 sur toutes les options d'un sous-jacent."""
    out = []
    messages = tardis.replay(
        exchange="deribit",
        from_date=f"{date_str}T00:00:00Z",
        to_date=f"{date_str}T23:59:59Z",
        symbols=[],
        channels=[Channel(name="book", symbols=["options"], depth=10)],
        chunk_size=500,
    )
    async for m in messages:
        if underlying not in m.symbol:
            continue
        bids, asks = m.message["bids"], m.message["asks"]
        if not bids or not asks:
            continue
        mid = (bids[0][0] + asks[0][0]) / 2
        out.append({
            "ts": m.message["timestamp"], "symbol": m.symbol,
            "mid": mid, "spread": asks[0][0] - bids[0][0],
        })
    return out

ticks = asyncio.run(collect_deribit_options("2024-06-15"))
print(f"{len(ticks)} snapshots collectés sur BTC")

Sur mon poste (16 Go RAM, Debian 12, fibre 200 Mbps), cette étape prend entre 6 et 11 minutes pour une journée complète, soit l'équivalent de 38 Go de JSON compressé. La latence réseau observée vers api.tardis.dev se situe entre 87 ms (p50 intra-UE) et 142 ms (p95).

Étape 2 — Inversion de Black-Scholes pour obtenir l'IV

import numpy as np
from scipy.stats import norm
from scipy.optimize import brentq

def bs_price(S, K, T, r, sigma, cp="C"):
    if T <= 0:
        return max(0.0, S - K if cp == "C" else K - S)
    d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)
    return (S * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)) if cp == "C" \
        else (K * np.exp(-r * T) * norm.cdf(-d2) - S * norm.cdf(-d1))

def implied_vol(market, S, K, T, r, cp="C"):
    f = lambda s: bs_price(S, K, T, r, s, cp=cp) - market
    return brentq(f, 1e-6, 5.0, xtol=1e-8, maxiter=200)

Exemple : spot BTC = 65 200 $, strike 67 500 $, maturité 12 j,

prime call = 1 420 $, taux sans risque = 5,10 %.

iv = implied_vol(1420.0, 65_200, 67_500, 12/365, 0.051) print(f"IV = {iv:.4f}") # -> IV =