How to Backtest a Polymarket Trading Strategy in Python
A step-by-step guide to backtesting a Polymarket crypto Up-or-Down strategy in Python using polyReplay's historical L25 order book and Binance spot data, with copy-pasteable code.
By polyReplay team
Quick answer: to backtest a Polymarket strategy in Python, download historical order book + trade data as Parquet, load it with pandas, replay it in timestamp order, apply your entry/exit rules against the best bid/ask at each tick, and score the result against the market's final resolution. Below is a complete, runnable example using polyReplay data.
What you need
A useful prediction-market backtest needs three things per market:
- Prices over time — the order book (best bid/ask, ideally L25 depth) at each tick, so you can model realistic fills.
- The resolution outcome — did the market resolve Up or Down?
- The underlying spot price — if your signal references BTC/ETH price.
polyReplay packages all three in a single Parquet file per market, joined on timestamp, so you don't have to align feeds yourself.
Step 1 — Get the data
Grab a free API key from the dashboard, then download a market's Parquet file:
curl -L -H "Authorization: Bearer $POLYREPLAY_API_KEY" \
"https://polyreplay.dev/api/v1/datasets/<id>/slug/btc-updown-5m-1717804800" \
-o market.parquet
Step 2 — Load and inspect
import pandas as pd
df = pd.read_parquet("market.parquet").sort_values("ts").reset_index(drop=True)
print(df[["ts", "best_bid", "best_ask", "spot_price"]].head())
# Resolution is constant across the file:
winner = df["winner"].iloc[0] # "UP" or "DOWN"
Step 3 — Replay it without look-ahead
The golden rule: decide using the snapshot at time t, fill at the prices
quoted at time t — never peek at the resolution. Here's a tiny momentum
strategy: buy the UP token when spot has risen over the last minute and the ask
is still cheap.
position = None
entry_price = 0.0
trades = []
for _, row in df.iterrows():
spot_now = row["spot_price"]
spot_1m_ago = df.loc[df["ts"] <= row["ts"] - 60_000, "spot_price"]
if spot_1m_ago.empty:
continue
momentum = spot_now - spot_1m_ago.iloc[-1]
# Entry: momentum up and UP token still cheap
if position is None and momentum > 0 and row["best_ask"] < 0.55:
position = "UP"
entry_price = row["best_ask"] # we cross the spread to buy
entry_ts = row["ts"]
# Exit: take profit if the bid runs to 0.80
elif position == "UP" and row["best_bid"] > 0.80:
trades.append(row["best_bid"] - entry_price)
position = None
# If still holding at resolution, settle at 1.0 (UP win) or 0.0 (UP loss)
if position == "UP":
payout = 1.0 if winner == "UP" else 0.0
trades.append(payout - entry_price)
Step 4 — Score it
import numpy as np
pnl = np.array(trades)
print(f"trades: {len(pnl)}")
print(f"total PnL per share: {pnl.sum():.3f}")
print(f"win rate: {(pnl > 0).mean():.1%}")
Step 5 — Scale to many markets
Loop the same logic over every Parquet file for a token/timeframe. List them via the API:
import requests
H = {"Authorization": f"Bearer {API_KEY}"}
markets = requests.get(
"https://polyreplay.dev/api/v1/markets",
params={"token": "BTC", "timeframe": "5m", "limit": 1000},
headers=H,
).json()["data"]
# download + backtest each, then aggregate PnL across the whole set
Doing it the easy way: the SDK
The polytradrr Python SDK (pre-release) lets you write a strategy once and
run it in backtest, paper, or live mode without changing the code:
from polytradrr import Client, Mode
client = Client(mode=Mode.BACKTEST, api_key="pr_xxx")
async for ev in client.events():
... # same handler runs live later
See the SDK docs.
Common pitfalls
- Look-ahead bias — never fill at the resolution price; fill at the book
quoted at decision time. Replaying in
tsorder is what protects you. - Ignoring the spread — buying means crossing to the ask, not the mid.
- Survivorship — backtest the full set of markets, not just the ones you remember being interesting.
Ready to try it? Grab a free key and pull your first market, or browse the full dataset catalog.
Frequently asked questions
How do I backtest a Polymarket strategy in Python?
Download historical Polymarket order book and trade data as Parquet (for example from polyReplay), load it into pandas or Polars, replay it row by row in timestamp order, apply your entry and exit rules against the best bid/ask, and tally the resolved-market outcome to compute PnL. The key is using point-in-time order book data so you never look ahead.
What data do I need to backtest prediction markets?
At minimum you need historical prices over time for each market and the final resolution outcome. For realistic fills you also want the order book (best bid/ask, and ideally L25 depth) so you can model slippage, plus the underlying spot price if your signal depends on it. polyReplay packages all of these together per market.
How do I avoid look-ahead bias when backtesting Polymarket?
Only ever use information available at or before the current simulated timestamp. Replay the data in strict timestamp order, make decisions using the order book snapshot at time t, and fill at the prices quoted at time t — never at the market's final resolution price. Joining each market with point-in-time Binance spot keeps your signal honest too.