AymarData
Backtest · Polymarket 15m

Polymarket 15 Minute Backtest: Tick-Level Up/Down Data

Fifteen-minute Up/Down windows are the default research interval — 96 independent markets per day with enough path for mid and imbalance signals.

Each 15m window is its own market. Backtests that treat the family slug as a single series will mix regimes; always iterate concrete window ids.

Use btc-updown-15m as the starter family, then expand to ETH/SOL. Platform Polymarket total ~207.0M messages since 2025-06-01.

Inputs

What you need for a 15-minute Polymarket backtest

What a serious backtest needs beyond candles.

96 windows/day structure

Open on quarter-hour boundaries. Sample once near the open or on a schedule inside the window — document which.

Book + settlement pair

Decision from /v1/book; P&L from /v1/settlement. Optional /v1/trades for flow filters.

Cross-venue spot (optional)

Join Binance BTCUSDT when measuring lag between spot and Up price.

Coverage

Live 15-minute coverage

Exact BTC/ETH/SOL/XRP 15m event counts are on the matching /data/polymarket/*-updown-15m pages.

Windows / day / coin

96

15-minute family

BTC 15m data page

Event rates

/data/polymarket/btc-updown-15m

Polymarket total

~207.0M

As of

2026-10-25

Code

End-to-end 15-minute backtest (REST + requests)

Family btc-updown-15m — mid threshold strategy with settlement check.

import os
import requests

API = "https://api.aymardata.com/v1"
KEY = os.environ["AYMARDATA_API_KEY"]  # export AYMARDATA_API_KEY=ad_live_…
H = {"Authorization": f"Bearer {KEY}", "User-Agent": "aymardata-backtest/1.0"}

def list_windows(q: str, interval: str, limit: int = 40):
    r = requests.get(
        f"{API}/markets",
        headers=H,
        params={"source": "polymarket", "q": q, "interval": interval, "limit": limit},
        timeout=30,
    )
    r.raise_for_status()
    # De-dupe Up/Down rows that share the same window slug
    seen, out = set(), []
    for row in r.json()["data"]:
        m = row["market"]
        if m in seen:
            continue
        seen.add(m)
        out.append(row)
    return out

def book(market: str, outcome: str, at: str, depth: int = 5):
    r = requests.get(
        f"{API}/book",
        headers=H,
        params={
            "source": "polymarket",
            "market": market,
            "outcome": outcome,
            "at": at,
            "depth": depth,
        },
        timeout=30,
    )
    r.raise_for_status()
    return r.json()

def settlement(market: str):
    r = requests.get(
        f"{API}/settlement",
        headers=H,
        params={"source": "polymarket", "market": market},
        timeout=30,
    )
    if r.status_code == 404:
        return None
    r.raise_for_status()
    return r.json()

def mid(snap):
    bids, asks = snap.get("bids") or [], snap.get("asks") or []
    if not bids or not asks:
        return None
    return (bids[0]["price"] + asks[0]["price"]) / 2

THRESHOLD = 0.45
rows = []
for w in list_windows("btc-updown-15m", "15m"):
    market = w["market"]
    hist = w.get("history_from")
    if not hist:
        continue
    # Sample ~60s after history starts
    at = hist.replace("+00:00", "Z")
    if at.endswith("Z") is False and "T" in at:
        at = at + ("Z" if "+" not in at else "")
    snap = book(market, "up", at if "T" in at else hist)
    m = mid(snap)
    if m is None or m >= THRESHOLD:
        continue
    # Buy Up at the ask; score later against settlement when resolved
    entry = (snap["asks"] or [{}])[0].get("price")
    sett = settlement(market)
    won = None
    if sett:
        outcome = str(sett.get("outcome") or sett.get("winner") or "").lower()
        won = outcome in ("up", "yes", "1")
    rows.append({"market": market, "mid": m, "entry": entry, "won": won})

# Counts only — label any P&L/win-rate/edge you add:
# "Illustrative output — not a real result"
print(f"signals={len(rows)} sample={rows[:3]}")

API base https://api.aymardata.com. Set AYMARDATA_API_KEY from your dashboard. Examples use requests. Any P&L, win-rate, or edge figure derived from these snippets is illustrative output — not a real result.

Free samples

Download sample CSV / Parquet

No key required — small files for schema and local smoke tests.

FAQ

Common questions

Why 15 minutes instead of 5?

Fifteen minutes balances sample count with time to act on a signal. Use the 5-minute page when latency and final-minute collapse are the research target.

Related

Data pages and guides

Catalog leaves for schema and samples; guides for longer walkthroughs.

Run your first backtest with real ticks

Get a key, paste the snippet above, then expand to the full catalog.

Educational content, not trading or investment advice. Past data doesn't predict future results.