> ## Documentation Index
> Fetch the complete documentation index at: https://docs.predexon.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Orderbook replay

> Replay historical Polymarket orderbook state to backtest strategies against the real depth that existed at the time.

The most accurate way to backtest a prediction-market strategy is to replay the **orderbook snapshots that actually existed** at decision time — not a candle, not a mid price, the real bids and asks. Predexon stores per-token orderbook snapshots starting **January 1st, 2026** and exposes them as a free, unlimited endpoint.

<Tip>
  **Free & unlimited.** [`GET /v2/polymarket/orderbooks`](/api-reference/markets/orderbooks) doesn't count against your monthly quota on any plan.
</Tip>

***

## What you get per snapshot

Each snapshot is a full L2 book for one token at one point in time:

```json theme={null}
{
  "timestamp": 1736294400000,
  "token_id": "82855...",
  "condition_id": "0x...",
  "bids": [{ "price": "0.62", "size": "5000" }, ...],
  "asks": [{ "price": "0.63", "size": "3000" }, ...]
}
```

Snapshots are produced at the cadence the market produced them — busy markets give you more snapshots per second, illiquid markets fewer. Timestamps are **milliseconds**.

***

## Walk an interval

The simplest replay loop: pull snapshots between two timestamps, then iterate.

```python theme={null}
import requests, time

API_KEY = "YOUR_API_KEY"
BASE = "https://api.predexon.com"
HEADERS = {"x-api-key": API_KEY}

TOKEN_ID = "82855..."          # the outcome you're backtesting
START_MS = 1736294400000       # 2026-01-08 00:00 UTC
END_MS   = 1736380800000       # 2026-01-09 00:00 UTC

cursor = START_MS
while cursor < END_MS:
    snaps = requests.get(
        f"{BASE}/v2/polymarket/orderbooks",
        headers=HEADERS,
        params={
            "token_id": TOKEN_ID,
            "start_time": cursor,
            "end_time": END_MS,
            "limit": 200,
        },
    ).json()

    if not snaps:
        break

    for s in snaps:
        # your strategy sees the book exactly as it was at s["timestamp"]
        best_bid = float(s["bids"][0]["price"]) if s["bids"] else 0
        best_ask = float(s["asks"][0]["price"]) if s["asks"] else 1
        spread   = best_ask - best_bid
        # decide(s, best_bid, best_ask, spread)

    cursor = snaps[-1]["timestamp"] + 1
```

`limit` caps at **200** snapshots per call. The walk pattern above advances the cursor to the last snapshot's timestamp plus one millisecond.

***

## Simulating fills

The honest way to simulate a fill against a historical book:

| Order type                 | Simulated fill                                                                                                                                                                                     |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Market buy of size S**   | Walk the asks ascending. Take size from each level until you've filled S or exhausted the book. Average price = your fill price.                                                                   |
| **Market sell of size S**  | Same, walking the bids descending.                                                                                                                                                                 |
| **Limit buy at P, size S** | Fill only if `best_ask ≤ P` at the snapshot. Take size from asks `≤ P` only.                                                                                                                       |
| **Resting limit order**    | At each subsequent snapshot, check if a trade would have crossed your price. The [trades endpoint](/api-reference/trading/trades) is the ground truth for what actually traded — match against it. |

For resting limit orders, pair orderbook snapshots with the [trades endpoint](/api-reference/trading/trades) over the same window. The trades tape tells you what actually crossed; the orderbook tells you whether your resting order would have been the resting side.

***

## Slippage and depth checks

You can answer "could I have actually traded \$X in this market at the time?" directly from a snapshot:

```python theme={null}
def simulate_market_buy(snapshot, usd_to_spend):
    spent, filled = 0.0, 0.0
    for level in snapshot["asks"]:
        price = float(level["price"])
        size  = float(level["size"])
        level_cost = price * size
        if spent + level_cost <= usd_to_spend:
            spent  += level_cost
            filled += size
        else:
            remaining_usd = usd_to_spend - spent
            partial_size  = remaining_usd / price
            spent  += remaining_usd
            filled += partial_size
            break
    avg_price = spent / filled if filled else None
    return {"filled_shares": filled, "avg_price": avg_price, "usd_spent": spent}
```

Run this across every snapshot in your window to get a slippage curve for the size you actually want to trade — a much better sanity check than blindly trusting candle data.

***

## Backtesting tips

* **Use both sides of the market.** Subscribe (or replay) Yes *and* No outcomes. Spreads between them often imply arbitrage opportunities or stale liquidity.
* **Snapshots aren't tick-level.** They're produced when the book changes. Between two snapshots, assume the book held — don't interpolate.
* **Watch for empty books.** Newly created markets, low-liquidity outcomes, and weekends all produce sparse snapshots. Handle `bids: []` and `asks: []` gracefully.
* **Pair with the trades endpoint.** [`GET /v2/polymarket/trades`](/api-reference/trading/trades) gives you the actual fills that happened — useful for validating your simulated execution.

***

## Going live with the same logic

Once a strategy backtests well, the live version uses the same shapes via the [orderbook WebSocket channel](/websocket/orderbook) instead of REST. The `book_snapshot` event has the same `bids` / `asks` shape, and `price_change` events let you maintain the book incrementally — so your decision function rarely needs to change between backtest and prod.
