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:

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:

Voraussetzungen und Installation

Systemanforderungen

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:

✗ Nicht geeignet für:

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:

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:

  1. Datenbeschaffung via Tardis Machine API
  2. Lokale回放 mit Python-Streaming
  3. 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

👉 Registrieren Sie sich bei HolySheep AI — Startguthaben inklusive