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.47% 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.06% 0.0% null where no IV solves for the ask side
mark_price double nullable 0.0% 4.01% 0 where the exchange published no mark
bid_price double nullable 0.0% 5.41% 0 means no bid at that instant - not null. Filter > 0
ask_price double nullable 0.0% 0.06% 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% 21.76%
volume_24h double nullable 0.0% 59.76%
delta double nullable 0.01% 1.14% exchange-published, not re-derived by us
gamma double nullable 0.01% 5.33%
vega double nullable 0.01% 1.63%
theta double nullable 0.01% 1.27%
rho double nullable 0.01% 1.78%
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-10-24.parquet (1,046,338 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.

Deep ITM marks can sit below intrinsic

On a small share of live-captured rows the exchange's mark_price is marginally below the option's intrinsic value computed from the underlying_price reported in the same message. Measured across 32,441,980 rows on the 28 days where it occurs: 11,776 rows, 0.036%, of which 11,774 are puts. It concentrates in very illiquid deep in-the-money, long-dated strikes, where a mark priced moments earlier is paired with a forward that has since moved - the mark is consistent with a forward about 1.7% higher.

mark_iv is the sound field on those rows: re-pricing at the row's own stored IV clears intrinsic on 11,776 of 11,776. We do not rewrite the exchange's mark, for the same reason we pass zeros through - it is an observation, and correcting it would put our archive at odds with Deribit's own record, which is what customers reconcile against. If you need a no-arbitrage mark on deep ITM strikes, derive it from mark_iv rather than reading mark_price.

This is distinct from the 2021-2024 historical archive, where mark_price was our derivation from vendor quotes and was wrong. That was repaired on 2026-10-08.

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
""")