Documentation Index
Fetch the complete documentation index at: https://docs.polydata.live/llms.txt
Use this file to discover all available pages before exploring further.
This guide walks through building a simple mean-reversion backtest using PolyData.
What you’ll build
A strategy that:
- Buys when YES price drops below 0.45
- Sells when YES price rises above 0.55
- Uses real orderbook depth to compute realistic fills
Setup
import requests
import pandas as pd
from datetime import datetime, timedelta, timezone
API_KEY = "pd_live_xxxxx"
BASE = "https://api.polydata.live"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
def fetch_ticks(asset_id, start_ms, end_ms):
"""Fetch all tick data for a market in a time range."""
resp = requests.get(
f"{BASE}/v1/ticks",
params={
"asset_id": asset_id,
"start": start_ms,
"end": end_ms,
"event_type": "price_change",
"limit": 100000,
},
headers=HEADERS,
)
return resp.json()["data"]
def get_execution_price(asset_id, side, amount, timestamp_ms):
"""Compute realistic fill price using historical orderbook depth."""
resp = requests.get(
f"{BASE}/v1/execution-price",
params={
"asset_id": asset_id,
"side": side,
"amount": amount,
"timestamp": timestamp_ms,
},
headers=HEADERS,
)
return resp.json()["data"]
Choose a market
Pick a market with reasonable volatility:
ASSET_ID = "13915689317269078219168496739008737517740566192006337297676041270492637394586"
# Time range: April 15, 2026, 8am UTC
START = int(datetime(2026, 4, 15, 8, tzinfo=timezone.utc).timestamp() * 1000)
END = int(datetime(2026, 4, 15, 9, tzinfo=timezone.utc).timestamp() * 1000)
Pull tick data
ticks = fetch_ticks(ASSET_ID, START, END)
df = pd.DataFrame(ticks)
df["t"] = pd.to_datetime(df["t"], unit="ms")
df = df.set_index("t")
print(f"Loaded {len(df):,} ticks")
print(df[["best_bid", "best_ask"]].head())
Build the strategy
class MeanReversionStrategy:
def __init__(self, buy_below=0.45, sell_above=0.55, position_size=100):
self.buy_below = buy_below
self.sell_above = sell_above
self.position_size = position_size
self.position = 0
self.cash = 10000
self.trades = []
def on_tick(self, ts, mid_price):
if mid_price < self.buy_below and self.position == 0:
# Buy with realistic fill
fill = get_execution_price(ASSET_ID, "BUY", self.position_size, int(ts.timestamp()*1000))
cost = fill["average_price"] * fill["filled_amount"]
self.cash -= cost
self.position += fill["filled_amount"]
self.trades.append({"t": ts, "side": "BUY", "price": fill["average_price"], "size": fill["filled_amount"]})
elif mid_price > self.sell_above and self.position > 0:
# Sell with realistic fill
fill = get_execution_price(ASSET_ID, "SELL", self.position, int(ts.timestamp()*1000))
proceeds = fill["average_price"] * fill["filled_amount"]
self.cash += proceeds
self.position -= fill["filled_amount"]
self.trades.append({"t": ts, "side": "SELL", "price": fill["average_price"], "size": fill["filled_amount"]})
Run the backtest
strat = MeanReversionStrategy()
# Sample the price every 5 seconds (avoid running on every tick)
df["mid"] = (df["best_bid"] + df["best_ask"]) / 2
sampled = df.resample("5s")["mid"].last().dropna()
for ts, mid in sampled.items():
if pd.notna(mid):
strat.on_tick(ts, mid)
print(f"Final cash: {strat.cash:.2f}")
print(f"Open position: {strat.position}")
print(f"Trades: {len(strat.trades)}")
Analyze results
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(12, 5))
df["mid"].plot(ax=ax, label="Mid price", alpha=0.5)
trades_df = pd.DataFrame(strat.trades).set_index("t")
buys = trades_df[trades_df["side"] == "BUY"]
sells = trades_df[trades_df["side"] == "SELL"]
ax.scatter(buys.index, buys["price"], color="green", marker="^", s=100, label="Buy")
ax.scatter(sells.index, sells["price"], color="red", marker="v", s=100, label="Sell")
ax.legend()
plt.show()
Key concepts
Use realistic fills
Don’t backtest at mid_price or best_bid/best_ask. The /v1/execution-price endpoint walks the actual historical orderbook to compute what you’d really fill at, accounting for:
- Order size impact
- Available depth at each price level
- Total slippage
Match data resolution to your strategy
- HFT (millisecond): Use raw ticks, no resampling
- Intraday (seconds–minutes): Sample at 1-5s intervals
- Swing trading (hours+): Use OHLCV candles directly
Don’t peek into the future
When making decisions at time t, only use data with timestamp <= t. PolyData’s APIs are designed around this — if you pass timestamp=T, you get the latest snapshot at or before T.
Next steps