vike

Recipe 25 of 30

Backtest a Polymarket strategy on the real historical book (fills, fees, lookahead)

Take a day of recorded top-of-book or full-book quotes from the archive and the same day's on-chain outcomes from /settlements/bulk, decide only on quotes received before each decision, fill at the standing price up to its size, and add fees yourself.

A backtest needs two things the API keeps apart: the book as you could have seen it, and how each market ended. GET /v1/polymarket/settlements/bulk gives the second in one call per day: each market's winning token, from the chain. The archive holds the first, one file per stream and UTC day (l1_quotes for the touch, book_events for full depth). Join them in DuckDB and price every entry against the quote that stood at the moment you would have decided. The bulk parameters are on Settlements in bulk.

Quick answer

Fetch how every market of the family resolved on the day you test.

cURL
# how every 5-minute BTC market resolved on 2026-10-23: the payoff side of the backtest
curl -H "X-API-Key: $VIKE_API_KEY" \
  "https://data.vike.io/v1/polymarket/settlements/bulk?slug_prefix=btc-updown-5m-&from_ms=1792713600000&to_ms=1792800000000&limit=5000"

Example response

Captured from the live API on 2026-10-24 at 17:22 UTC, with limit=3, trimmed to 2 rows; at limit=5000 the same rows open the page. A winning token pays $1 a share and the other pays 0. The slug carries the window's start in unix seconds, so the window closes 300 seconds later; resolved_ts_ms is when the chain settled it, 81 and 143 seconds after the close for these two.

Example response
{
  "settlements": [
    {
      "condition_id": "0xe8eb153c68bb090d0f7378220dda294de0fb903ca7455298e889115bd8db3926",
      "market_known": true,
      "outcomes": ["Up", "Down"],
      "payouts": [1, 0],
      "question": "Bitcoin Up or Down - October 22, 7:55PM-8:00PM ET",
      "resolved_block": 96005540,
      "resolved_ts_ms": 1792713681000,
      "slug": "btc-updown-5m-1792713300",
      "token_ids": ["66807904852803027944717699017367321508323078567048144477724810240924212801296", "31737787044827274982239313079132196514193684856716359909945275491390424775584"],
      "winning_index": 0,
      "winning_outcome": "Up",
      "winning_token_id": "66807904852803027944717699017367321508323078567048144477724810240924212801296"
    },
    {
      "condition_id": "0xc0b564b0d97b8494ea9c05312043825e03355064cf6c86dcd2857d02d6172496",
      "market_known": true,
      "outcomes": ["Up", "Down"],
      "payouts": [0, 1],
      "question": "Bitcoin Up or Down - October 22, 8:00PM-8:05PM ET",
      "resolved_block": 96005781,
      "resolved_ts_ms": 1792714043000,
      "slug": "btc-updown-5m-1792713600",
      "token_ids": ["95675224515755543774285417772928531590582853511339802875009054381294430813145", "71942067128320219824750689771945336085097224938258510367609625645394607749800"],
      "winning_index": 1,
      "winning_outcome": "Down",
      "winning_token_id": "71942067128320219824750689771945336085097224938258510367609625645394607749800"
    }
  ],
  "has_more": true,
  "next_cursor": "1792714280000:0x9bb2ac747df3557a00c1bfe4bc6189daa5289f31eafb7c4f32bae6071a341f2a"
}

Variations

Save the outcomes for DuckDB. One line per market, with the Up token and the winner.

cURL — outcomes file
# markets.jsonl: condition_id, slug, Up token and winning token of each market
curl -s -H "X-API-Key: $VIKE_API_KEY" \
  "https://data.vike.io/v1/polymarket/settlements/bulk?slug_prefix=btc-updown-5m-&from_ms=1792713600000&to_ms=1792800000000&limit=5000" \
  | jq -c '.settlements[] | select(.outcomes[0] == "Up") | {condition_id, slug, up_token: .token_ids[0], winning_token_id}' > markets.jsonl

Download the day's quotes.

cURL — quotes
# the top of book of every 5-minute BTC market on 2026-10-23 (186 MB)
curl -s -L -H "X-API-Key: $VIKE_API_KEY" \
  "https://data.vike.io/v1/polymarket/archive/url?date=2026-10-23&stream=l1_quotes&asset=btc&tenor=5m&redirect=true" \
  -o btc-5m-l1_quotes-2026-10-23.parquet

The backtest in DuckDB. An example rule: buy Up at the ask 60 seconds before the close when the ask is between 0.55 and 0.90, and hold to resolution. The ASOF join takes the last quote received at or before the decision.

DuckDB
WITH m AS (
  SELECT *, (CAST(split_part(slug, '-', 4) AS BIGINT) + 300) * 1000 - 60000 AS decide_ms
  FROM read_json_auto('markets.jsonl')
),
q AS (
  SELECT token_id, local_ts, CAST(ask AS DOUBLE) AS ask, CAST(ask_size AS DOUBLE) AS ask_size
  FROM read_parquet('btc-5m-l1_quotes-2026-10-23.parquet') WHERE ask > 0
),
entries AS (
  SELECT m.slug, q.ask, q.ask_size, m.decide_ms - q.local_ts AS quote_age_ms,
         CASE WHEN m.winning_token_id = m.up_token THEN 1.0 ELSE 0.0 END AS payout
  FROM m ASOF JOIN q ON m.up_token = q.token_id AND m.decide_ms >= q.local_ts
)
SELECT count(*) AS trades,
       avg(payout) AS win_rate,
       avg(payout - ask) AS pnl_per_share_before_fees
FROM entries
WHERE quote_age_ms < 5000 AND ask BETWEEN 0.55 AND 0.90;

Full depth instead of the touch. For orders bigger than the best level, replay the book_events file (1.65 GB that day) and walk the ladder.

cURL — full book
# where the day's full-book file is, with its size
curl -H "X-API-Key: $VIKE_API_KEY" \
  "https://data.vike.io/v1/polymarket/archive/url?date=2026-10-23&stream=book_events&asset=btc&tenor=5m"

Before you rely on it

Lookahead, fills and fees are yours to model

Decide on local_ts, the time a quote reached the recorder, and use only quotes at or before the decision; the settlement row is the payoff, never an input. A taker fill at the ask is real only up to ask_size; a resting order's place in the queue is not in the data. Nothing in the tape or the archive carries fees, so subtract the venue's fee schedule yourself.

Stale quotes after a gap

Where the recorder lost the feed, book_events has status rows gap_start and live_resume, and the quotes in between are missing. Require a fresh quote, as the query does with quote_age_ms < 5000, or skip those stretches.

Ask your agent

Connected to the Vike MCP server (https://vike.io/mcp) in Claude, ChatGPT or Cursor, ask it in plain words. The polymarket_archive tool answers with the download links, at 1 credit a call. The backtest runs on your side.

Ask your agent
Give me the download links of the Polymarket 5-minute BTC top-of-book and full-book files for 2026-10-23
Example response
{  "settlements": [    {      "condition_id": "0xe8eb153c68bb090d0f7378220dda294de0fb903ca7455298e889115bd8db3926",      "market_known": true,      "outcomes": ["Up", "Down"],      "payouts": [1, 0],      "question": "Bitcoin Up or Down - October 22, 7:55PM-8:00PM ET",      "resolved_block": 96005540,      "resolved_ts_ms": 1792713681000,      "slug": "btc-updown-5m-1792713300",      "token_ids": ["66807904852803027944717699017367321508323078567048144477724810240924212801296", "31737787044827274982239313079132196514193684856716359909945275491390424775584"],      "winning_index": 0,      "winning_outcome": "Up",      "winning_token_id": "66807904852803027944717699017367321508323078567048144477724810240924212801296"    },    {      "condition_id": "0xc0b564b0d97b8494ea9c05312043825e03355064cf6c86dcd2857d02d6172496",      "market_known": true,      "outcomes": ["Up", "Down"],      "payouts": [0, 1],      "question": "Bitcoin Up or Down - October 22, 8:00PM-8:05PM ET",      "resolved_block": 96005781,      "resolved_ts_ms": 1792714043000,      "slug": "btc-updown-5m-1792713600",      "token_ids": ["95675224515755543774285417772928531590582853511339802875009054381294430813145", "71942067128320219824750689771945336085097224938258510367609625645394607749800"],      "winning_index": 1,      "winning_outcome": "Down",      "winning_token_id": "71942067128320219824750689771945336085097224938258510367609625645394607749800"    }  ],  "has_more": true,  "next_cursor": "1792714280000:0x9bb2ac747df3557a00c1bfe4bc6189daa5289f31eafb7c4f32bae6071a341f2a"}