← All posts

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:

  1. Prices over time — the order book (best bid/ask, ideally L25 depth) at each tick, so you can model realistic fills.
  2. The resolution outcome — did the market resolve Up or Down?
  3. 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 ts order 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.