Skip to content

Commit 76b0a94

Browse files
committed
Add Deribit test
1 parent 4364877 commit 76b0a94

5 files changed

Lines changed: 191 additions & 3 deletions

File tree

quantflow/data/deribit.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,8 @@ async def volatility_surface_loader(
171171
perp_bid_ask: tuple[Any, Any] | None = None
172172
for entry in futures:
173173
name = entry["instrument_name"]
174-
meta = instrument_map[name]
174+
if (meta := instrument_map.get(name)) is None:
175+
continue
175176
if (
176177
meta["settlement_period"] == "perpetual"
177178
and (bid_ := entry["bid_price"])
@@ -187,7 +188,8 @@ async def volatility_surface_loader(
187188
bid_, ask_ = perp_bid_ask
188189
if bid_ and ask_:
189190
name = entry["instrument_name"]
190-
meta = instrument_map[name]
191+
if (meta := instrument_map.get(name)) is None:
192+
continue
191193
tick_size = to_decimal(meta["tick_size"])
192194
min_tick_size = min(min_tick_size, tick_size)
193195
bid = round_to_step(bid_, tick_size)
@@ -220,7 +222,8 @@ async def volatility_surface_loader(
220222
for entry in options:
221223
if (bid_ := entry["bid_price"]) and (ask_ := entry["ask_price"]):
222224
name = entry["instrument_name"]
223-
meta = instrument_map[name]
225+
if (meta := instrument_map.get(name)) is None:
226+
continue
224227
tick_size = to_decimal(meta["tick_size"])
225228
min_tick_size = min(min_tick_size, tick_size)
226229
loader.add_option(
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[
2+
{
3+
"instrument_name": "BTC-PERPETUAL",
4+
"bid_price": 68678.0,
5+
"ask_price": 68680.0,
6+
"open_interest": 937968230,
7+
"volume_usd": 1500000000
8+
},
9+
{
10+
"instrument_name": "BTC-25APR26",
11+
"bid_price": 68900.0,
12+
"ask_price": 68910.0,
13+
"open_interest": 85568390,
14+
"volume_usd": 250000000
15+
},
16+
{
17+
"instrument_name": "BTC-GHOST-26",
18+
"bid_price": 69000.0,
19+
"ask_price": 69010.0,
20+
"open_interest": 1000,
21+
"volume_usd": 100000
22+
}
23+
]
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
[
2+
{
3+
"instrument_name": "BTC-PERPETUAL",
4+
"kind": "future",
5+
"settlement_period": "perpetual",
6+
"tick_size": 0.5,
7+
"expiration_timestamp": 32503680000000,
8+
"is_active": true
9+
},
10+
{
11+
"instrument_name": "BTC-25APR26",
12+
"kind": "future",
13+
"settlement_period": "month",
14+
"tick_size": 0.5,
15+
"expiration_timestamp": 1745568000000,
16+
"is_active": true
17+
},
18+
{
19+
"instrument_name": "BTC-25APR26-70000-C",
20+
"kind": "option",
21+
"option_type": "call",
22+
"settlement_period": "month",
23+
"tick_size": 0.0001,
24+
"strike": 70000,
25+
"expiration_timestamp": 1745568000000,
26+
"is_active": true
27+
},
28+
{
29+
"instrument_name": "BTC-25APR26-70000-P",
30+
"kind": "option",
31+
"option_type": "put",
32+
"settlement_period": "month",
33+
"tick_size": 0.0001,
34+
"strike": 70000,
35+
"expiration_timestamp": 1745568000000,
36+
"is_active": true
37+
},
38+
{
39+
"instrument_name": "BTC-25APR26-75000-C",
40+
"kind": "option",
41+
"option_type": "call",
42+
"settlement_period": "month",
43+
"tick_size": 0.0001,
44+
"strike": 75000,
45+
"expiration_timestamp": 1745568000000,
46+
"is_active": true
47+
}
48+
]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[
2+
{
3+
"instrument_name": "BTC-25APR26-70000-C",
4+
"bid_price": 0.05,
5+
"ask_price": 0.06,
6+
"open_interest": 100,
7+
"volume_usd": 500000
8+
},
9+
{
10+
"instrument_name": "BTC-25APR26-70000-P",
11+
"bid_price": 0.04,
12+
"ask_price": 0.05,
13+
"open_interest": 80,
14+
"volume_usd": 400000
15+
},
16+
{
17+
"instrument_name": "BTC-25APR26-75000-C",
18+
"bid_price": 0.02,
19+
"ask_price": 0.03,
20+
"open_interest": 60,
21+
"volume_usd": 200000
22+
},
23+
{
24+
"instrument_name": "BTC-10APR26-67500-P",
25+
"bid_price": 0.01,
26+
"ask_price": 0.02,
27+
"open_interest": 10,
28+
"volume_usd": 50000
29+
}
30+
]
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"""Tests for the Deribit volatility surface loader."""
2+
3+
from __future__ import annotations
4+
5+
import json
6+
from pathlib import Path
7+
from typing import Any, AsyncIterator
8+
from unittest.mock import AsyncMock, patch
9+
10+
import pytest
11+
12+
from quantflow.data.deribit import Deribit
13+
14+
FIXTURES = Path(__file__).parent / "fixtures"
15+
16+
17+
def load_fixture(name: str) -> list[dict]:
18+
return json.loads((FIXTURES / name).read_text())
19+
20+
21+
@pytest.fixture
22+
def futures() -> list[dict]:
23+
return load_fixture("deribit_futures.json")
24+
25+
26+
@pytest.fixture
27+
def options() -> list[dict]:
28+
return load_fixture("deribit_options.json")
29+
30+
31+
@pytest.fixture
32+
def instruments() -> list[dict]:
33+
return load_fixture("deribit_instruments.json")
34+
35+
36+
@pytest.fixture
37+
async def deribit_cli(
38+
futures: list[dict], options: list[dict], instruments: list[dict]
39+
) -> AsyncIterator[Deribit]:
40+
async def get_path(path: str, **kw: Any) -> list[dict]:
41+
params = kw.get("params", {})
42+
if path == "public/get_instruments":
43+
return instruments
44+
if path == "public/get_book_summary_by_currency":
45+
return futures if params.get("kind") == "future" else options
46+
raise ValueError(f"Unexpected path: {path}")
47+
48+
with patch.object(Deribit, "get_path", AsyncMock(side_effect=get_path)):
49+
async with Deribit() as cli:
50+
yield cli
51+
52+
53+
async def test_loader_loads_known_options(deribit_cli: Deribit) -> None:
54+
"""Options present in both book summary and instruments are loaded."""
55+
loader = await deribit_cli.volatility_surface_loader("btc")
56+
surface = loader.surface()
57+
# fixture has 2 strikes (70000 C+P, 75000 C) all on one maturity
58+
total_strikes = sum(len(m.strikes) for m in surface.maturities)
59+
assert total_strikes == 2
60+
61+
62+
async def test_loader_skips_option_missing_from_instruments(
63+
deribit_cli: Deribit, options: list[dict]
64+
) -> None:
65+
"""Options absent from the instruments list are silently skipped."""
66+
ghost = "BTC-10APR26-67500-P"
67+
assert any(o["instrument_name"] == ghost for o in options)
68+
69+
loader = await deribit_cli.volatility_surface_loader("btc")
70+
surface = loader.surface()
71+
all_strikes = {
72+
strike.strike for mat in surface.maturities for strike in mat.strikes
73+
}
74+
assert 67500 not in all_strikes
75+
76+
77+
async def test_loader_skips_future_missing_from_instruments(
78+
deribit_cli: Deribit, futures: list[dict]
79+
) -> None:
80+
"""Futures absent from the instruments list are silently skipped."""
81+
assert any(f["instrument_name"] == "BTC-GHOST-26" for f in futures)
82+
83+
loader = await deribit_cli.volatility_surface_loader("btc")
84+
assert loader is not None

0 commit comments

Comments
 (0)