How the gap is reconstructed
2024-10-02 → 2026-04-20 is not real Deribit tape. It is a modeled surface, reconstructed from real trade prints and disclosed as such everywhere it is served. This page is the whole method and the whole scorecard - including the parts that fail. Nothing on it is hand-written.
What is modeled
Every currency has a real range (genuine Deribit tape, no reconstruction) and, for BTC only, a modeled range covering the historical gap. Modeled months are a bonus layer on top of the real archive, not a substitute for it.
| Currency | Real coverage | Modeled coverage |
|---|---|---|
| BTC | 2021-06-01 → 2024-10-01, 2026-04-21 → ongoing | 2024-10-02 → 2026-04-20 |
| ETH | 2026-04-21 → ongoing | none |
| SOL | 2026-04-27 → ongoing | none |
Per-field provenance within a modeled window
| Field | Provenance |
|---|---|
| mark_iv anchors | Real Deribit trade prints - the only ground truth used. |
| volume | Computed from the trade tape (not modeled). |
| open_interest | NULL - unavailable for this window at any defensible price. Disclosed, not estimated. |
| bid / ask | Modeled spread (calibrated spread model), not observed quotes. |
| greeks | Deribit-native computation, applied to the modeled surface. |
ETH / SOL historical samples
Pre-live and sparse - these are samples, not continuous coverage.
| Currency | Range | Note |
|---|---|---|
| ETH | 2024-01-01 → 2026-04-01 | first-of-month days, 10-minute aggregated bars |
| SOL | 2024-04-01 → 2026-04-01 | first-of-month days, 10-minute aggregated bars |
Method
The reconstruction runs in five steps: (1) recover trade-print anchors from the tape; (2) recover the forward via put-call parity; (3) invert Black-76 off that forward to get anchor IVs; (4) fit a global eSSVI surface with a calendar penalty, subject to the complete Gatheral-Jacquier no-arbitrage bounds; (5) apply a calibrated spread model to produce bid/ask around the fitted mark.
- Mingone, A. (2022), "No arbitrage global parametrization for the eSSVI volatility surface", arXiv:2204.00312 - eSSVI parameterization and calendar-spread conditions. †
- Gatheral, J. & Jacquier, A. (2014), "Arbitrage-free SVI volatility surfaces", Corollary 4.1 - the butterfly / no-arbitrage bound the fit is constrained to.
- Alexander, C., Chen, D. & Imeraj, A. (2023), "Crypto quanto and inverse options", Mathematical Finance 33(4), 1005–1043 - the put-call duality underlying the forward recovery step. Deribit's index is non-tradable and settlement uses a 30-minute TWAP, so this inversion is approximate, not exact.
† The Hendriks-Martini calendar conditions used here are sufficient (not the full Mingone necessary-and-sufficient set), per Pasquazzi (2023), "eSSVI Surface Calibration", arXiv:2304.02106.
Accuracy
Measured against a protocol and a set of bars fixed before the run - the hash at the foot of this page is that protocol. Core metrics block release; wing metrics are reported and never block.
Recorded misses (7), none blocking. These are wing metrics - outside the Δ 0.25-0.75, 7-90d core - which the protocol reports and never gates release on. They are not cosmetic: a wing cell that misses its bar has its quotes nulled rather than served, so the effect shows up as absent data, never as a number we did not stand behind.
- wing m1t0 median_abs_iv 0.0414 > 0.0400
- wing m1t0 p90_abs_iv 0.1605 > 0.1000
- wing m2t0 median_abs_iv 0.0938 > 0.0400
- wing m2t0 p90_abs_iv 0.3386 > 0.1000
- wing m2t1 median_abs_iv 0.0499 > 0.0400
- wing m2t1 p90_abs_iv 0.2114 > 0.1000
- wing m2t2 p90_abs_iv 0.1424 > 0.1000
Core (Δ 0.25-0.75, 7-90d) - blocks ship
| Metric | Measured | 95% CI | Bar | Status |
|---|---|---|---|---|
| band_containment | 0.4001 | [0.3427, 0.4457] | 0.75 | FAIL |
| median_abs_iv | 0.0108 | [0.0075, 0.0159] | 0.015 | PASS |
| p90_abs_iv | 0.0358 | [0.0248, 0.0505] | 0.04 | PASS |
| spread_width_median_rel_err | 0.3933 | [0.3383, 0.4461] | 0.4 | PASS |
| recall | 1.0000 | [1.0000, 1.0000] | 0.95 | PASS |
Wing (everything outside core) - reported, never blocks ship
| Metric | Measured | 95% CI | Bar | Status |
|---|---|---|---|---|
| band_containment | 0.3412 | [0.3006, 0.3787] | 0.5 | FAIL |
| median_abs_iv | 0.0272 | [0.0220, 0.0341] | 0.04 | PASS |
| p90_abs_iv | 0.1223 | [0.0961, 0.1594] | 0.1 | FAIL |
| spread_width_median_rel_err | 0.4671 | [0.4217, 0.5116] | 0.6 | PASS |
Wing 9-cell grid (mark_iv, moneyness × tenor)
| Cell | median_abs_err | median CI | p90_abs_err | p90 CI | n_days | n_rows |
|---|---|---|---|---|---|---|
| m0t0 | 0.0217 | [0.0193, 0.0242] | 0.0832 | [0.0701, 0.0962] | 9 | 1470279 |
| m0t1 | 0.0090 | [0.0067, 0.0123] | 0.0278 | [0.0191, 0.0415] | 9 | 376244 |
| m0t2 | 0.0270 | [0.0196, 0.0377] | 0.0785 | [0.0573, 0.1012] | 9 | 330811 |
| m1t0 | 0.0414 | [0.0369, 0.0463] | 0.1605 | [0.1305, 0.2035] | 9 | 640562 |
| m1t1 | 0.0163 | [0.0119, 0.0220] | 0.0552 | [0.0356, 0.0842] | 9 | 1421400 |
| m1t2 | 0.0280 | [0.0216, 0.0367] | 0.0782 | [0.0584, 0.0993] | 9 | 584716 |
| m2t0 | 0.0938 | [0.0499, 0.1504] | 0.3386 | [0.1289, 0.6536] | 8 | 59396 |
| m2t1 | 0.0499 | [0.0364, 0.0683] | 0.2114 | [0.1572, 0.2801] | 9 | 1424130 |
| m2t2 | 0.0379 | [0.0309, 0.0456] | 0.1424 | [0.1127, 0.1788] | 9 | 1781720 |
Worst days (by core median |IV error|)
| Day | Core median |IV err| | Wing median |IV err| | Recall |
|---|---|---|---|
| 2024-12-01 | 0.0297 | 0.0505 | 1.00 |
| 2025-02-01 | 0.0126 | 0.0338 | 1.00 |
| 2025-06-01 | 0.0103 | 0.0280 | 1.00 |
| 2026-02-01 | 0.0085 | 0.0213 | 1.00 |
| 2025-08-01 | 0.0079 | 0.0258 | 1.00 |
Engine counters (summed over scored days)
- snapshots_emitted12900
- snapshots_rejected20
- snapshots_repaired18
- anchors_used25827
- carried_dropped71
null, not zero.
The 2021–2024 archive marks were repaired, and 11.2M were nulled
A paying customer validating the dense BTC archive against a Deribit
replay found mark_price far below intrinsic value on
in-the-money strikes — a put marked 0.0005 BTC that was worth
17.21. He was right, and it was ours: the importer derived
mark_price as the midpoint of the vendor’s bid and
ask, and for illiquid deep-ITM strikes the vendor supplies a bid and
no ask at all, so that “midpoint” collapsed to a
bare token bid. It had been there since the archive was imported.
Scope before the repair, counting only shortfalls over 0.01 BTC so tick noise is excluded: 19.7% of priced rows on 2023-01-01, 4.5% on 2022-06-15, 0.13% on 2024-06-15, and 0.00% on live-collected days. Worst in the early years, improving through 2024, never present in live capture.
Repaired on 2026-10-08 by re-pricing from the vendor’s own
MARK_IV, which it does supply and which was sound:
48.0 million marks re-priced across 1,214 days. Where no
implied volatility survived — the quote was two-sided zero, meaning
no market existed — the mark is now NULL rather than a
guess: 11.2 million rows. Inventing a price there would have
been worse than the defect being fixed.
If you downloaded archive files before 2026-10-08, re-pull them. Those nulls are new, and so are the corrected prices. All 1,219 days were then re-read from storage and the check re-derived from scratch rather than on the repair’s own word: zero rows below intrinsic, zero read errors.
bid_price and ask_price were deliberately left
alone. Those are the vendor’s reported observations;
mark_price was our own derivation and was what we derived
wrongly. The consequence is worth stating plainly: for ITM
strikes in 2021–2024, treat the raw bid/ask as unusable
— the source itself quotes a 0.0005 bid against a 1.0 ask on a call
worth about 0.21 BTC. mark_price is now sound; the raw
quotes around it are not.
The modeled window was rebuilt on 2026-10-12
The 2024-10 → 2026-04 reconstructed window changed twice in October 2026, and both changes are visible in files you may already hold.
2026-10-09 — the published
underlying was the index, not the forward. The engine priced
each option at its per-expiry forward, converted to BTC by dividing by
the index, and then wrote the index into
underlying_price. Both real eras either side do the
opposite, which is also the exchange’s convention. The marks were
never wrong, but they sat on a different basis from the underlying
published beside them, so intrinsic computed from that underlying
overstated put intrinsic by the carry. Corrected in place by recovering
each forward algebraically from put-call parity and rescaling: no
implied volatility was touched and no price was re-derived from a model.
2026-10-12 — the window was regenerated, and grew from 530 to 566 days. Two engine fixes: the reconstruction now carries its 24-hour fit across midnight (33 days previously began at 01:00, because 00:00–01:00 UTC is too thin to calibrate on its own and the engine correctly refuses to emit without a fit), and inverse options are now priced off the forward on the input side too. Every day in the window is now complete at 1,440 snapshots. The 36 added days are the last and first of each month boundary, which had been generated and validated in July but never uploaded.
If you downloaded modeled files before 2026-10-12, re-pull them. Measured against Deribit’s own reported trade volatilities, the regenerated surfaces are of comparable accuracy rather than better — mean absolute IV error moved 0.0406 → 0.0404 and the 95th percentile moved 0.1187 → 0.1225. The reasons to re-pull are the added coverage and the corrected underlying, not an accuracy gain, and we would rather say so than imply one.
open_interest is unchanged and remains null across the
whole window. It is an observable, not something a surface fit can
produce, and the extra days do not change that.
What a snapshot timestamp means
A chain snapshot's timestamp is the moment the collector
finished gathering that cycle, not the moment it started. Every row in a
cycle shares one timestamp, so a snapshot is a single point in time for
range queries and joins.
This changed on 2026-10-06, and the change matters if you are backtesting. Before that date the timestamp was taken at the start of the cycle and the data was fetched afterwards, so a row was labelled with a moment slightly before its own contents existed - look-ahead bias. The error equals the cycle duration: normally about 1.5 seconds, but cycles are not reliably fast. The worst observed was 2026-09-23, when one cycle took 181 seconds and its later underlyings were stamped roughly 145 seconds early.
Rows written from 2026-10-06 onward carry the opposite, safer error: their contents may be up to one cycle older than the label. A stale quote cannot manufacture a fill that was never available; a prescient one can. Rows written before that date retain the original convention and have not been restamped - we cannot recover the fetch times that were never recorded. If your study is sensitive at the seconds scale, treat pre-2026-10-06 timestamps as the start of a window whose width is that cycle's duration rather than as an exact instant.
Analytics provenance
Every analytics response carries _meta.method_version, naming
the pipeline that produced the rows. Over a window spanning more than one
pipeline it reports the oldest method present: the weakest
row sets what the response can be relied on for, and reporting the newest
would be the more flattering and less true answer.
legacy-pre-2026-08-17 is the pipeline that produced every
analytics row before the 2026-08-17 cutover - the whole archive back to
2016. Treat its surface-derived metrics as unverified.
The sign of the 25-delta risk reversal is the known
defect: on adjacent fortnights either side of the cutover the two methods
disagree on it for most values. No timestamp was computed by both
pipelines, so that is a comparison of adjacent windows and
not a same-row recompute - the per-row error size is not
established, and we would rather say that than imply a controlled result
we do not have.
quality does not mean the same thing across that boundary,
so do not filter on it alone. On rows produced after the
cutover it reflects a divergence check against the options chain the
surface was fitted to. On legacy-pre-2026-08-17 rows it
records only whether the SVI optimiser raised an error -
a surface that has lost the money still serialises perfectly and was
still marked good. A legacy row reading
quality: "good" is therefore a statement about the fit
completing, not about the numbers being right. Filter on
method_version as well.
Analytics responses also carry _meta.input_provenance, which
describes what the calculation was fed rather than which code ran.
real means captured market data; modeled means the
input was our own reconstruction, which inside the gap window is all that
exists; unknown means no provenance was recorded, which is true
of every row written before 2026-10-06. Over a window it reports the
weakest value present - a window mixing real and
reconstructed days is not a real window, and unknown is weaker
than modeled, because "nobody recorded it" tells you less than
"we know it was modelled".
Access
Modeled files are opt-in: pass ?include_modeled=true to
GET /v1/files/{ccy}/bulk - the only endpoint that accepts this
parameter - to include modeled days in the bulk manifest (each such entry is
flagged modeled: true). By default the manifest lists real
coverage only.
On GET /v1/chains/{ccy}/snapshots and
GET /v1/chains/{ccy}/instrument/{symbol}, ?source=
filters the response body by the exact provenance tag carried in each row's
source column - for example modeled_surface,
live_ws, or live_rest. An unknown tag returns
400 invalid_source; there is no real/modeled
shorthand.