- 98.4%Pairs that jump
- 109 msMedian row gap
- ~28Events per gap
- 50 msCapture throttle
A backtest is only as trustworthy as the series underneath it, so it is tempting to reach for the sequence number as a completeness check: walk the series, and where the numbers jump, you found a hole. That method is wrong, and the data says so bluntly. On BTC 5m snapshots, 98.4% of adjacent stored rows jump by more than one. The counter is not measuring what most people think it is.
This post used to say the opposite
An earlier version of this article told you that a jump in sequence_number flags a dropped event, and that a clean consecutive run proves a window is complete. Measurement disproved it: applied against real data, that rule would quarantine almost every window we store. The article has been rewritten around what the counter genuinely measures, and the API reference, the OpenAPI schema and the MCP tool descriptions have been corrected to match.
Capture here is event-driven, a snapshot is produced when Polymarket emits a change to the book rather than on a fixed polling clock. That much of the original framing was right, and it does raise the obvious question: how do you know what you received? The mistake was assuming the sequence number answers it.
What the counter actually counts
The sequence number is ours, not Polymarket’s. Each token’s book carries a counter that increments every time we apply an event to it, whether that event is a full snapshot or an incremental delta. Storage is a separate decision made afterwards: we keep at most one row per 50ms per token, and we drop rows whose top of book is unchanged. The throttle runs after the event has already been applied, so a dropped row still advances the counter. That single ordering detail is the whole misunderstanding.
Events, not rows
It counts what the collector applied to the book, not what survived the capture throttle, so the gap between two stored rows is expected sampling rather than loss.
- Increments on every applied event
- Throttle runs after apply
- A jump is the norm, not an alarm
It resets
When a token’s book is dropped and re-subscribed, the counter starts again at 1. Window rollovers and connection replacements both do this routinely.
- Restarts at 1
- Every window rollover
- Not monotonic across a lifetime
Backfilled rows are zero
Rows reconstructed from a historical source carry a constant 0. The counter only means something on rows we captured live.
- Constant 0, not a counter
- Check before differencing
- Live rows only
Why a jump is the normal case
Measured across a week of BTC 5m markets, the median pair of consecutive stored rows sits 109ms apart and spans roughly 28 applied book events. About a quarter of all pairs are more than 150ms apart with ten or more events inside them. None of that is missing data in any meaningful sense, it is the sampling policy working exactly as designed: the book moved many times, and we recorded the state at the end of each 50ms slice rather than every intermediate frame.
One 239 ms window, 341 events, zero rows
In the same dataset, a single 239ms stretch carried 341 applied book events while the price ran from 0.905 to 0.190, and not one row was stored inside it. A completeness check would have flagged that window. What it would not have told you is the far more useful fact: the market traded through the entire move while your series was between samples.
What the counter is genuinely good for
Read correctly, it answers a better question than the one it was being asked. The delta between two consecutive rows tells you how much book activity your sample skipped, which turns an unknown into a measurable quantity. That is the honest version of the old quiet-versus-gap question, and unlike the completeness check, it actually works.
- 1Pull the snapshots for your market and window in ascending order, so the counter runs in capture order and each row can be compared to the one before it.
- 2Filter out any rows where sequence_number is 0, those are backfilled and carry no counter, and split the series wherever the value drops rather than rises, since a drop marks a re-subscribe rather than anything about the market.
- 3Take the delta between adjacent rows, that is the number of book events that happened between the two states you can actually see.
- 4Read a small delta across a long stretch of wall-clock time as a genuinely quiet book, and a large delta across a short stretch as an active book you are sampling coarsely. Those are very different regimes and this is the field that separates them.
- 5Where the delta is large and the move between rows is large, treat the interval as unresolved rather than as a straight line, and go to the trade tape for what happened inside it.
The fields this rests on
sequence_numberCount of applied book events, per token, on live rowsevent_timestampWhen Polymarket emitted the changecapture_timestampWhen we processed it- order ascPull in capture order so deltas are meaningful
One caveat worth stating plainly: the counter is per token, so never compare it across the UP and DOWN legs of the same market. They are independent books with independent counters, and differencing across them produces a number that means nothing at all.
When you need what happened inside the gap
The snapshot series is a sample of the book. The trade tape is a different instrument: executions arrive on a separate WebSocket, unthrottled, carrying Polymarket’s own event timestamp. Where the snapshot series is silent between two rows, the tape frequently is not, which makes it the right tool for the question the sequence number cannot answer.
This matters more than it sounds. Across the same BTC 5m sample, of 1,569 downward crossings of a 0.80 level, 354 landed more than three cents below it in the next stored row, and 47 of those had a real execution inside the band the snapshots stepped over. A backtest reading snapshots alone would have filled those at prices the market never printed. Reading the tape alongside the book fixes it, and the correction only ever moves an exit earlier, never later.
Being honest about what you can and cannot claim
- A sequence delta tells you how many events you skipped, it does not tell you what those events were or where the price travelled in between.
- A clean consecutive run does not prove completeness, it usually just means an illiquid book that barely moved, which is a statement about the market rather than about your data.
- Do not interpolate across a wide delta, that manufactures a path that never existed. Hold the last known state, skip the interval, or resolve it against the trade tape.
- For actual capture-loss accounting, the throttle and dedup counters on the debug endpoint are the right instrument, because they distinguish deliberate skips from genuine drops. Sequence arithmetic cannot.
The counter was never lying to you. It answers a precise question, how much did the book move between the two states you can see, and that is a more useful thing to know than whether a series is nominally complete.
Resolve a window properly
Pull the snapshots and the trade tape for the same market and window from the API, then measure what your sample skipped instead of assuming it skipped nothing.
Frequently asked questions
What is the sequence_number on a snapshot?
It is our own per-token counter of orderbook events applied to that book, incremented on every full snapshot or incremental delta the collector processes. It is not a Polymarket stream identifier, and it counts events applied rather than rows stored. Because capture keeps at most one row per 50ms per token and drops unchanged books, the value on two adjacent stored rows normally differs by much more than one.
Does a jump in sequence_number mean an event was dropped?
No, and this is the most common misreading. The capture throttle runs after the event has already been applied to the book, so a row that is throttled or deduplicated away still advances the counter. Measured on BTC 5m data, 98.4% of consecutive stored row pairs jump by more than one, with a median gap of 109ms spanning about 28 applied events. Treating every jump as a dropped event would flag almost all healthy data as broken.
How do I detect real gaps in order book data then?
Use the right instrument for the question. To measure how much book activity a stored row skipped, take the delta in sequence_number between adjacent rows. To find out what actually happened inside that interval, read the trade tape, which is captured on a separate unthrottled connection and carries Polymarket event timestamps. For genuine capture-loss accounting, use the throttle and dedup counters on the debug endpoint, which distinguish deliberate skips from real drops.
Capture is event-driven, does a long gap in time mean missing data?
Not on its own, and the sequence delta is what tells you which case you are in. A small delta across a long stretch of wall-clock time means the book genuinely did not change, that is a quiet market. A large delta across a short stretch means the book was very active and you are sampling it coarsely. Both are normal, they are simply different regimes, and only the second one warrants going to the trade tape before you trust a price path through it.
Why does sequence_number sometimes go backwards or sit at zero?
It resets to 1 whenever a token’s book is dropped and re-subscribed, which happens routinely at window rollovers and when a connection is replaced, so it is not monotonic across a market’s lifetime. A constant 0 means the row was backfilled from a historical source rather than captured live, so it carries no counter at all. Split your series at resets and exclude backfilled rows before differencing anything.



