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:
- Quantitative researchers building options volatility models who need tick-level orderbook snapshots
- Algo traders backtesting market-making strategies on Deribit's BTC/ETH options
- Data engineers constructing streaming pipelines for crypto derivatives
- Hedge fund teams migrating from legacy data vendors seeking cost-effective alternatives
- Academic researchers studying options microstructure with real historical depth data
Not Ideal For:
- Traders needing only current spot prices (use free websocket streams)
- High-frequency traders requiring sub-millisecond co-location (need direct exchange connectivity)
- Projects requiring data for regulatory reporting (compliance archives differ)
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:
- Python 3.9+ with asyncio support
- HolySheep API key (get yours at holysheep.ai/register)
- pandas, numpy, and aiohttp installed
# 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:
- Underlying: BTC or ETH
- Expiration: DDMMMYY (e.g., 28MAR26)
- Strike: Numeric price in USD
- Type: C (Call) or P (Put)
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
- Unified Multi-Exchange Access: One API key connects to Binance, Bybit, OKX, Deribit, and 30+ exchanges. No per-exchange pricing.
- Sub-50ms Latency: Our Tokyo and Singapore edge nodes route Deribit traffic optimally. Real-time feeds update faster than the official API.
- 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.
- Complete Historical Archives: Deribit options data from 2017 onward, tick-level granularity, no gaps. Ideal for training ML models on full market cycles.
- 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.
- 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.