If you want historical Polymarket order book depth, most archives offer the same thing: a stream of book snapshots and price_change deltas, with instructions to seed from a snapshot and apply the deltas to rebuild the book at any past moment. We tried it. Across 381,093 checkpoints, the replay disagreed with the archive's own snapshots 67.8% of the time, and 6.4% of the books it produced were crossed — a bid above an ask, which cannot happen in a real market.

This is not a criticism of any particular archive. The snapshots themselves are accurate; we checked 18,087 of them and not one was crossed. The problem is structural, it applies to any event archive of a venue this busy, and it has a mechanism worth understanding before you plan a backtest around reconstructed depth.

The test grades itself

The neat thing about this data is that you do not need an external reference. The archive contains periodic book snapshots and the deltas between them. So seed from one snapshot, apply every following price_change, and when the next snapshot arrives, compare. The source marks its own homework.

from decimal import Decimal
import json

def levels(raw):
    out = {}
    for price, size in json.loads(raw):
        size = Decimal(size)
        if size > 0:
            out[Decimal(price)] = size
    return out

bids, asks, seeded = {}, {}, False
checkpoints = divergences = crossed = 0

for kind, b, a, price, size, side in rows:      # one token, ordered by time
    if kind == "book":
        src_bids, src_asks = levels(b), levels(a)
        if seeded:                              # compare replay against the source
            checkpoints += 1
            if bids != src_bids or asks != src_asks:
                divergences += 1
                if bids and asks and max(bids) >= min(asks):
                    crossed += 1
        bids, asks, seeded = src_bids, src_asks, True
    elif kind == "price_change":
        book = bids if side == "BUY" else asks
        amount, key = Decimal(size), Decimal(price)
        if amount <= 0:
            book.pop(key, None)                 # a zero size removes the level
        else:
            book[key] = amount

We ran this over the 25 most active tokens in each of twelve hours, spread from April to August 2026.

hour             checkpoints    diverged   crossed   extra:missing
2026-04-20T09            577      100.0%     40.6%          273:20
2026-05-02T15          1,498       99.6%     36.6%          657:69
2026-05-24T12            221       95.9%     80.5%           168:1
2026-06-03T21         78,815       67.8%      4.7%       9985:2850
2026-06-11T12         71,766       69.3%      7.9%      10469:2560
2026-06-18T06         14,575       56.7%      2.1%         593:183
2026-06-24T18         19,455       57.9%      0.2%         889:303
2026-07-05T03         52,224       66.1%      6.9%       7829:1754
2026-07-14T11          2,548       69.5%      3.6%          767:87
2026-07-22T16         55,383       70.5%      7.1%       7074:1172
2026-08-01T08         40,700       67.5%      7.1%       6300:1529
2026-08-09T23         43,331       71.0%      7.4%       7711:2022

POOLED               381,093       67.8%      6.4%      52715:12550

What the numbers say

Divergence is stable and high. Every high-volume hour lands between 57% and 71%. Two thirds of the time, replaying the deltas does not reproduce the book the source says existed.

Crossed books are the visible symptom. 6.4% pooled, though the range is wide: 0.2% in a quiet hour, 80.5% in one sparse hour where snapshots were far apart. If your reconstruction produces a bid above an ask, you are not looking at a market state that ever existed.

The asymmetry is the diagnosis. When the replay disagrees, it usually holds levels the source does not: 52,715 cases of extra levels against 12,550 of missing ones, a ratio of 4.2 to 1. That rules out a bug in the delta logic. A mistake in applying updates would drop levels roughly as often as it kept them. One-directional accumulation means removal events are missing from the stream — orders that were cancelled with no price_change to say so.

Those orphaned levels sit in the reconstructed book at prices nobody is quoting any more. Eventually one of them crosses the other side.

Why this is nobody's fault

Polymarket's websocket carries around 24,000 events a second across all markets. At that rate every collector drops messages, and a dropped removal is invisible: the stream does not number its events, so there is no gap to detect. The archive is recording faithfully; it simply cannot record what it never received.

Live systems survive this because they can ask again. A collector re-reads the full book from the REST API on a timer, and every re-read silently repairs whatever drifted since the last one.

# What a live collector can do that a replay cannot
while running:
    apply_websocket_events()          # the delta stream
    if now() - last_resync > 60:      # ask the venue what the book really is
        book = rest_client.get_book(token_id)
        registry.replace(token_id, book)
        last_resync = now()

A replay has nothing to re-read against. The market closed months ago. Whatever was not captured at the time is not recoverable at any price, from any vendor.

The interval is the whole story

We changed our own reconcile interval from five minutes to sixty seconds and measured crossed books on Polymarket's five-minute BTC markets, bucketed by how close each snapshot was to settlement:

time to close      before      after
0-60s              11.3%       0.0%
60-120s            13.3%       1.2%
120-180s           11.7%       0.5%

Nothing else changed. The same websocket, the same parsing, the same storage. Only how often we asked the venue what the book actually looked like. That is also why the archive hours with sparse snapshots are so much worse than the dense ones — the checkpoint interval is doing the same work, after the fact.

What to do with this

If you need prices, use Polymarket's own API. It is free, it serves 1-minute buckets on markets resolved years ago, and you should not pay anyone for it. We wrote up exactly what it does and does not cover on the Polymarket API pricing page.

If you need depth — slippage against a real ladder, fill probability, how liquidity behaved before a resolution — then the ladder has to have been captured, with reconciliation running, while the market was live. Reconstruction from an event stream will give you something that looks like a book two thirds of the time and is provably impossible 6% of the time.

That is what PolyOrderbooks records: Polymarket crypto order books captured at 1-second resolution with a 60-second reconcile, resolved markets kept alongside their winning outcome. You can download a free 1-second L2 sample and run the crossed-book check on it yourself. It is three lines of pandas, and we would rather you checked.

FAQ

Can you reconstruct a Polymarket order book from historical data?

Only approximately, and not reliably. Seeding from a book snapshot and applying every subsequent price_change disagreed with the source's own next snapshot at 67.8% of 381,093 checkpoints we tested. 6.4% of the reconstructions were crossed, meaning they described a state no real market was ever in.

Why are reconstructed order books crossed?

Because removal events go missing. When an order is cancelled the stream should carry aprice_change with size zero. At roughly 24,000 events a second some of those are dropped, and the stream is not sequenced, so there is no gap to detect. The replay keeps a level the venue removed. Extra levels outnumbered missing ones 4.2 to 1 in our test, which is the signature of a lossy feed rather than a faulty replay.

Does Polymarket provide historical order book depth?

No. The CLOB API returns the current book only, and there is no documented endpoint for the ladder at a past timestamp. Historical prices are a different matter and are served free down to 1-minute buckets, even on markets resolved years ago — see Polymarket API pricing for what the official API does and does not cover.

How do I check whether an order book dataset is correct?

Count crossed books. A real book can never have a bid at or above the best ask, so any row where max(bids) >= min(asks) is impossible. It is four lines of pandas and it is the first thing worth running on any sample, including ours. The provider comparison has the snippet.