← Back to blog

Backtesting Polymarket Strategies: Tools, Datasets, and the Depth Data Problem

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

By the ZenHodl team — we run the trading bots this blog writes about, and the qualifying live-position record, including losses, is public with its admission rules at /results.

Historical prices can replay a strategy's decisions; they do not replay its execution. Polymarket's free CLOB price-history API supplies recorded prices, without the historical bid/ask ladder or an execution record. A useful backtest must declare how it models spread, depth, fees and latency. Recorded book data can constrain those assumptions, but cannot prove how your own order would have filled.

This post walks through what you can do with the free price-history data, how to approximate slippage when you cannot measure it, and when the approximation is no longer enough — at which point you either capture depth data yourself going forward, or buy a dataset that already captured it.

What You Need for a Useful Polymarket Backtest

A real backtest has four ingredients:

  1. A strategy. The signal logic, the entry/exit rules, the sizing.
  2. Historical prices for the markets the strategy would have traded.
  3. A model of transaction costs — fees, slippage, latency penalties.
  4. A ground-truth outcome for every market traded (did YES or NO win?).

For Polymarket, ingredients 1 and 4 are entirely yours. Ingredient 2 is partially free via the CLOB API. Ingredient 3 is the part most amateur backtests get wrong, and it is the part that produces the wide gap between backtest results and live results.

The Free Option: CLOB Price-History API

Polymarket's CLOB exposes a historical price endpoint:

GET https://clob.polymarket.com/prices-history?market={token_id}&interval=max&fidelity={N}

{token_id} identifies the YES or NO asset to query. {interval} selects the requested time interval. Polymarket documents {fidelity} in minutes, with a default of one minute: fidelity=5 requests five-minute accuracy, not five-second bars. Inspect returned timestamps rather than assuming perfectly spaced observations. Official price-history parameter reference.

The response is a historical price series with timestamps and prices. It does not supply historical depth ladders or an execution record. Do not treat it as a replay of bids, asks, queue position or fills, and do not assume every returned price has independently verified midpoint semantics.

This is enough to do basic strategy backtesting. You walk the time series forward, evaluate your signal at each timestamp, and record what your strategy would have decided. For each "trade" your strategy generates, you compare your entry price to the market's eventual resolution and compute P&L.

For our own backtest scripts — backtest_moneyline_wp.py, backtest_cs2_combined.py, backtest_soccer_wp.py, backtest_tennis_wp.py, backtest_spread_total_wp.py — price history with fidelity=5 has been used to replay strategy logic. That parameter requests five-minute accuracy; it does not demonstrate five-second observations. The replay is a simulation whose clock and cost assumptions must be documented separately.

What the Free Data Cannot Tell You

The fundamental limit of price-only backtesting: you do not know how your order would have executed.

A mid-price of 55c does not mean you could have bought at 55c. The orderbook at that moment might have been 53c bid / 57c ask — a 4-cent spread, half of which you would have eaten as a taker. Or it might have been 54.5c / 55.5c — a 1-cent spread costing you essentially nothing. The midpoint is the same in both cases; your actual fill cost is very different.

If your strategy depends on small edges (anything under 5 cents of expected value per trade), the spread-cost uncertainty can flip your backtested P&L from profitable to unprofitable. This is the single biggest source of backtest-vs-live divergence for serious quant strategies.

A second problem: book depth. Even if the inside spread is tight, depth might be thin. Your hypothetical 500-share order at 55c might fill 50 shares at 55c and then move the price to 58c for the next 450 shares. A backtest that assumes 500 shares at 55c is overstating your edge by a meaningful amount.

There are three ways to handle this gap, in increasing order of fidelity.

Approach 1: Assume Conservative Slippage

The cheapest approach: add a fixed slippage cost to every trade in your backtest. Common values:

Our backtest scripts use a SLIPPAGE_C constant (typically 1c, with sport-specific overrides where book thinness justifies more) and add a latency penalty (0.5-2c) on top. The total transaction cost per trade is taker_fee_c + slippage_c + latency_penalty_c. We covered this formula in our Polymarket fees explained post.

Pros: Simple, computationally cheap, no extra data required.

Cons: It is an assumed cost, not a measured fill. A constant does not capture market variation, order size or queue position, and need not be right in aggregate. For strategies where individual trade sizing matters, this is too coarse.

A fixed-cost scenario can be useful for early research, but its assumption can change the directional answer. Report sensitivity to a range of plausible costs instead of treating the estimate as observed execution.

Approach 2: Estimate Slippage From Price-Move Patterns

Another approach is to vary assumed slippage with observed price changes. This is a cost scenario, not a measurement of historical depth or fills. With five-minute requested accuracy, this price series cannot establish second-by-second movement after a trade.

You can build a per-market cost scenario from the standard deviation of returned price changes over declared windows. Higher observed volatility can be assigned higher assumed costs. Compare it with a flat-cost scenario; the price series alone cannot validate either against actual slippage.

Pros: No extra data cost. Captures some per-market variation.

Cons: Still an estimator. You are inferring book depth from price stability, which is correlated but not equivalent. Misses fast-moving markets where the price stayed apparently steady because both sides had deep liquidity at the same price.

This is the right approach for mid-stage strategy refinement — you have an idea you believe has edge and you want a better estimate of how that edge survives execution.

Approach 3: Use Actual Historical Orderbook Depth

For questions about recorded spreads and resting liquidity, you need historical bid/ask observations and the relevant depth fields, not just a price series. Polymarket does not expose this through any free API. If you want it, you have to either capture it yourself going forward (months of WebSocket logging) or buy a dataset that captured it for you.

Choose the recorded fields that match the question:

The newer Polymarket sports captures contain no trade prints. Quotes are sampled from selected markets; a price can be up to 300 seconds old without an explicit quote-age column, and displayed top sizes can be zero or stale. The separate L2 rows can repeat the last full book for up to 300 seconds and retain at most 20 levels per side. Use their recorded sizes with these freshness limits when studying resting depth. Missing observations are not unchanged quotes.

Recorded levels can constrain hypothetical order-walk and spread-cost scenarios. They cannot establish queue position, actual fills, realized slippage or performance after costs. Those require an execution record or clearly declared simulation assumptions. For Kalshi's separate depth and captured prints, inspect the Kalshi historical-data overview; exchange trade time and retrieval-time sports context are different clocks.

Inspect a Polymarket backtesting dataset before buying

Download the free Polymarket sports quote CSV into your working folder and read the matching schema and capture limits. This Python example uses only the standard library and inspects five recorded quote rows:

import csv
from itertools import islice
from pathlib import Path

with Path("poly_sports_tryout_sample.csv").open(encoding="utf-8", newline="") as file:
    reader = csv.DictReader(file)
    required = {"timestamp", "token_id", "bid", "ask", "spread"}
    if not required.issubset(reader.fieldnames or []):
        raise ValueError("Unexpected columns; check the sample version and schema")
    for row in islice(reader, 5):
        print(row["timestamp"], row["token_id"],
              "bid", row["bid"], "ask", row["ask"],
              "recorded_spread", row["spread"])

timestamp is the recorder's sample time, not the exchange time of the quote. bid, ask and spread use 0–1 USDC-per-share units. These CSV rows are top quotes, not per-level L2 ladders, and displayed size does not establish an executable fill. A sample alone cannot measure whole-archive coverage, quote age or a strategy's performance.

If the fields fit your study, use the $9 two-day tryout to test the paid Parquet layout and separate depth files. Then compare the $149 sports archive for a larger recorded window. The free CSV is not the full paid tryout, and neither download supplies ESPN probabilities or official sporting-event timestamps; those require separate sources and verified joins.

Choosing Your Strategy's Ground Truth

The fourth ingredient — outcome data — is straightforward for Polymarket: every resolved market has a definitive YES or NO outcome from the UMA oracle, accessible through the market metadata.

A win-probability model provides an estimate, not a realized outcome label. A calibrated probability API can be a model input, while verified market settlement supplies the outcome used to evaluate predictions. For a historical study, establish that each estimate was available at the decision time and avoid treating later model outputs as prior information.

If your strategy is "buy when fair prob > market price + N cents," you need both: the market price (from Polymarket data) and a fair probability source (from a model). Backtests of trading strategies without a fair-probability anchor are really just market-following strategies, which is a very different thing.

A Worked Backtest Outline

A skeleton of how our Polymarket backtests are structured:

  1. Pull resolved markets for the sport and date range you want to test. Get token IDs, settlement outcomes, game start/end times.
  2. For each market, request price history with fidelity=5 — requested five-minute accuracy, using the token ID and explicit query bounds. Inspect returned timestamps and record the actual observations available before each simulated decision.
  3. Pull the corresponding fair probability series from your model (or our API) — one estimate per timestep, aligned to the price series.
  4. Walk the joint series forward. At each timestep, compute edge = fair_prob - market_price. If edge crosses your threshold and other entry criteria are met, record a trade with timestamp, entry price, fair probability, and edge.
  5. Apply transaction costs to each trade entry: effective_entry = market_price + slippage_c + taker_fee_c + latency_penalty_c. Hold to settlement.
  6. Compute P&L at settlement: pnl = ($1 if won else $0) - effective_entry. Aggregate across all trades for win rate, ROI, edge realized.
  7. Compare to expected: was your average realized edge close to your average expected edge? If not, your slippage model is off and your live P&L will differ from your backtest by that gap.

A backtest that reports +20c per trade with no slippage model is not the same as a backtest that reports +8c per trade with realistic slippage. The second is closer to live reality and is the number to make deployment decisions against.

What to Watch For in Your Own Backtest

Common pitfalls when backtesting Polymarket strategies:

Choose data for the research question

Historical prices can support a replay of decision rules. Recorded bid/ask quotes and per-level depth can constrain spread and liquidity assumptions. Neither supplies actual fills or proves that a strategy's edge survives costs; a simulated result depends on its declared model, clocks and data selection.

Use the sample-inspection example above to check whether the current sports dataset has the fields your study needs before paying for more coverage.

For the older two-venue capture, review the $150 legacy archive and its separate physical tables. The Kalshi historical-data overview covers Kalshi's sampled L2 and captured prints. All free samples and schemas let you compare the contracts before buying.

Related deeper reads: - Where to get historical Polymarket & Kalshi order-book data — an honest vendor-by-vendor comparison. - Polymarket Fees Explained — the cost side of every trade. - Polymarket Paper Trading — when to backtest vs shadow trade vs live test. - Polymarket API Documentation — the API surfaces that produce backtest data. - How We Profit on Polymarket — backtest-to-live realization on real trades.

Related reading

Get ZenHodl Weekly

One weekly email with live results, one model insight, and product updates.

Tuesday mornings. No spam.

Backtest with historical bid and ask snapshots

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