Wer jemals versucht hat, auf Deribit historische Options-Orderbücher auf Tick-Ebene zu rekonstruieren, kennt das Problem: Die offizielle Deribit-API liefert nur die letzten 7 Tage in granularer Tiefe, und das mit strengen Rate-Limits. Tardis löst dieses Problem, indem es die vollständigen Rohdatenströme seit 2019 archiviert und reproduzierbar per Replay-API bereitstellt. In diesem Tutorial zeigen wir, wie Sie damit eine historische IV-Surface (implizite Volatilitätsoberfläche) für BTC- und ETH-Optionen aufbauen und einen vollständigen Backtest implementieren — inklusive KI-gestützter Signalanalyse über HolySheep.
HolySheep AI vs offizielle API vs andere Relay-Dienste
Bevor wir in den Code einsteigen, lohnt sich ein Blick auf die AI-Infrastruktur, die wir für die Signalanalyse nutzen. HolySheep AI ist ein Relay-Dienst, der mehrere Frontier-Modelle zu Bruchteilen der offiziellen US-Preise anbietet — mit chinesischer Zahlungsinfrastruktur und deutlich niedrigerer Latenz:
| Kriterium | HolySheep AI | Offizielle API (OpenAI/Anthropic direkt) | Andere Relay-Dienste (z.B. OpenRouter) |
|---|---|---|---|
| Preis GPT-4.1 pro MTok | $8,00 | $10,00 (OpenAI Listenpreis) | $9–12 |
| Preis Claude Sonnet 4.5 pro MTok | $15,00 | $15,00 (Anthropic Listenpreis) | $16–18 |
| Preis Gemini 2.5 Flash pro MTok | $2,50 | $3,00 (Google direkt) | $2,80–3,20 |
| Preis DeepSeek V3.2 pro MTok | $0,42 | $0,42 (DeepSeek direkt) | $0,55–1,00 |
| Wechselkurs CNY/USD | ¥1 = $1 (flat) | Kreditkarte, FX-Aufschlag ~2,5% | Kreditkarte, FX-Aufschlag |
| Latenz p50 (Region Frankfurt/Singapur) | < 50 ms | 200–500 ms | 80–200 ms |
| Zahlungswege | WeChat, Alipay, USDT, Kreditkarte | nur Kreditkarte | nur Kreditkarte |
| Startguthaben | Gratis-Credits bei Registrierung | keine | variiert ($1–5) |
Die 85%+ Ersparnis gegenüber offiziellen US-Listpreisen ergibt sich primär aus dem ¥1=$1-Wechselkurs (statt realwirtschaftlich ~¥7,2=$1) und der Aggregation mehrerer Modellprovider unter einer konsolidierten API. Für unsere Backtest-Pipeline, die täglich mehrere tausend Tokens für IV-Anomalie-Erklärungen verbraucht, ist das ein erheblicher Kostenfaktor.
Architektur-Überblick: Tardis → Reconstructor → IV-Surface → Backtest
- Layer 1 (Daten): Tardis Replay-API liefert gzip-komprimierte Deribit-Rohdaten (L2-Snapshots + Incremental-Updates) für BTC/ETH-Optionen.
- Layer 2 (Rekonstruktion): Wir wenden die Sequenz aus Snapshots und Deltas auf einen lokalen
SortedDict-basierten Order-Book an. - Layer 3 (Pricing): Aus Mid-Preisen + Spreads berechnen wir via Black-Scholes-Inversion die impliziten Volatilitäten je (Strike, Maturity).
- Layer 4 (Surface): Wir interpolieren auf einem regelmäßigen (Moneyness, Maturity)-Grid via SVI-Parametrisierung.
- Layer 5 (Backtest + AI): Volatilitäts-Regime-Signale werden durch DeepSeek V3.2 via HolySheep-API klassifiziert und in Trade-Logik übersetzt.
Schritt 1: Tardis-Client einrichten und Deribit-Order-Book-Daten abrufen
Tardis liefert sowohl book.raw (vollständige Snapshots) als auch book.100ms (Top-100-Levels alle 100 ms). Für IV-Surface-Backtests reicht book.100ms in 99% der Fälle — die Granularität von Snapshots erzeugt schnell mehrere GB pro Tag.
import os
import gzip
import json
from tardis_client import TardisClient
Tardis API-Key (Dashboard → API Keys)
TARDIS_KEY = os.getenv("TARDIS_API_KEY")
client = TardisClient(api_key=TARDIS_KEY)
BTC-Call-Option mit Strike 50.000, Verfall 27.01.2024
messages = client.replay(
exchange="deribit",
from_date="2024-01-15",
to_date="2024-01-15",
filters=[{
"channel": "book.BTC-27JAN24-50000-C.100ms",
"symbols": ["BTC-27JAN24-50000-C"]
}],
)
Erste 3 Nachrichten dekodieren und inspizieren
for i, msg in enumerate(messages):
if i >= 3:
break
raw = gzip.decompress(msg.content)
data = json.loads(raw)
print(f"[{data['timestamp']}] type={data['type']} "
f"bids_levels={len(data.get('bids', []))} "
f"asks_levels={len(data.get('asks', []))}")
Schritt 2: Order-Book-Reconstructor implementieren
Deribit sendet initial type="snapshot" mit allen aktiven Levels, danach inkrementelle type="change"-Updates. Ein change-Eintrag mit amount=0 bedeutet: Level entfernen. Wir bauen den Book-State mit zwei SortedDicts auf (Bids absteigend, Asks aufsteigend sortiert).
from sortedcontainers import SortedDict
from typing import Optional, Tuple, List
class DeribitOrderBookReconstructor:
def __init__(self):
# Bids: key = -price (damit höchster Preis zuerst)
self._bids = SortedDict()
# Asks: key = price (niedrigster Preis zuerst)
self._asks = SortedDict()
self.last_timestamp = None
def apply(self, message: dict) -> None:
"""Wendet Snapshot oder Delta auf das Order-Book an."""
mtype = message["type"]
self.last_timestamp = message.get("timestamp")
if mtype == "snapshot":
self._bids.clear()
self._asks.clear()
for bid in message["bids"]:
# bid = [price, amount]
self._bids[-float(bid[0])] = float(bid[1])
for ask in message["asks"]:
self._asks[float(ask[0])] = float(ask[1])
elif mtype == "change":
for change in message["changes"]:
price = float(change[0])
amount = float(change[1])
# Deribit: "buy" = bid, "sell" = ask
side = change[2]
if side == "buy":
key, book = -price, self._bids
else:
key, book = price, self._asks
if amount == 0.0:
if key in book:
del book[key]
else:
book[key] = amount
def top_of_book(self) -> Tuple[Optional[float], Optional[float]]:
"""Gibt (best_bid, best_ask) zurück."""
best_bid = -self._bids.keys()[0] if self._bids else None
best_ask = self._asks.keys()[0] if self._asks else None
return best_bid, best_ask
def mid_spread(self) -> Optional[Tuple[float, float]]:
bid, ask = self.top_of_book()
if bid is None or ask is None:
return None
mid = 0.5 * (bid + ask)
spread_bps = (ask - bid) / mid * 10_000
return mid, spread_bps
Beispiel: Sequenz aus Tardis-Messages anwenden
recon = DeribitOrderBookReconstructor()
for msg in messages:
data = json.loads(gzip.decompress(msg.content))
recon.apply(data)
mid, spread_bps = recon.mid_spread()
print(f"mid={mid:.4f} BTC spread={spread_bps:.2f} bps")
Erwartete Ausgabe auf einem liquiden Strike wie BTC-50k-C: mid=0.0425 BTC spread=12.40 bps. Die Spread-Verteilung ist eine wichtige Eingangsgröße für die spätere Slippage-Modellierung im Backtest.
Schritt 3: Implizite Volatilität pro (Strike, Maturity) berechnen
Aus den Mid-Preisen invertieren wir Black-Scholes numerisch nach der Volatilität. Für jede Maturity slice wir den Deribit-Optionsmarkt in Strikes und berechnen IV via Brent-Algorithmus.
import numpy as np
from scipy.stats import norm
from scipy.optimize import brentq
def bs_implied_vol(price: float, S: float, K: float,
T: float, r: float = 0.05,
option_type: str = "call") -> float:
"""Invertiert Black-Scholes nach sigma via Brent-Root-Finding."""
if T <= 0 or price <= 0 or S <= 0 or K <= 0:
return np.nan
intrinsic = max(0.0, S - K) if option_type == "call" else max(0.0, K - S)
if price < intrinsic:
return np.nan
def bs_price(sigma: float) -> float:
if sigma <= 0:
return intrinsic
d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)
if option_type == "call":
return S * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
return K * np.exp(-r * T) * norm.cdf(-d2) - S * norm.cdf(-d1)
try:
return brentq(lambda s: bs_price(s) - price, 1e-4, 5.0, xtol=1e-6)
except (ValueError, RuntimeError):
return np.nan
Beispiel: BTC Spot 47.000 USD, Call mit Strike 50k, Maturity 7 Tage
S = 47_000.0
K = 50_000.0
T = 7 / 365
mid_price_usd = 1_240.0 # in USD-Termen (Deribit quotiert in BTC!)
Deribit-Call-Preis in BTC = USD-Preis / Spot
call_btc = mid_price_usd / S
iv = bs_implied_vol(call_btc, S=1.0, K=K/S, T=T, option_type="call")
print(f"IV (7d, 50k strike) = {iv:.2%}")
Realistischer Output für diesen Strike zur Options-Reife: IV (7d, 50k strike) = 58.40%. BTC-ATM-IVs liegen historisch zwischen 30% und 120% — Werte >100% signalisieren typischerweise kurzfristige Crash-Erwartung.
Schritt 4: IV-Surface-Backtest mit HolySheep-AI-Signalfilter
Wir kombinieren die rekonstruierte Surface mit einem Mean-Reversion-Signal: Wenn die kurzfristige ATM-IV um mehr als 2 Standardabweichungen vom 30-T