Summary
Critical: orderbook permanently desyncs after a single sequence gap.
_handle_seq_gap (in kalshi/ws/client.py) clears the affected local books and resets the sequence tracker, but never requests a fresh snapshot, never re-subscribes, and never invalidates the subscription. Kalshi only sends an orderbook_snapshot on subscribe, so once a gap fires:
- Local books are wiped via
OrderbookManager.remove(ticker).
- Every subsequent
orderbook_delta arriving for that ticker hits OrderbookManager.apply_delta, finds no _BookState, and silently returns None (DEBUG-level warning only).
- The consumer iterator keeps receiving delta envelopes — but the local book stays permanently empty.
This is silent data divergence on the most safety-critical channel of the SDK. Any production orderbook strategy that experiences a single dropped frame will operate against an empty book forever after.
Compounding issue: all-markets orderbook subscriptions (subscribe_orderbook_delta() with no tickers=) have sub.params['market_tickers'] empty, so _handle_seq_gap iterates an empty list and removes no books — every locally-maintained book on that sid then silently diverges with no remediation.
Location
kalshi/ws/client.py:380-403 — _handle_seq_gap
kalshi/ws/orderbook.py:91-105 — apply_delta silently returns None for missing book
Evidence
# kalshi/ws/client.py:380-403
async def _handle_seq_gap(self, gap: SequenceGap) -> None:
"""Handle a sequence gap by logging and triggering resync."""
logger.warning("Sequence gap on sid %d: ... Triggering resync.", ...)
if self._sub_mgr:
sub = self._sub_mgr.get_subscription_by_sid(gap.sid)
if sub and sub.channel == "orderbook_delta":
tickers = sub.params.get("market_tickers", []) # empty if all-markets sub
if self._orderbook_mgr:
for ticker in tickers:
self._orderbook_mgr.remove(ticker)
if self._seq_tracker:
self._seq_tracker.reset(gap.sid)
# ← returns; never resubscribes, never asks server for snapshot
# kalshi/ws/orderbook.py:91-105
def apply_delta(self, msg: OrderbookDeltaMessage) -> Orderbook | None:
ticker = msg.msg.market_ticker
state = self._books.get(ticker)
if state is None:
logger.warning("Delta for unknown ticker %s (no snapshot yet)", ticker)
return None # silently swallow → consumer's local book is forever empty
Recommended fix
Drive real resync from _handle_seq_gap:
- For each affected subscription (orderbook_delta and any other sequenced channel —
order_group_updates has the same problem; see separate issue), unsubscribe and re-subscribe with send_initial_snapshot=True.
- For all-markets orderbook subscriptions, track which tickers a sid has produced snapshots for in
OrderbookManager state, then clear all of them before resubscribe.
- Until real resync ships, the gap MUST surface to the consumer iterator (typed envelope like
OrderbookResyncRequired or a KalshiSequenceGapError raised through __anext__) so safety-critical strategy code can halt rather than silently consume a permanently-empty book.
Add an integration test that injects a forced gap (manipulate SequenceTracker._last_seq) on both a per-ticker and an all-markets subscribe_orderbook_delta and asserts the book repopulates within N frames.
Severity & category
critical / correctness, ws
Summary
Critical: orderbook permanently desyncs after a single sequence gap.
_handle_seq_gap(inkalshi/ws/client.py) clears the affected local books and resets the sequence tracker, but never requests a fresh snapshot, never re-subscribes, and never invalidates the subscription. Kalshi only sends anorderbook_snapshoton subscribe, so once a gap fires:OrderbookManager.remove(ticker).orderbook_deltaarriving for that ticker hitsOrderbookManager.apply_delta, finds no_BookState, and silently returnsNone(DEBUG-level warning only).This is silent data divergence on the most safety-critical channel of the SDK. Any production orderbook strategy that experiences a single dropped frame will operate against an empty book forever after.
Compounding issue: all-markets orderbook subscriptions (
subscribe_orderbook_delta()with notickers=) havesub.params['market_tickers']empty, so_handle_seq_gapiterates an empty list and removes no books — every locally-maintained book on that sid then silently diverges with no remediation.Location
kalshi/ws/client.py:380-403—_handle_seq_gapkalshi/ws/orderbook.py:91-105—apply_deltasilently returnsNonefor missing bookEvidence
Recommended fix
Drive real resync from
_handle_seq_gap:order_group_updateshas the same problem; see separate issue), unsubscribe and re-subscribe withsend_initial_snapshot=True.OrderbookManagerstate, then clear all of them before resubscribe.OrderbookResyncRequiredor aKalshiSequenceGapErrorraised through__anext__) so safety-critical strategy code can halt rather than silently consume a permanently-empty book.Add an integration test that injects a forced gap (manipulate
SequenceTracker._last_seq) on both a per-ticker and an all-marketssubscribe_orderbook_deltaand asserts the book repopulates within N frames.Severity & category
critical / correctness, ws