← Back to blog

Backtesting Polymarket: Check Data Before Simulating Fills

updated 2026-10-08 polymarket backtesting historical-data strategy research

By ZenHodl. Dataset documentation, research and model evaluations are linked in the article. A separate filtered ledger of bot-attributed trades, including losses and its admission rules, is public at /results.

A Polymarket backtest needs more than a price series. You need to know which quote was available when the rule fired, whether size was recorded reliably, and how you will simulate execution. Historical order book data helps answer some of those questions.

Here we check a real ZenHodl public CSV, show the Python output, and explain which assumptions still need testing. We sell the archives discussed below; the free file lets you inspect the fields before buying.

Prices replay decisions, not fills

Polymarket price history is a series of recorded prices, not a record of historical book states. The exchange's own documentation describes current order books as showing resting bids and asks, while price history supplies price observations over time (Polymarket market data docs). A backtest can walk those prices forward and ask what a rule would have decided at each timestamp. It cannot conclude that a decision became a fill at that price, because the historical bid/ask ladder and queue position are missing.

Recorded books can constrain spread and depth assumptions, but they do not establish your queue position or actual fills. A midpoint, a purchase at the displayed ask, and an order that consumes several price levels produce different simulations. State which rule you use, the assumed order size, and how you handle missing observations.

A free sample you can check

Before buying anything, download this public CSV and keep its given filename:

The Python check uses only the standard library and stops if the sample hash changes:

import csv
import hashlib
from decimal import Decimal
from pathlib import Path
from statistics import median

path = Path("poly_sports_archive_sample.csv")
expected = "196dc6397d4ff48cb9519da1de280b0f379a411931ac7cb952bf7217b31f4d33"
if hashlib.sha256(path.read_bytes()).hexdigest() != expected:
    raise ValueError("Sample changed; recheck the published result")
with path.open(encoding="utf-8-sig", newline="") as handle:
    rows = list(csv.DictReader(handle))
zero_sizes = sum(Decimal(r["bid_size"]) == 0 and
                 Decimal(r["ask_size"]) == 0 for r in rows)
gaps = [Decimal(r["ask"]) - Decimal(r["bid"]) for r in rows]
print("rows:", len(rows))
print("sports:", sorted({r["sport"] for r in rows}))
print("sample timestamps:", len({r["timestamp"] for r in rows}))
print("both displayed sizes zero:", zero_sizes)
print("median quoted gap (USDC/share):", median(gaps))

The checked output on the published file is:

rows: 400
sports: ['CFB']
sample timestamps: 4
both displayed sizes zero: 396
median quoted gap (USDC/share): 0.09

Checked on October 7, 2026, the file has 400 quote rows, all CFB, and four recorder timestamps from 2026-09-06T00:00:14.258490Z to 2026-09-06T00:01:44.557642Z. In 396 of the 400 rows, both displayed top sizes are zero. The median displayed ask-minus-bid gap is 0.09 USDC per share, or 9c. That is not a claim about Polymarket as a venue, a season, or the paid archive. It is a quality check on one short schema example, not a random sample.

The 396 zero-size rows cannot support an estimate of executable volume. They also do not establish that those markets had no resting liquidity. The schema documents size-validity limitations: a displayed size may belong to an earlier price, and a validity flag was not retained. This check does not identify the cause of each zero. The 9c median is a quoted gap in this file, not measured slippage or a trading fee.

Schema limits that constrain a backtest

The public schema records several limits that matter more than row counts:

A larger row count does not repair a missing clock or size-validity field. The recent sports archive contains this quote/L2 schema. The legacy historical archive is a separate product with independent venue tables; it is not a matched panel. Choose the product whose fields and recorded window fit the research question.

Extending the check to a paid archive

For the recent sports archive, inspect tokens.csv and days.csv before replaying a strategy:

  1. Check each token's observations, presence share and maximum gap. Presence share uses the recorder-cycle denominator described in the schema; it is not continuous exchange uptime.
  2. Use the daily record to distinguish readable files from missing or unreadable partitions.
  3. Declare a gap and freshness rule before computing results. If an observation is missing or too old for that rule, exclude it or report it separately. Forward-filling it does not establish an unchanged book.

The capture-gap guide explains the audit in more detail. These checks require the relevant archive files; the 400-row CSV does not establish their results.

Test the execution assumptions

Because the sample contains no fills, a backtest should not state that a strategy paid an exact fee, spread, or slippage number as if that number were measured. Use declared sensitivity scenarios. For example, run the same decision replay under several explicit cost assumptions: a flat cost per trade, a cost based on the recorded ask rather than midpoint, and a larger cost for larger assumed order sizes. Compare how the result changes.

Keep the input rows and decision rule fixed while changing the execution assumptions. Report which assumptions change the result. A result that survives only when costs are assumed away is a warning that the data cannot support the execution claim.

Ground truth and outcomes

For resolved Polymarket markets, the settlement outcome is separate from price history. Use a verified market settlement label when available, not a later probability model output. If a strategy uses a fair-probability estimate, document that the estimate was available at the decision time. Price data tells you which prices were recorded; it does not tell you what fair value was.

Try the schema before choosing a larger window

Start with the free CSV above and the other files on the samples page. If the recent sports schema fits, the two-day sports tryout provides a smaller archive with the same layout. Review its own dates and limitations before moving to a larger window.

Method note, October 7, 2026: DeepSeek assisted drafting. We reran the displayed Python check against the public CSV and reviewed the claims against the dated schema. The findings describe this sample; the paid archive was not recounted for this article.

Related reading

Get ZenHodl Weekly

Dataset releases, research notes, and public results, including corrections.

Research and dataset updates from ZenHodl.

Backtest with historical bid and ask snapshots

Review the archive window, score-join fields, known gaps, and Parquet sample.