DEV Community

SteadyScrape
SteadyScrape

Posted on

Tracking Prediction Market Odds With Python: Polymarket Prices as a Clean Time Series

Prediction markets have become a common data source for journalists, analysts and dashboards. "Markets give a 93% chance the Fed holds rates" is a more concrete line than "economists expect". The data is public, but going from "I want the odds" to "I have a clean table I can chart every hour" takes more work than you'd expect.

This post walks through it in Python: reading Polymarket's public market data, the quirks that trip people up, and building a time series. At the end there's a hosted option if you'd rather not maintain it yourself.

Step 1: read markets from the Gamma API

Polymarket publishes market metadata through a public, no-login API (Gamma). A first pass looks like this:

import json, requests

def fetch_markets(limit=100, offset=0):
    r = requests.get("https://gamma-api.polymarket.com/markets", params={
        "active": "true", "closed": "false", "limit": limit, "offset": offset,
    }, timeout=30)
    r.raise_for_status()
    return r.json()

rows = []
for m in fetch_markets():
    outcomes = json.loads(m.get("outcomes") or "[]")        # e.g. '["Yes", "No"]'
    prices = [float(p) for p in json.loads(m.get("outcomePrices") or "[]")]
    rows.append({
        "question": m["question"],
        "yes": prices[0] if prices else None,
        "volume": float(m.get("volume") or 0),
        "liquidity": float(m.get("liquidity") or 0),
        "closes": m.get("endDate"),
        "outcomes": dict(zip(outcomes, prices)),
    })

for r in sorted(rows, key=lambda r: -r["volume"])[:5]:
    print(f'{r["yes"]:.3f}  {r["question"][:70]}')
Enter fullscreen mode Exit fullscreen mode

Step 2: handle the quirks

That works for a demo. For real use, here's what you'll run into:

  • Prices and outcomes are JSON strings inside JSON. outcomePrices is '["0.93", "0.07"]', not a list of numbers. Parse it twice.
  • Events vs markets. "Fed decision in October" is one event made of several yes/no markets (no change, cut 25 bps, hike 25 bps, …). If you want "the probability of each outcome", group markets by event.
  • Paging. There are thousands of open markets. You need offset paging and a stopping rule.
  • Thin markets lie. A market with $300 of liquidity and a 20-cent spread doesn't tell you much. Keep liquidity and the bid/ask spread, and filter on them.
  • Keyword search is loose. Searching "rate" matches every market that mentions a rate of anything. You'll want your own strict word matching afterwards.
  • History is a separate API. The current price is on the market, but a price series needs a per-market call to the CLOB prices-history endpoint, keyed by token ID.

Step 3: build a time series

The simplest robust approach is a snapshot: run on a schedule, append the markets you care about to a table, and chart it.

import csv, datetime as dt

def snapshot(rows, path="odds.csv"):
    now = dt.datetime.now(dt.timezone.utc).isoformat(timespec="minutes")
    with open(path, "a", newline="") as f:
        w = csv.writer(f)
        for r in rows:
            w.writerow([now, r["question"], r["yes"], r["volume"], r["liquidity"]])
Enter fullscreen mode Exit fullscreen mode

Run it hourly with cron and you have your own dataset, one that also keeps markets after they resolve.

The hosted version

Disclosure: we (SteadyScrape) built the tool below and get paid when it's used.

If you don't want to own the paging, parsing and history calls, Polymarket Scraper & Odds API (steady-scrape/prediction-markets) is an Apify Actor that reads the same official public APIs (Gamma, Data API and CLOB) and returns one flat row per market: question, every outcome with price and token ID, impliedProbability, best bid and ask, spread, 24-hour and 1-week price change, volume, liquidity, open interest, close date, status and the result once resolved. It has strict keyword search (every word has to match; quoted phrases are exact), normalized categories, filters for closing soon, minimum volume and minimum liquidity, and optional daily or hourly price history.

import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("steady-scrape/prediction-markets").call(run_input={
    "searchTerms": ["fed rates"],
    "maxItems": 20,
    "includePriceHistory": True,
    "priceHistoryDays": 30,
})
for m in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(f'{m["impliedProbability"]:.1%}  spread {m["spread"]}  {m["question"]}')
Enter fullscreen mode Exit fullscreen mode

Here's what a "fed rates" search with maxItems: 10 returned in one of our test runs on November 9, 2026 (the odds will have moved by the time you read this):

Market Implied probability Volume (USD) Spread
Will the Fed increase interest rates by 25 bps after the December 2026 meeting? 79.5% 5.2M 0.01
Will there be no change in Fed interest rates after the December 2026 meeting? 18.5% 4.2M 0.01
Will no Fed rate cuts happen in 2026? 97.95% 9.5M 0.001
Will 1 Fed rate cut happen in 2026? 0.55% 3.7M 0.001
Will 2 Fed rate cuts happen in 2026? 0.65% 3.6M 0.001

The run took about 4 seconds. The first two rows belong to the "Fed Decision in December?" event and the rest to "How many Fed rate cuts in 2026?", which is why eventTitle matters when you group results.

Each row also includes eventTitle and outcomeLabel, so multi-outcome events group cleanly, plus url back to the market. Other useful inputs: categories (politics, economy, crypto, sports, …), closingWithinDays, minVolume, minLiquidity, and marketIds, which accepts pasted market or event URLs. An event URL returns every market in the event.

Some limits to know about. It's Polymarket only right now (Kalshi is listed in the input but currently unavailable, and runs say so in their summary). Data is read live when the run starts, so for a time series you schedule it. It's meant for research, journalism, dashboards and personal analysis. SteadyScrape isn't affiliated with Polymarket; if you plan to redistribute the data commercially, read Polymarket's own terms first.

What it costs

$1 per 1,000 markets returned ($0.001 per market) on Apify's free plan, and $0.0009 to $0.0007 per market on paid Apify plans (Bronze and up), plus Apify's $0.00005 start fee per run. Price history is included in the per-market price, and references that aren't found return no row and aren't charged.

Use Markets per run Monthly cost (free-plan price)
A daily snapshot of the 50 markets you follow 50 ~$1.50
The same 50 markets every hour 50 ~$36
A one-off pull of 5,000 open markets 5,000 $5 (once)

Hourly tracking of a short watchlist is where most dashboards end up. Cap runs with maxItems if you want a hard ceiling.

Whichever route you take, DIY or hosted, keep the liquidity and spread columns next to the probability. A 95% in a deep market and a 95% in a $500 market mean very different things.

Top comments (0)