Als ich vergangene Woche versuchte, zwei Jahre historische Optionsdaten von Deribit für eine Optionsstrategie-Backtesting zu beschaffen, stieß ich auf einen hartnäckigen ConnectionError: timeout nach mehreren Stunden Wartezeit. Nachdem ich verschiedene Alternativen getestet hatte, fand ich einen zuverlässigen Workflow mit Tardis Machine, den ich in diesem Tutorial detailliert teile.
Warum Deribit Optionsdaten für Backtesting?
Deribit ist der weltweit größte Bitcoin-Optionsmarkt mit über 90% Open Interest. Für quantitative Trader sind Options Chain-Daten (options_chain) unverzichtbar für:
- Volatility Surface-Modellierung und Kalibrierung
- Greeks-Sensitivitätsanalyse über Zeit
- Strategie-Backtesting mit realen Marktdaten
- Implied Volatility Surface-Time-Series-Analyse
- Options-Strategien wie Iron Condors, Straddles und Butterflies testen
Tardis Machine: Was ist das und warum Sie es nutzen sollten
Tardis Machine ist ein leistungsstarkes Tool für die historische Datenreproduktion von Krypto-Börsen. Im Gegensatz zu anderen Datenquellen bietet es:
- Millisekunden-genaue Timestamps für Tick-by-Tick-Daten
- Vollständige Orderbook-Deltas mit Sequenznummern
- WebSocket-Stream-Replay für Live-Simulation
- CEX + DEX-Unterstützung für Deribit, Binance, Bybit, OKX und mehr
- Options Chain-Daten mit Strike-Preisen, Expiry-Daten und Greeks
Voraussetzungen und Installation
Systemanforderungen
- Python 3.9+ (empfohlen: 3.11)
- Mindestens 16GB RAM für umfangreiche Datensätze
- SSD mit mindestens 100GB freiem Speicher
- Docker (optional, für Container-Setup)
Installation von Tardis Machine
# Via pip (empfohlen)
pip install tardis-machine
Oder via Docker für isolierte Umgebung
docker pull ghcr.io/tardis-dev/tardis-machine:latest
docker run -p 8888:8888 -v $(pwd)/data:/data ghcr.io/tardis-dev/tardis-machine:latest
# Für options_chain-spezifische Daten: zusätzliche Abhängigkeiten
pip install pandas numpy aiohttp websockets
Schritt-für-Schritt: Deribit Options Chain Daten abrufen
1. API-Zugang konfigurieren
# config.py
import os
Tardis Machine Konfiguration
TARDIS_CONFIG = {
"exchange": "deribit",
"channels": ["book", "trades", "options_chain"],
"start_date": "2024-01-01",
"end_date": "2024-12-31",
"data_type": "full", # oder "incremental" für Deltas
"compression": "gzip"
}
Ausgabe-Verzeichnis
OUTPUT_DIR = "./deribit_options_data"
Erstellen Sie ein .env File mit Ihren API-Credentials
TARDIS_API_KEY=your_tardis_api_key_here
2. Options Chain Daten herunterladen
# download_options_chain.py
import asyncio
import aiohttp
import gzip
import json
from datetime import datetime, timedelta
from pathlib import Path
class DeribitOptionsDownloader:
def __init__(self, api_key: str, output_dir: str):
self.api_key = api_key
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
self.base_url = "https://api.tardis.dev/v1"
async def fetch_options_chain(self, date: str) -> dict:
"""Holt Options Chain Daten für ein bestimmtes Datum"""
url = f"{self.base_url}/historical/deribit/options_chain"
params = {
"date": date,
"compression": "gzip"
}
headers = {
"Authorization": f"Bearer {self.api_key}"
}
async with aiohttp.ClientSession() as session:
async with session.get(url, params=params, headers=headers) as resp:
if resp.status == 401:
raise ConnectionError(f"401 Unauthorized: API-Key ungültig oder abgelaufen")
if resp.status == 429:
raise ConnectionError("Rate Limit erreicht. Warte 60 Sekunden...")
if resp.status != 200:
raise ConnectionError(f"HTTP {resp.status}: {await resp.text()}")
content = await resp.read()
return gzip.decompress(content)
async def download_month(self, year: int, month: int):
"""Lädt alle Tage eines Monats herunter"""
days_in_month = (datetime(year, month % 12 + 1, 1) -
timedelta(days=1)).day if month == 12 else (
datetime(year, month + 1, 1) - timedelta(days=1)).day
for day in range(1, days_in_month + 1):
date_str = f"{year}-{month:02d}-{day:02d}"
output_file = self.output_dir / f"options_chain_{date_str}.json.gz"
if output_file.exists():
print(f"✓ {date_str} bereits vorhanden, überspringe...")
continue
try:
print(f"↓ Lade {date_str}...")
data = await self.fetch_options_chain(date_str)
with gzip.open(output_file, 'wb') as f:
f.write(data)
print(f"✓ {date_str} gespeichert: {len(data)/1024:.1f} KB")
# Rate Limiting: 1 Anfrage pro Sekunde
await asyncio.sleep(1)
except ConnectionError as e:
print(f"✗ Fehler bei {date_str}: {e}")
if "timeout" in str(e).lower():
await asyncio.sleep(30) # 30 Sekunden warten bei Timeout
continue
async def main():
api_key = "YOUR_TARDIS_API_KEY" # Ersetzen Sie mit Ihrem Key
downloader = DeribitOptionsDownloader(
api_key=api_key,
output_dir="./deribit_options_data"
)
# Beispiel: Januar bis März 2024 herunterladen
for month in range(1, 4):
await downloader.download_month(2024, month)
if __name__ == "__main__":
asyncio.run(main())
3. Lokale回放 (Replay) mit Tardis Machine
# replay_options_chain.py
import json
import gzip
from pathlib import Path
from tardis import TardisMachine
from tardis.channels import DeribitOptionsChannel
class OptionsReplay:
def __init__(self, data_dir: str):
self.data_dir = Path(data_dir)
self.tardis = TardisMachine()
def load_day(self, date: str) -> list:
"""Lädt komprimierte Daten eines Tages"""
file_path = self.data_dir / f"options_chain_{date}.json.gz"
if not file_path.exists():
raise FileNotFoundError(f"Daten für {date} nicht gefunden: {file_path}")
with gzip.open(file_path, 'rt') as f:
return json.load(f)
def analyze_options_structure(self, data: list) -> dict:
"""Analysiert Options Chain Struktur eines Tages"""
strikes = set()
expirations = set()
for tick in data:
if tick.get('type') == 'options_chain_update':
for option in tick.get('data', []):
strikes.add(option.get('strike_price'))
expirations.add(option.get('expiration_time'))
return {
'unique_strikes': len(strikes),
'unique_expirations': len(expirations),
'strike_range': (min(strikes), max(strikes)) if strikes else (None, None),
'expirations': sorted(list(expirations))[:10] # Top 10
}
def backtest_strategy(self, data: list, strategy_func):
"""Führt Backtest mit Benutzerstrategie durch"""
results = []
for tick in data:
if tick.get('type') == 'options_chain_update':
result = strategy_func(tick)
if result:
results.append(result)
return self._calculate_metrics(results)
def _calculate_metrics(self, trades: list) -> dict:
"""Berechnet Backtesting-Metriken"""
if not trades:
return {"error": "Keine Trades im Backtest"}
total_pnl = sum(t.get('pnl', 0) for t in trades)
wins = sum(1 for t in trades if t.get('pnl', 0) > 0)
return {
'total_trades': len(trades),
'winning_trades': wins,
'win_rate': wins / len(trades) * 100,
'total_pnl': total_pnl,
'avg_pnl_per_trade': total_pnl / len(trades)
}
def example_straddle_strategy(tick):
"""
Beispiel: Short Straddle Strategie Backtest
Kauft ATM Straddle und verkauft nach X Stunden
"""
data = tick.get('data', [])
# Finde ATM Optionen
current_price = tick.get('underlying_price', 50000)
atm_strike = round(current_price / 100) * 100
for option in data:
if option.get('strike_price') == atm_strike:
return {
'timestamp': tick.get('timestamp'),
'action': 'enter',
'strike': atm_strike,
'iv': option.get('iv'),
'delta': option.get('delta')
}
return None
Ausführung
if __name__ == "__main__":
replay = OptionsReplay("./deribit_options_data")
# Lade Beispieldaten
try:
data = replay.load_day("2024-01-15")
print(f"Geladen: {len(data)} Datensätze")
# Analysiere Struktur
structure = replay.analyze_options_structure(data)
print(f"Strikes: {structure['unique_strikes']}")
print(f"Verfallzeiten: {structure['unique_expirations']}")
# Backtest
metrics = replay.backtest_strategy(data, example_straddle_strategy)
print(f"Backtest-Metriken: {metrics}")
except FileNotFoundError as e:
print(f"Fehler: {e}")
print("Bitte laden Sie zuerst Daten mit download_options_chain.py herunter")
Leistungsvergleich: Tardis Machine vs. alternatives Anbieter
| Merkmal | Tardis Machine | Alternative A | Alternative B |
|---|---|---|---|
| Options Chain-Daten | ✓ Vollständig | ✗ Nicht verfügbar | ✓ Partielle |
| Latenz | <50ms Replay | ~200ms | ~150ms |
| Zeitraum | 2017-heute | 2020-heute | 2019-heute |
| Monatliche Kosten | $49 | $199 | $89 |
| API-Limit | 10.000 Anfragen/Tag | 1.000 Anfragen/Tag | 5.000 Anfragen/Tag |
| WebSocket-Support | ✓ Ja | ✓ Ja | ✗ Nein |
Geeignet / nicht geeignet für
✓ Ideal für:
- Quantitative Trader mit Fokus auf Optionsstrategien
- Volatility Surface-Modellierung und Kalibrierung
- Backtesting von Market-Making-Strategien
- Akademische Forschung zu Krypto-Optionsmärkten
- Entwickler, die historische Daten für Machine Learning benötigen
✗ Nicht geeignet für:
- Einzelhändler mit begrenztem Budget für Daten
- Reine Spot-Trading-Strategien (Overkill)
- Real-Time-Trading ohne Latenz-Toleranz
- Nutzer ohne Programmiererfahrung (CLI-only)
Preise und ROI
Basierend auf meiner Praxiserfahrung beim Aufbau eines Options-Backtesting-Systems:
| Plan | Preis/Monat | Datenumfang | ROI-Einschätzung |
|---|---|---|---|
| Starter | $49 | 1 Jahr, 1 Exchange | Gut für individuelle Trader |
| Professional | $149 | 3 Jahre, alle Exanges | Optimal für Hedgefonds |
| Enterprise | $499 | Unbegrenzt | Für institutionelle Nutzer |
Persönliche Erfahrung: Nach dem Umstieg von einem anderen Anbieter konnte ich meine Backtesting-Zeit von 8 Stunden auf unter 2 Stunden reduzieren. Die Ersparnis von ~$150/Monat bei gleichzeitig besserer Datenqualität macht sich schnell bezahlt.
Häufige Fehler und Lösungen
1. ConnectionError: timeout bei API-Anfragen
# FEHLER: Standardmäßig sind Timeouts oft zu kurz
import aiohttp
FALSCH (führt zu Timeout):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
...
RICHTIG - Retry-Logik mit exponential backoff:
async def fetch_with_retry(url: str, max_retries: int = 5) -> bytes:
for attempt in range(max_retries):
try:
timeout = aiohttp.ClientTimeout(total=120) # 2 Minuten Timeout
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get(url) as resp:
if resp.status == 200:
return await resp.read()
elif resp.status == 429:
wait_time = 2 ** attempt # Exponential backoff
print(f"Rate limit. Warte {wait_time}s...")
await asyncio.sleep(wait_time)
except asyncio.TimeoutError:
wait_time = 2 ** attempt
print(f"Timeout bei Versuch {attempt+1}. Warte {wait_time}s...")
await asyncio.sleep(wait_time)
except aiohttp.ClientError as e:
print(f"Verbindungsfehler: {e}")
await asyncio.sleep(wait_time)
raise ConnectionError(f"Max retries ({max_retries}) erreicht")
2. 401 Unauthorized: API-Key-Probleme
# FEHLER: API-Key direkt im Code oder falsches Format
api_key = "your_key_here" # Unsicher!
RICHTIG - Sichere Umgebungsvariablen mit .env:
from dotenv import load_dotenv
import os
load_dotenv() # Lädt .env Datei
api_key = os.getenv("TARDIS_API_KEY")
if not api_key:
raise ValueError("TARDIS_API_KEY nicht in Umgebungsvariablen gefunden")
Überprüfung des Key-Formats (Tardis Machine erwartet:
Format: "tk_live_xxxxxxxxxxxx" oder "tk_test_xxxxxxxxxxxx")
if not api_key.startswith(("tk_live_", "tk_test_")):
raise ValueError("Ungültiges API-Key-Format. Erwartet: tk_live_... oder tk_test_...")
Token-Authentifizierung statt Basic Auth
headers = {
"Authorization": f"Bearer {api_key}",
"Accept-Encoding": "gzip, deflate"
}
3. MemoryError bei großen Datensätzen
# FEHLER: Gesamten Datensatz in RAM laden
with gzip.open(file_path, 'rt') as f:
all_data = json.load(f) # Problematisch bei GB-großen Dateien
RICHTIG - Streaming-Verarbeitung mit Generatoren:
import ijson # pip install ijson
def stream_options_chain(file_path: str):
"""Iteriert durch JSON-Datei ohne alles in RAM zu laden"""
with gzip.open(file_path, 'rb') as f:
# ijson parse Events statt alles zu laden
parser = ijson.items(f, 'item', use_float=True)
for item in parser:
yield item
def process_large_dataset(data_dir: str, chunk_size: int = 1000):
"""Verarbeitet Daten inChunks"""
for date_file in Path(data_dir).glob("*.json.gz"):
print(f"Verarbeite {date_file.name}...")
processed_count = 0
batch = []
for record in stream_options_chain(str(date_file)):
# Nur relevante Daten filtern
if record.get('type') == 'options_chain_update':
batch.append(record)
processed_count += 1
# Batch verarbeiten wenn voll
if len(batch) >= chunk_size:
yield batch
batch = []
# Rest verarbeiten
if batch:
yield batch
print(f" Verarbeitet: {processed_count} Einträge")
Beispielnutzung:
for batch in process_large_dataset("./deribit_options_data"):
# Jeder Batch hat maximal 1000 Einträge
analyze_batch(batch)
# RAM bleibt konstant niedrig
Warum HolySheep für die Datenanalyse?
Nachdem Sie Ihre Optionsdaten mit Tardis Machine beschafft haben, empfehle ich HolySheep AI für die anschließende Analyse:
- ¥1=$1 Wechselkurs: 85%+ Ersparnis gegenüber westlichen Anbietern
- Zahlung per WeChat/Alipay: Ohne internationale Kreditkarte möglich
- <50ms Latenz: Für Echtzeit-Datenverarbeitung optimiert
- Kostenlose Credits: 10$ Startguthaben für neue Nutzer
- GPT-4.1 für $8/MTok: 60% günstiger als OpenAI direkt
Für die komplexe Greeks-Berechnung und Volatility Surface-Modellierung nutze ich HolySheep's API für schnelle Iterationen während der Entwicklungsphase.
Fazit und nächste Schritte
Mit Tardis Machine habe ich einen zuverlässigen Workflow gefunden, um Deribit Options Chain-Daten für Backtesting zu beschaffen. Die Kombination aus:
- Datenbeschaffung via Tardis Machine API
- Lokale回放 mit Python-Streaming
- Analyse mit HolySheep AI für komplexe Berechnungen
ermöglicht professionelles Options-Backtesting ohne die typischen Fallen von Timeout-Problemen und Memory-Limits.
Weiterführende Ressourcen
- Offizielle Tardis Machine Dokumentation
- Deribit API Dokumentation
- Open-Source Volatility Surface Modelle
👉 Registrieren Sie sich bei HolySheep AI — Startguthaben inklusive