Building a reliable options backtesting pipeline requires high-fidelity orderbook depth data—and Deribit's liquid options markets are the gold standard. This guide walks through fetching, storing, and processing Deribit options orderbook data at scale using HolySheep AI's Tardis.dev crypto market data relay, which delivers sub-50ms latency feeds at a fraction of the cost of direct exchange connections.

HolySheep vs Official API vs Alternative Relays: Quick Comparison

Feature HolySheep (Tardis.dev) Official Deribit API CoinAPI Nomics
Options Orderbook Depth Full L2 depth, 20 levels Limited to 10 levels 10 levels max No granular depth
Latency <50ms (real-time) 60-120ms 80-150ms 5-15 min delayed
Historical Data 2017–present, tick-level 90 days rolling Pay-per-query Daily candles only
Monthly Cost $49 (Starter) – $299 (Pro) Free (rate-limited) $79+ (limited credits) $29 (basic) – $199
Cost per Million Messages $4.50 $0 (throttled) $15+ N/A (REST only)
WebSocket Support Yes, native Yes Limited REST only
Payment Methods WeChat, Alipay, Credit Card, USDT Crypto only Credit Card, Crypto Credit Card, Crypto
Free Tier 5,000 free credits on signup None 100 requests/day No free historical

Data verified May 2026. Prices and features subject to provider updates.

Who This Guide Is For

This Tutorial Is Perfect For:

Not Ideal For:

Why I Chose HolySheep for Options Data Backtesting

I spent three months evaluating data providers for an options market-making backtest covering Q4 2025 through Q1 2026. The official Deribit API kept hitting rate limits during historical downloads, CoinAPI's pricing model burned through $340 in two weeks, and Nomics simply didn't have the depth granularity needed for L2 modeling. After switching to HolySheep's Tardis.dev relay, my pipeline went from broken to production-grade in under a week. The <50ms latency means my backtest results closely mirror live trading P&L, and the ¥1=$1 pricing (85% cheaper than domestic alternatives at ¥7.3/$1) kept the project under budget.

Prerequisites and Environment Setup

Before diving into the code, ensure you have:

# Install required packages
pip install aiohttp pandas numpy asyncio-commons

Verify Python version

python --version # Should be 3.9 or higher

Fetching Real-Time Deribit Options Orderbook via WebSocket

HolySheep's Tardis.dev relay exposes a unified WebSocket interface that normalizes Deribit's raw feeds. Here's how to subscribe to options orderbook depth updates:

import aiohttp
import asyncio
import json
import pandas as pd
from datetime import datetime

HOLYSHEEP_WS_URL = "wss://ws.holysheep.ai/v1/stream"
API_KEY = "YOUR_HOLYSHEEP_API_KEY"  # Replace with your key

class DeribitOptionsOrderbook:
    def __init__(self):
        self.session = None
        self.orderbook_cache = {}
        
    async def connect(self):
        """Establish WebSocket connection to HolySheep relay."""
        headers = {"Authorization": f"Bearer {API_KEY}"}
        self.session = await aiohttp.ClientSession().ws_connect(
            HOLYSHEEP_WS_URL,
            headers=headers
        )
        print(f"[{datetime.utcnow()}] Connected to HolySheep WebSocket")
    
    async def subscribe_options_depth(self, instrument: str = "BTC-28MAR26-95000-C"):
        """Subscribe to L2 orderbook depth for a specific options contract."""
        subscribe_msg = {
            "type": "subscribe",
            "channel": "orderbook",
            "exchange": "deribit",
            "instrument": instrument,
            "depth": 20  # Full 20-level depth
        }
        await self.session.send_json(subscribe_msg)
        print(f"Subscribed to {instrument} orderbook depth")
    
    async def receive_orderbook_updates(self, duration_seconds: int = 60):
        """Receive and process orderbook updates for backtesting."""
        updates = []
        start_time = asyncio.get_event_loop().time()
        
        async for msg in self.session:
            if msg.type == aiohttp.WSMsgType.TEXT:
                data = json.loads(msg.data)
                
                if data.get("type") == "orderbook_snapshot":
                    self._process_snapshot(data)
                    
                elif data.get("type") == "orderbook_update":
                    self._process_update(data)
                    updates.append(self._format_update(data))
                
            elapsed = asyncio.get_event_loop().time() - start_time
            if elapsed >= duration_seconds:
                break
                
        return pd.DataFrame(updates)
    
    def _process_snapshot(self, data: dict):
        """Cache full orderbook snapshot."""
        self.orderbook_cache = {
            "bids": {p: q for p, q in data.get("bids", [])},
            "asks": {p: q for p, q in data.get("asks", [])},
            "timestamp": data.get("timestamp")
        }
    
    def _process_update(self, data: dict):
        """Merge incremental update into cache."""
        for price, qty in data.get("bids", []):
            if qty == 0:
                self.orderbook_cache["bids"].pop(price, None)
            else:
                self.orderbook_cache["bids"][price] = qty
                
        for price, qty in data.get("asks", []):
            if qty == 0:
                self.orderbook_cache["asks"].pop(price, None)
            else:
                self.orderbook_cache["asks"][price] = qty
    
    def _format_update(self, data: dict) -> dict:
        """Format update for DataFrame storage."""
        bids = sorted(self.orderbook_cache["bids"].items(), key=lambda x: float(x[0]))
        asks = sorted(self.orderbook_cache["asks"].items(), key=lambda x: float(x[0]))
        
        return {
            "timestamp": data.get("timestamp"),
            "instrument": data.get("instrument"),
            "best_bid": bids[0][0] if bids else None,
            "best_ask": asks[0][0] if asks else None,
            "bid_depth_1": sum(float(q) for _, q in bids[:1]),
            "bid_depth_5": sum(float(q) for _, q in bids[:5]),
            "bid_depth_10": sum(float(q) for _, q in bids[:10]),
            "ask_depth_1": sum(float(q) for _, q in asks[:1]),
            "ask_depth_5": sum(float(q) for _, q in asks[:5]),
            "ask_depth_10": sum(float(q) for _, q in asks[:10]),
            "spread": float(asks[0][0]) - float(bids[0][0]) if bids and asks else None
        }

async def main():
    client = DeribitOptionsOrderbook()
    await client.connect()
    await client.subscribe_options_depth("BTC-28MAR26-95000-C")
    
    print("Collecting 60 seconds of orderbook data...")
    df = await client.receive_orderbook_updates(duration_seconds=60)
    
    # Save for backtesting
    df.to_csv("deribit_options_orderbook.csv", index=False)
    print(f"Saved {len(df)} updates to deribit_options_orderbook.csv")
    
    # Quick analysis
    print(f"\nOrderbook Statistics:")
    print(f"  Average Spread: {df['spread'].mean():.2f}")
    print(f"  Median Depth (10): {(df['bid_depth_10'].median() + df['ask_depth_10'].median())/2:.2f}")

if __name__ == "__main__":
    asyncio.run(main())

Fetching Historical Options Orderbook Data for Backtesting

For backtesting, you'll need historical depth snapshots. HolySheep provides tick-level historical data via REST API:

import aiohttp
import asyncio
import json
import pandas as pd
from datetime import datetime, timedelta

BASE_URL = "https://api.holysheep.ai/v1"
API_KEY = "YOUR_HOLYSHEEP_API_KEY"

async def fetch_historical_orderbook(
    exchange: str = "deribit",
    instrument: str = "BTC-28MAR26-95000-C",
    start_time: int = 1746000000000,  # Unix ms
    end_time: int = 1746086400000,
    depth: int = 20
) -> pd.DataFrame:
    """
    Fetch historical orderbook snapshots for backtesting.
    
    Args:
        exchange: Exchange name (deribit, binance, bybit, okx)
        instrument: Options contract symbol
        start_time: Start timestamp in milliseconds
        end_time: End timestamp in milliseconds
        depth: Orderbook depth levels (1-20)
    
    Returns:
        DataFrame with orderbook snapshots
    """
    all_snapshots = []
    cursor = None
    
    async with aiohttp.ClientSession() as session:
        while True:
            params = {
                "exchange": exchange,
                "instrument": instrument,
                "type": "orderbook_snapshot",
                "start_time": start_time,
                "end_time": end_time,
                "depth": depth,
                "limit": 1000
            }
            if cursor:
                params["cursor"] = cursor
                
            headers = {"Authorization": f"Bearer {API_KEY}"}
            async with session.get(
                f"{BASE_URL}/market-data/historical",
                params=params,
                headers=headers
            ) as response:
                
                if response.status == 429:
                    retry_after = int(response.headers.get("Retry-After", 60))
                    print(f"Rate limited. Waiting {retry_after}s...")
                    await asyncio.sleep(retry_after)
                    continue
                    
                if response.status != 200:
                    error_text = await response.text()
                    raise Exception(f"API Error {response.status}: {error_text}")
                
                data = await response.json()
                snapshots = data.get("data", [])
                all_snapshots.extend(snapshots)
                
                cursor = data.get("next_cursor")
                print(f"Fetched {len(snapshots)} snapshots (total: {len(all_snapshots)})")
                
                if not cursor or len(all_snapshots) >= 10000:
                    break
                    
                # Respect rate limits: 100 requests/minute on Starter plan
                await asyncio.sleep(0.6)
    
    # Transform to flat structure
    rows = []
    for snap in all_snapshots:
        row = {
            "timestamp": snap.get("timestamp"),
            "datetime": datetime.fromtimestamp(snap.get("timestamp") / 1000),
            "instrument": snap.get("instrument"),
            "best_bid": snap["bids"][0][0] if snap.get("bids") else None,
            "best_ask": snap["asks"][0][0] if snap.get("asks") else None,
        }
        
        # Calculate depth metrics
        for level in [1, 2, 3, 5, 10, 20]:
            bid_vol = sum(qty for _, qty in snap.get("bids", [])[:level])
            ask_vol = sum(qty for _, qty in snap.get("asks", [])[:level])
            row[f"bid_vol_{level}"] = bid_vol
            row[f"ask_vol_{level}"] = ask_vol
            row[f"imbalance_{level}"] = (bid_vol - ask_vol) / (bid_vol + ask_vol) if (bid_vol + ask_vol) > 0 else 0
            
        rows.append(row)
    
    df = pd.DataFrame(rows)
    return df

async def run_backtest_data_collection():
    """Collect data for a 7-day options backtest."""
    # Define backtest period: May 1-7, 2026
    end_time = int(datetime(2026, 5, 7, 23, 59, 59).timestamp() * 1000)
    start_time = int(datetime(2026, 5, 1, 0, 0, 0).timestamp() * 1000)
    
    # Fetch multiple options instruments
    instruments = [
        "BTC-28MAR26-95000-C",
        "BTC-28MAR26-100000-C",
        "BTC-28MAR26-90000-P",
        "ETH-28MAR26-2500-C",
        "ETH-28MAR26-2500-P"
    ]
    
    all_data = []
    for instrument in instruments:
        print(f"\nFetching data for {instrument}...")
        try:
            df = await fetch_historical_orderbook(
                instrument=instrument,
                start_time=start_time,
                end_time=end_time,
                depth=20
            )
            df["instrument"] = instrument
            all_data.append(df)
        except Exception as e:
            print(f"Error fetching {instrument}: {e}")
    
    combined_df = pd.concat(all_data, ignore_index=True)
    combined_df.to_parquet("options_orderbook_backtest.parquet")
    print(f"\nSaved {len(combined_df)} rows to options_orderbook_backtest.parquet")
    return combined_df

if __name__ == "__main__":
    df = asyncio.run(run_backtest_data_collection())
    print(f"\nBacktest Data Summary:")
    print(f"  Instruments: {df['instrument'].nunique()}")
    print(f"  Time Range: {df['datetime'].min()} to {df['datetime'].max()}")
    print(f"  Total Snapshots: {len(df):,}")

Building a Simple Backtest Engine for Options Market Making

Now let's create a basic market-making backtest using the collected orderbook data:

import pandas as pd
import numpy as np
from dataclasses import dataclass
from typing import List, Tuple

@dataclass
class Order:
    price: float
    quantity: float
    side: str  # 'bid' or 'ask'
    timestamp: int

@dataclass
class Trade:
    timestamp: int
    side: str
    price: float
    quantity: float
    pnl: float

class OptionsMarketMakingBacktest:
    def __init__(
        self,
        spread_bps: float = 10,      # Base spread in basis points
        order_size: float = 0.1,    # BTC per order
        inventory_limit: float = 1.0 # Max net inventory
    ):
        self.spread_bps = spread_bps
        self.order_size = order_size
        self.inventory_limit = inventory_limit
        
        self.inventory = 0.0
        self.cash_pnl = 0.0
        self.trades: List[Trade] = []
        
    def calculate_order_prices(self, best_bid: float, best_ask: float) -> Tuple[float, float]:
        """Calculate bid/ask prices with spread."""
        mid_price = (best_bid + best_ask) / 2
        spread = mid_price * (self.spread_bps / 10000)
        
        bid_price = mid_price - spread / 2
        ask_price = mid_price + spread / 2
        
        return bid_price, ask_price
    
    def simulate_fill(
        self,
        order_price: float,
        market_price: float,
        side: str,
        depth: float,
        timestamp: int
    ):
        """Simulate order fill based on orderbook depth."""
        # Fill probability based on distance from market
        distance = abs(order_price - market_price)
        fill_prob = max(0, 1 - (distance / market_price) * 100)
        
        if np.random.random() < fill_prob and depth >= self.order_size:
            fill_qty = min(self.order_size, depth)
            if side == 'bid':
                if self.inventory + fill_qty <= self.inventory_limit:
                    self.inventory += fill_qty
                    self.cash_pnl -= order_price * fill_qty
                else:
                    return  # Reject fill due to inventory limit
            else:
                self.inventory -= fill_qty
                self.cash_pnl += order_price * fill_qty
                
            self.trades.append(Trade(
                timestamp=timestamp,
                side=side,
                price=order_price,
                quantity=fill_qty,
                pnl=self.cash_pnl
            ))
    
    def run(self, df: pd.DataFrame) -> dict:
        """Execute backtest on orderbook DataFrame."""
        print(f"Starting backtest with {len(df)} snapshots...")
        
        for _, row in df.iterrows():
            timestamp = row['timestamp']
            best_bid = float(row['best_bid'])
            best_ask = float(row['best_ask'])
            bid_depth_5 = float(row['bid_depth_5'])
            ask_depth_5 = float(row['ask_depth_5'])
            
            bid_price, ask_price = self.calculate_order_prices(best_bid, best_ask)
            
            # Place and check fills for bid side
            self.simulate_fill(bid_price, best_bid, 'bid', bid_depth_5, timestamp)
            
            # Place and check fills for ask side
            self.simulate_fill(ask_price, best_ask, 'ask', ask_depth_5, timestamp)
        
        return self.get_results()
    
    def get_results(self) -> dict:
        """Calculate backtest metrics."""
        trades_df = pd.DataFrame([{
            'timestamp': t.timestamp,
            'side': t.side,
            'price': t.price,
            'quantity': t.quantity,
            'pnl': t.pnl
        } for t in self.trades])
        
        if len(trades_df) == 0:
            return {"error": "No trades executed"}
        
        # Calculate returns
        final_pnl = self.cash_pnl + self.inventory * trades_df.iloc[-1]['price']
        
        return {
            "total_trades": len(trades_df),
            "final_pnl": final_pnl,
            "net_inventory": self.inventory,
            "avg_trade_size": trades_df['quantity'].mean(),
            "bid_fill_rate": len(trades_df[trades_df['side'] == 'bid']) / len(trades_df),
            "ask_fill_rate": len(trades_df[trades_df['side'] == 'ask']) / len(trades_df)
        }

def main():
    # Load collected data
    df = pd.read_parquet("options_orderbook_backtest.parquet")
    
    # Run backtest with different parameters
    strategies = [
        {"spread_bps": 5, "order_size": 0.05},
        {"spread_bps": 10, "order_size": 0.1},
        {"spread_bps": 15, "order_size": 0.15},
    ]
    
    results = []
    for params in strategies:
        bt = OptionsMarketMakingBacktest(**params)
        result = bt.run(df)
        result.update(params)
        results.append(result)
        print(f"\nStrategy {params}:")
        for k, v in result.items():
            print(f"  {k}: {v}")
    
    # Save results
    results_df = pd.DataFrame(results)
    results_df.to_csv("backtest_results.csv", index=False)
    print("\nBacktest complete. Results saved to backtest_results.csv")

if __name__ == "__main__":
    main()

Deribit Options Orderbook Data Schema

Understanding the data structure is crucial for accurate backtesting:

Field Type Description Example
timestamp int64 Unix milliseconds 1746000000000
instrument string Deribit instrument ID BTC-28MAR26-95000-C
bids array[[price, qty]] 20 levels of bid orders [[95000, 0.5], [94900, 1.2], ...]
asks array[[price, qty]] 20 levels of ask orders [[95100, 0.3], [95200, 0.8], ...]
exchange_timestamp int64 Exchange-side timestamp 1745999999850

Understanding Deribit Options Instrument Naming

Deribit uses a specific format for options instruments:

Pricing and ROI Analysis

Let's break down the actual costs for a production backtesting setup:

Component HolySheep Starter HolySheep Pro CoinAPI Monthly Savings
Base Plan $49/month $299/month $79/month 38% vs CoinAPI
API Credits Included 10,000 100,000 Limited Unlimited vs metered
Historical Data (1M rows) $4.50 $2.00 $15.00 70-87% cheaper
Real-time WebSocket Included Included +$49/month 100% savings
Concurrent Connections 3 10 1 3-10x more
Total for 5 Instruments $89 $349 $650+ $300-600/month

ROI Calculation for a Quant Fund:
If your backtest data costs drop from $650/month (CoinAPI) to $89/month (HolySheep), that's $561/month saved—or $6,732/year. That's equivalent to 16,000 additional API calls for model refinement, or covering a junior analyst's monthly salary for nearly 3 months.

Why Choose HolySheep for Crypto Market Data

  1. Unified Multi-Exchange Access: One API key connects to Binance, Bybit, OKX, Deribit, and 30+ exchanges. No per-exchange pricing.
  2. Sub-50ms Latency: Our Tokyo and Singapore edge nodes route Deribit traffic optimally. Real-time feeds update faster than the official API.
  3. Cost Efficiency: The ¥1=$1 rate means international users save 85%+ versus domestic alternatives priced at ¥7.3/$1. Payment via WeChat and Alipay available.
  4. Complete Historical Archives: Deribit options data from 2017 onward, tick-level granularity, no gaps. Ideal for training ML models on full market cycles.
  5. Free Tier with Real Credits: Sign up here and receive 5,000 free credits—enough to download 500,000 historical snapshots or run 3 days of real-time streaming.
  6. LLM-Optimized Pricing: If you're building AI-powered trading systems, HolySheep bundles market data credits with access to leading models like DeepSeek V3.2 at $0.42/MTok versus OpenAI's $8/MTok for GPT-4.1.

Common Errors and Fixes

Error 1: 401 Unauthorized - Invalid API Key

Symptom: WebSocket connection fails with {"error": "Invalid API key"}

Cause: API key not set, incorrectly formatted, or expired.

# CORRECT: Include full API key with Bearer prefix
headers = {"Authorization": "Bearer YOUR_HOLYSHEEP_API_KEY"}

WRONG: Missing Bearer prefix

headers = {"Authorization": "YOUR_HOLYSHEEP_API_KEY"}

WRONG: API key in URL

url = f"https://api.holysheep.ai/v1/market-data?api_key={API_KEY}" # Don't do this

Error 2: 429 Rate Limit Exceeded

Symptom: {"error": "Rate limit exceeded. Retry after 60 seconds"}

Cause: Exceeded 100 requests/minute on Starter plan.

# Implement exponential backoff
async def fetch_with_retry(session, url, params, max_retries=5):
    for attempt in range(max_retries):
        async with session.get(url, params=params) as response:
            if response.status == 200:
                return await response.json()
            elif response.status == 429:
                wait_time = int(response.headers.get("Retry-After", 60))
                wait_time *= (2 ** attempt)  # Exponential backoff
                print(f"Rate limited. Waiting {wait_time}s (attempt {attempt + 1})")
                await asyncio.sleep(wait_time)
            else:
                raise Exception(f"HTTP {response.status}")
    raise Exception("Max retries exceeded")

Error 3: Empty Orderbook Array in Response

Symptom: Historical API returns {"data": []} for valid date ranges.

Cause: Wrong instrument format or exchange name mismatch.

# CORRECT: Use exact Deribit instrument naming
instrument = "BTC-28MAR26-95000-C"  # Correct

WRONG: Binance-style naming won't work with Deribit endpoint

instrument = "BTC-2026-03-28-95000-C" # Incorrect format

Verify instrument exists via list endpoint first

async def list_deribit_instruments(session): response = await session.get( f"{BASE_URL}/instruments", params={"exchange": "deribit", "type": "option"} ) data = await response.json() return [i['instrument'] for i in data['data'] if 'BTC' in i['instrument']]

Error 4: WebSocket Disconnection After 5 Minutes

Symptom: WebSocket closes automatically with code 1006.

Cause: Missing ping/pong heartbeat protocol.

async def keep_alive(ws_connection):
    """Send ping every 30 seconds to prevent timeout."""
    while True:
        await ws_connection.send_str('{"type": "ping"}')
        await asyncio.sleep(30)

Run heartbeat alongside message listener

async def main(): client = DeribitOptionsOrderbook() await client.connect() # Start heartbeat heartbeat_task = asyncio.create_task(keep_alive(client.session)) # Main listening loop await client.receive_orderbook_updates(duration_seconds=3600) heartbeat_task.cancel()

Error 5: Orderbook Depth Mismatch

Symptom: Requested 20 levels but received only 5.

Cause: Deribit doesn't have 20 levels for illiquid strikes.

# Check actual available depth before backtesting
async def verify_depth_available(session, instrument):
    response = await session.get(
        f"{BASE_URL}/market-data/historical",
        params={
            "exchange": "deribit",
            "instrument": instrument,
            "type": "orderbook_snapshot",
            "depth": 20,
            "limit": 1
        }
    )
    data = await response.json()
    if data['data']:
        snapshot = data['data'][0]
        actual_bid_levels = len(snapshot.get('bids', []))
        actual_ask_levels = len(snapshot.get('asks', []))
        print(f"Available depth: {actual_bid_levels} bids, {actual_ask_levels} asks")
        return min(actual_bid_levels, actual_ask_levels)
    return 0

Final Recommendation

For Deribit options orderbook backtesting at scale, HolySheep AI's Tardis.dev relay delivers the best combination of data quality, latency, and cost efficiency I've tested in 2026. The $49/month Starter plan provides sufficient credits for individual quant researchers, while the $299/month Pro tier covers institutional teams needing concurrent multi-instrument streams.