Schema · chains · v1.6.0

Know it before you buy it.

This is the exact Parquet schema you receive — generated from the definition the collector writes against, so it cannot drift from the files themselves. Null rates are measured from a real recent file, not estimated.

23 columns
v1.6.0 schema version
Parquet · zstd format
ColumnTypeNull Null rateZero rateNote
timestamp timestamp[us, tz=UTC] required 0.0% snapshot instant, UTC
underlying string required 0.0%
instrument string required 0.0% Deribit instrument name, e.g. ETH-25DEC26-1800-C
strike double required 0.0% 0.0%
expiry timestamp[us, tz=UTC] required 0.0% explicit column — never parse it out of the instrument name. Expired contracts persist in the chain briefly after expiry (up to ~10 min observed), so filter expiry > timestamp
type string required 0.0%
mark_iv double nullable 0.0% 0.0% decimal, not percent: 0.65 means 65%
bid_iv double nullable 25.93% 0.0% null where no IV solves — mostly deep ITM rows WITH a live bid, below intrinsic; not an illiquidity marker
ask_iv double nullable 0.33% 0.0% null where no IV solves for the ask side
mark_price double nullable 0.0% 6.13% 0 where the exchange published no mark
bid_price double nullable 0.0% 7.56% 0 means no bid at that instant — not null. Filter > 0
ask_price double nullable 0.0% 0.32% 0 means no ask at that instant — not null. Filter > 0
underlying_price double nullable 0.0% 0.0%
open_interest double nullable 0.0% 20.55%
volume_24h double nullable 0.0% 62.8%
delta double nullable 0.05% 1.4% exchange-published, not re-derived by us
gamma double nullable 0.05% 9.84%
vega double nullable 0.05% 1.63%
theta double nullable 0.05% 1.46%
rho double nullable 0.05% 1.77%
source string required 0.0% capture provenance. Live capture is live_ws, with occasional live_rest snapshots when the socket stalls (~0.01% of rows); reconstructed rows are modeled_surface
settlement_currency string nullable 0.0% currency Greeks and mark_price are expressed in
quote_currency string nullable 0.0% currency strikes are quoted in

Null rates measured from 2026-09-09.parquet (761,124 rows).

Conventions worth knowing

Units

Implied volatilities are decimals, not percentages — 0.65 means 65%. Greeks are decimals too: delta = 0.5. Both are the exchange's own published values, captured at the snapshot instant and never re-derived by us.

Zero is not null — read this one

Deribit publishes 0, not null, when nothing is quoted at that instant. We pass it through unchanged rather than normalising it, because collapsing the two would destroy a real distinction and silently change data customers have already reconciled.

So a price column can read as fully populated while a material share of its rows carry no market. Filter bid_price > 0 and ask_price > 0 before computing a mid — a naive (bid+ask)/2 is wrong on roughly 9% of rows, and rows that look like crossed quotes are almost always an unquoted side rather than bad data. The zero-rate column above is measured, not estimated.

Nulls mean something rarer

A null means the field was absent from the exchange message altogether, or the row pre-dates the column's introduction. On the IV columns it also marks strikes the exchange quoted no volatility for — which is why bid_iv carries a much higher null rate than mark_iv. We never interpolate a value nobody quoted.

Time

All timestamps are UTC, microsecond precision. Files partition on the UTC day. expiry is an explicit column — you never have to parse it out of the instrument name.

Provenance

Every row carries a source tag recording how it was captured, so live capture and the openly-modeled bridge are always distinguishable in your own data. See methodology.

Loading it

pandas
import pandas as pd

df = pd.read_parquet("ETH-2026-07-14.parquet")
df[df.type == "call"].groupby("expiry").mark_iv.mean()
DuckDB
import duckdb

duckdb.sql("""
  SELECT expiry, avg(mark_iv)
  FROM 'ETH-*.parquet'
  WHERE type = 'call'
  GROUP BY expiry
""")