Measurement-driven studies of the market-data path — the part of the system where a stale order book turns directly into slippage. Each case starts from a real production symptom, measures the cause with a live probe, and ends in a shipped change or a "there's no lever here" conclusion. Per-venue feed facts are catalogued in EXCHANGE_NOTES.md; this document is the reasoning that produced them.
After the feed changes in cases 1–3 shipped, a same-day comparison of BTC tick rate across three production runs (measured from the recorded Parquet):
| Venue | Before | After | Change |
|---|---|---|---|
| Extended | 5.5/s (per-market, full depth) | 22.5/s (single, depth=1) |
~4× |
| Lighter | 13.3/s (order_book/) |
27.9/s (ticker/) |
~2.1× |
| Zero1 | 4.2/s | 4.8/s | noise — no lever exists |
Startup also dropped from ~120s to ~19s (6×), because moving Lighter to a single connection removed a 1500ms × 79-connection stagger — 118s of blocking on ob_ready_rx. The recorded Parquet schema stayed correct: bid1 fully populated, bid2..bid10 = 0 for the two BBO venues, all five levels still filled for Zero1.
Symptom. In live trading, Extended entries regularly took large negative slippage. One DOT trade on the Tokyo server took 459 bps of entry slip on the Extended leg (Tick→Recv = 82ms, Recv→Sig = 31ms) while the Lighter leg of the same trade took 0 bps. That asymmetry said the Extended order book was lagging the matching engine.
Measurement. A probe subscribed to the full-depth and ?depth=1 streams of one symbol in parallel and measured local_recv − exchange_ts over 120s:
| Metric | Full depth | BBO ?depth=1 |
|---|---|---|
| msgs/s | 13.8 | 19.5 |
| message type | mostly DELTA | all SNAPSHOT |
| avg Tick→Recv | 9,291 ms | 126 ms |
| p50 | 140 ms | 123 ms |
| p99 | 54,836 ms | 180 ms |
| max | 56,529 ms | 334 ms |
The full-depth stream wasn't "100ms batched" — it was genuinely tens of seconds behind, avg ~9.3s and p99 ~55s, a publisher-side backlog where the message ts is the original event time, not the publish time. The ?depth=1 stream behaved: p50 123ms, p99 180ms, with the ~110ms floor being the Mac→Tokyo transit (single-digit ms from the Tokyo server). The gap is 75× at p50, 300× at p99 — a complete explanation for the observed slippage.
Decision. Switch Extended to ?depth=1 BBO. Each message is a full top-of-book snapshot (no delta reassembly). Web research confirmed the mechanism is industry-standard: exchanges deliberately batch/throttle L2 channels (Coinbase Level2 50ms, Binance Diff Book 100–500ms, Extended 100ms full / 10ms BBO), and different WS channels are served by different worker pools with no cross-channel ordering. For a small stake the VWAP collapse to best price is harmless (order size ≤ top-of-book qty); a pre-flight check (order_size > level-0 qty → fallback) covers a growing stake.
The problem. Lighter's order_book/{mid} channel is documented 50ms batching — deliberate, not a bug, but any L1 tick early in the window waits up to 50ms (25ms on average) to be published. Unlike Extended's 55s backlog this is bounded, but a symmetric slip incident on the Lighter leg is only a matter of time as the stake grows. Lighter offers a lever: ticker/{mid} sends a BBO snapshot on every matching-engine nonce, unbatched. As on Extended, there's no depth=N middle ground — it's full order_book at 50ms or ticker at top-of-book.
Measurement. Two probes. A basic throughput run showed ticker at 144.9 msg/s vs order_book at 17.4 (8.3×) — but the Tick→Recv metric was unreliable there, because order_book's timestamp is the event time of the first event in the batch, not the publish time. So a second probe raced the two streams by matching on the nonce interval (begin_nonce, end_nonce] over 122s, 12,625 pairs:
ticker first : 11,504 (91.1%) ← ticker beats order_book in 9 of 10 updates
order_book first : 835 ( 6.6%) ← jitter between two independent TCP paths
Decision. Move Lighter to ticker/. Nine times in ten it delivers the same price change sooner, and switching to a single connection also removed the startup stagger (case 0). The recorded Parquet cutover to orderbook_depth=1 and its backtest implications are in EXCHANGE_NOTES.md → Lighter; the old full-L2 path stays in the tree behind orderbook_depth = "full".
Zero1's deltas@ is batched server-side at exactly ~100ms (like Extended's full stream, twice Lighter's 50ms), and there's no faster channel — trades@ ships in the same engine commit, carrying an identical update_id, so it offers no lead. A dedicated socket on the hot leg gives no cadence advantage either (Δp50 +0.7ms, pure noise). The 100ms floor is entirely server-side with no client lever, so there's nothing to change — the conclusion is "leave it," and it's documented in EXCHANGE_NOTES.md → Zero1 so it isn't re-investigated.
Symptom. In one 10-trade session, 5 of 10 trades took >50 bps of slippage on at least one leg — up to +274 bps — and the existing tick_max_spike_bps filter (set at 50 bps) fired on none of them.
Why the one-tick window leaks. The filter compared only the two most recent points, prev_bid_ask vs cur_bid_ask — one tick of memory. Trace the LINK trade's Extended bid (signal at t=0):
t=−43ms bid=9.138 ← normal
t=−30ms bid=9.269 ← +131 bps in one tick (maker repriced on taker flow)
t=−22ms bid=9.269
t= 0ms bid=9.269 ← SIGNAL: prev=9.269, cur=9.269, move=0 → filter passes
t=+147ms bid=9.145 ← collapses back, −135 bps
The +131 bps jump happened — but at t=−30, and by the time the strategy's own conditions triggered the signal, prev and cur were both already up on the pumped plateau. A one-tick filter can't see a spike that has dissolved into a run of cur = prev = pumped. The mechanisms varied across the bad trades (pump-and-fade, quote flicker with phantom 818-qty levels, a genuine move Lighter lagged), but the symptom was identical: the book was unstable around the signal, the favorable best was a transient, and the IOC arrived after the collapse.
The fix. Widen the filter's memory from one tick to a window of N ms, comparing max(bid) − min(bid) (and the ask) across every L1 tick in the window instead of the last diff — the same per-exchange, per-leg check and bps thresholds, just a small ring buffer instead of a prev/cur pair. Sweeping windows of 50–1000ms against the session:
- ≤100ms misses spikes that happened ~150ms before the signal — the bad WLFI trade (range 38) and a clean XPL trade (range 59) end up inverted, with no separating threshold.
- 200ms gives WLFI 98 and XPL 88 — correct order, a 10 bps gap. The only window with clean separation.
- ≥500ms picks up settled moves from 300–500ms earlier and starts blocking clean trades.
Result: a 200ms window at a 90 bps threshold (the midpoint of the admissible 88–97 bps band) caught 5/5 bad trades and passed 5/5 good ones — +851 bps of slippage avoided, 0 bps lost on the sample.
Hyperliquid's bbo channel is 5.6× faster than l2Book and is the fastest feed it offers, but its freshness floor is ~150–200ms (Tokyo) and structural: every market-data channel is block-gated, updating only once per HyperBFT block (~0.1s median). There is no premium endpoint, CloudFront bypass, or faster channel — the only documented way lower is running a non-validating node. So bbo is already optimal; the lever is on the strategy side (max_tick_age_ms at 500–750ms, and HL legs only for opportunities that outlive a block). Full analysis: EXCHANGE_NOTES.md → Hyperliquid.