Backtesting Prediction Markets: What 1,319 Resolved Polymarket Markets Show
Prediction markets are an unusually clean thing to study. Every market is a contract that pays 1 if an event happens and 0 if it doesn't, so the price is a probability and the eventual outcome is unambiguous ground truth. Before you backtest a strategy on this data, the first question to answer is whether the prices are any good in the first place — because that determines where an edge could even come from.
So we measured it. This post reports the real numbers from 1,319 of the most-liquid resolved Polymarket markets (146,556 daily on-chain observations), then shows how to backtest a strategy on top of them without the look-ahead and survivorship mistakes that invalidate most first attempts.
We use Polymarket throughout because it settles on-chain — every fill is recorded on the blockchain, so the history is real traded prices, not a thin API sample. ImpliedData's Polymarket on-chain tick history covers 404M fills (2023–present).
Method. We took the most-liquid resolved Polymarket markets, used their daily on-chain candles (YES-normalized to a 0–1 probability), and took each market's realized outcome from its on-chain settlement — the final daily close, which converges to 1 (YES) or 0 (NO) at resolution. 96% of liquid resolved markets settle cleanly to one or the other; the few that don't were excluded. Outcome base rate in the sample: 19.8% YES. Observations within a market are autocorrelated, so treat the n of observations as larger than the effective sample — the market count (1,319) is the conservative figure.
Result 1 — Polymarket is well calibrated
Calibration asks a simple question: when the market says 30%, does the thing happen about 30% of the time? Bucket every observation by its price and compare to the realized rate:
| Market price | Realized YES rate | Observations |
|---|---|---|
| 0.0 | 1.0% | 82,501 |
| 0.1 | 7.8% | 24,139 |
| 0.2 | 18.8% | 9,992 |
| 0.3 | 29.1% | 6,984 |
| 0.4 | 35.8% | 5,054 |
| 0.5 | 45.9% | 3,771 |
| 0.6 | 57.6% | 3,469 |
| 0.7 | 60.1% | 2,905 |
| 0.8 | 68.1% | 2,459 |
| 0.9 | 85.4% | 2,994 |
| 1.0 | 99.3% | 2,288 |
That is a strikingly diagonal reliability curve. The crowd's stated probability tracks the realized frequency closely across the whole range.
But look at the 0.5–0.9 band: a price of 0.7 resolves YES only 60% of the time, 0.8 resolves 68%, 0.9 resolves 85%. The market mildly overprices likely-YES outcomes — it's a touch too confident on favorites in that range. It's a small, consistent bias, and exactly the kind of thing a calibrated bettor (or a backtest) would want to probe further before trusting it, since pooled observations overstate significance.
You can reproduce the table on any slice of the data:
import pandas as pd
candles = pd.read_parquet("polymarket_ohlcv_1h_sample.parquet")
lookup = pd.read_parquet("sample_markets_lookup.parquet")
df = candles.merge(lookup[["market_id", "title", "status", "resolution"]], on="market_id")
resolved = df[df.status == "resolved"].copy()
resolved["y"] = (resolved.resolution == "Yes").astype(int)
resolved["bin"] = (resolved.close * 10).round() / 10
print(resolved.groupby("bin").agg(realized=("y", "mean"), n=("y", "size")))
Result 2 — prices sharpen toward resolution
Calibration says the prices are unbiased; the Brier score (mean squared error between price and outcome — lower is sharper) says how informative they are, and how that changes as the event nears:
| Days before resolution | Markets | Brier score |
|---|---|---|
| 30+ | 1,029 | 0.073 |
| 14–30 | 1,151 | 0.057 |
| 7–14 | 1,262 | 0.061 |
| 4–7 | 1,309 | 0.060 |
| 2–4 | 1,296 | 0.040 |
| 1–2 | 1,276 | 0.019 |
For reference, a no-skill forecaster that always guesses the base rate (0.198) scores a Brier of 0.159. Even a month out, the market is more than twice as sharp as that. The score then sits around 0.06 from two weeks out — and collapses in the final few days, to 0.019 inside the last 1–2 days, as information arrives and the price commits.
(The middle of the curve is roughly flat rather than perfectly monotonic — that's a composition effect, since long-dated and short-dated markets populate different buckets. The unmistakable signal is the hard sharpening in the final days.)
What this means for a backtest
A well-calibrated, sharpening market is bad news for naive strategies and good news for honest ones. If the price already reflects the true probability on average, a simple directional rule ("buy when it's been going up") has no free lunch — you're betting against a forecaster that's right on average. An edge has to come from something the single price doesn't already encode:
- a systematic bias like the 0.5–0.9 overpricing above (small, and you'd have to beat costs);
- mispriced tails or specific event types where the crowd is thin;
- execution — being faster than the price commits in those final sharpening days;
- cross-platform divergence — the same event priced differently on another venue (more below).
Backtesting without fooling yourself
Whatever the strategy, the mechanics decide whether the result is real. Four mistakes invalidate most backtests:
import numpy as np
def backtest_momentum(g, lookback=6):
g = g.sort_values("timestamp").copy()
g["mom"] = g.close - g.close.shift(lookback)
g["signal"] = np.sign(g["mom"]).shift(1) # act on the NEXT bar
g["pnl"] = g.signal * (g.close.shift(-1) - g.close)
return g
1. Look-ahead bias. The signal must act on the next bar (shift(1)), and PnL is the move after you enter (close.shift(-1) - close). If your backtest looks too good, you almost certainly scored a signal against the candle that produced it, or leaked the outcome into a feature.
2. Survivorship / selection. Prediction markets have a long tail of low-volume markets that barely trade. Backtest only the famous ones and your results won't generalize to what you can actually trade. Filter by liquidity deliberately — and report the filter (we used the most-liquid resolved markets above, and said so).
3. Resolution mechanics. Near settlement the price snaps to 0 or 1. A rule that "buys at 0.98 and holds to settlement" books a tiny, near-riskless gain that dominates naive PnL but is mostly unexecutable at size. Decide explicitly whether you're modelling the run-up or the settlement.
4. Liquidity and the spread. Candle close is a mid-ish print, not a fill. Real entries pay the spread and move the book on thin markets. Use the tick-level OrderFilled data to estimate realistic fills before trusting any PnL.
Going further: the same event on more than one venue
The most interesting prediction-market signals are cross-platform. The same real-world event often trades on Polymarket and another venue at the same time, and because you can't arbitrage a real-money market against a play-money one, a persistent gap between them is information rather than a closing trade. ImpliedData ships these matches as event links — the same event aligned across Polymarket, Manifold & Myriad, polarity-normalized so YES means the same thing on both sides. That divergence is an edge a single price can't show you. See how the event links work and our real-money vs. play-money study.
Get the data
- Free sample — candles + a join table for 130 markets, on the data page or HuggingFace. Enough to run the calibration code above at small scale.
- Full history — every market, intraday 1m–1d candles, the Polymarket on-chain tick archive (404M fills, 2023–present), resolved-outcome labels, and the cross-platform event links, via the API or bulk Parquet/CSV at implieddata.com.
Everything is YES-normalized into one schema, so the code above runs unchanged across platforms — swap the file and it still works.