The market-data contract
Three L2-book invariants — monotonic sequence, gap triggers a resnapshot, derived L1 matches the book — run against every covered venue's real book normalizer.
crates/vike-bridge-core/tests/market_data_conformance.rs is the market-data twin of the
exec contract. Where that harness machine-checks the
event contract, this one machine-checks the book seam: the L2 invariants each covered venue's
real market_data::route_frame normalizer must uphold. One invariant table, parameterized over
the vike_bridge_core::pump_spec roster, so a new venue cannot silently escape classification.
The three invariants
- Monotonic sequence — book updates fold in the venue's sequence order. A delta-sync venue
folds a contiguous delta and drops a stale or duplicate one, so the book's
last_seqnever regresses. A snapshot-full venue's frame advanceslast_seqto the frame's timestamp and replaces the book. - Gap triggers a resnapshot — a sequence gap answers
MdEvent::Resync, never a silent fold across the gap. The gapped delta is not applied and the book — best bid, best ask andlast_seq— is left exactly as it was, so the pump re-seeds from a fresh snapshot rather than serving a corrupt book. Delta-sync venues only. - Derived L1 consistency — for a venue whose live pump derives top-of-book from the L2 book rather than from a dedicated book-ticker channel, that derived quote must equal the book's real best bid and ask; a one-sided book derives no quote.
Why one table needs two book models
The covered venues do not share one book protocol, and BookModel is the axis that admits it.
DeltaSync — a snapshot seeds the book, then incremental deltas fold under a per-event
sequence rule and a gap triggers a resync. Three shapes sit inside that one variant:
- binance and aster seed
lastUpdateIdfrom a REST snapshot, then fold@depthdiffs under theU/uspan rule; a diff whoseUexceedslast_seq + 1is a gap. L1 rides a separate@bookTickerchannel, so invariant 3 is not applicable. - bybit takes an
orderbook.50snapshot then strictu += 1deltas throughL2Book::delta_decision(Strict); both a forward jump and auregression (a venue restart) are gaps. Bybit has no book-ticker channel, so its quote is derived from the L2 top throughquote_from_book— it is the one covered venue that exercises invariant 3. - deribit opens with a
type:"snapshot"frame anchored onchange_id, then chainschangeframes onprev_change_idmatching the previous frame'schange_id. Its contiguity is an explicit back-pointer, not a numeric step:change_idis venue-global and jumps arbitrarily between frames. Stale is judged first, before the chain check, because a replayed frame'sprev_change_idno longer matches the advanced anchor either, and a chain-first reading would report a harmless duplicate as a gap.
SnapshotFull — okx's books5 channel pushes a full top-five snapshot every frame with ts as
the sequence, so there are no deltas to gap across. Invariant 2 is recorded as not applicable rather
than skipped, and invariant 1 asserts the full-state advance.
Deribit is what keeps the table honest
It is the covered row whose contiguity is a back-pointer rather than an arithmetic step. Without it the shared invariant table would quietly assume numeric sequence contiguity, which three of the four covered venues happen to have.
The bybit row is a retired bug, guarded
The live-versus-backtest divergence ledger once carried "Bybit book fold has no gap arm — binance
and okx resync, bybit folds across dropped frames silently until the five-minute reseed." That was
resolved when bybit's delta was routed through the shared L2Book::delta_decision(Strict), so bybit
now answers MdEvent::Resync on a gap exactly like binance. The harness therefore covers bybit as a
passing delta-sync row, which turns a retired ledger item into a permanent regression guard: if
bybit's gap arm is ever removed, gap_triggers_resync_all_delta_sync_venues goes red. The task brief
that commissioned this harness expected bybit to be deferred as a known divergence; the file's own
header records the decision to guard the fix instead of documenting a bug that no longer exists.
Deferrals, and the two kinds of them
Every other roster venue carries a DEFERRED row with a written reason, and the reasons split into
two genuinely different categories.
Not on this seam. Aster's market data is a verbatim re-export of binance's
family::depth::route_frame, so the shared normalizer and its gap arm are already exercised through
the binance row and a dedicated aster row would re-run byte-identical assertions. Hyperliquid has an
l2Book feed, but it decodes in its own market-feed module rather than through this route_frame
seam. Polymarket's book sits behind its crate feature and uses a feed-local sequence convention the
feed derives itself. Each becomes a drop-in row the day its decoder is lifted to this shape.
No book at all, structurally. Oanda's pricing stream serves an unsequenced top-of-book ladder
— no sequence id, no delta grammar, every frame a full snapshot of the top — so all three invariants
are inexpressible, and its quote path is covered instead by its own decoder fixtures and
freshness tests. IG is a dealer venue publishing its own two-sided price: L1 and candles, no ladder.
Alpaca, cTrader and IBKR deliver bars, quotes and trades on their own pumps. FXCM has no live
market-data lane of any kind — pump_spec classifies it NoPump and its capability row declares
live_data: LiveDataCaps::NONE.
The roster gate
md_conformance_roster_is_exhaustive asserts that covered and deferred partition
vike_model::VENUES exactly once, that every deferral reason is non-empty, and — the check unique to
this harness — that every covered venue is MarketPumpSpec::OnDriver in pump_spec, because a
venue must have a live market feed before its book can be conformed. See
roster exhaustiveness and
the data model.
The exec contract
One scenario table folded through the real order FSM against every covered venue's real event mapper — the machine-checked version of emitter-split, no-silent-vanish and exactly-one-terminal.
Roster exhaustiveness
One canonical venue list, and the completeness tests that force every table and every harness to classify each member — covered, or deferred with a written reason.