How to read a Polymarket Up/Down outcome (and the Price To Beat) before the oracle settles

python dev.to

How to read a Polymarket Up/Down outcome (and the Price To Beat) before the oracle settles

Polymarket runs hugely popular short-window crypto markets — "Will BTC be Up or Down at the close?" — on BTC, ETH, SOL, XRP and DOGE, over 5m / 15m / 1h / 4h / 1d windows. If you automate them, two numbers decide everything:

  • Price To Beat (PTB) — the opening reference price. Final price > PTB → Up wins; otherwise Down.
  • Outcome — once the window closes, the market settles Up or Down.

Here's the part that surprises everyone: for crypto markets, neither number is in the public API in a way you can actually use to trade. This is where they live, and how to read the outcome about 20 seconds after close instead of waiting minutes for on-chain settlement.

The API gap is real (and specific)

Polymarket does expose an opening-price endpoint — but only for non-crypto:

GET https://polymarket.com/api/equity/price-to-beat/{slug}   # stocks, FX, gold, oil
Enter fullscreen mode Exit fullscreen mode

For crypto Up-or-Down windows there is no public PTB endpoint. And the settled Outcome? Neither the Gamma API (gamma-api.polymarket.com) nor the CLOB API (clob.polymarket.com) returns it quickly — on-chain Chainlink settlement can take 5–10 minutes for a 5-minute market. By then your window is long gone.

The outcome isn't in the HTML, either

The obvious move — curl the event page and parse it — doesn't work:

Source Can you read the outcome?
On-chain oracle (closed / outcomePrices) Authoritative, but lags by minutes
Raw HTML (curl the page) No — "Outcome: Up" isn't in the HTML, any XHR, or __NEXT_DATA__
Front-end render Yes — React shows it within ~20s of close ✅

The frontend computes the result client-side from a live oracle pipe and paints it into the DOM. It's never in a response you can fetch directly. So the reliable approach is to become the front-end.

Become the front-end: headless Chromium

Load the page in headless Chromium (Playwright), let React render, then poll the DOM inside the browser until the outcome text appears:

import asyncio
from polymarket_ptb_scraper import PolymarketScraper

async def main():
    async with PolymarketScraper(asset="btc", window="5m") as s:
        t_start = s.previous_window_start()
        r = await s.fetch(t_start)
        if r.success:
            print(f"{t_start}: {r.outcome.upper()}  PTB=${r.ptb}  final=${r.final_price}")

asyncio.run(main())
# BTC 1778839200: DOWN  PTB=80478.47  final=80470.32
Enter fullscreen mode Exit fullscreen mode

Three things make this fast and stable in production:

  1. In-browser pollingpage.wait_for_function() at 100ms looking for /Outcome:\s*(Up|Down)/, so it resolves the instant the text renders, not on a fixed Python-side timer.
  2. SPA navigation — Polymarket is Next.js, so page.goto(next_slug) is a client-side route change (JS/CSS stay cached) — roughly 5× faster than page.reload().
  3. Resource interception — block images/fonts/media and known analytics domains, but never block first-party JS/CSS or React won't render.

Add backoff-retry to a deadline and a periodic browser recycle (Chromium leaks a few MB per fetch) and it stays under ~300 MB RSS for hours.

The catch across timeframes: the slug

The scrape is the easy part. Building the URL is where most people get stuck, because the periods don't share a slug format:

Period Slug Derivable from a timestamp?
5m / 15m / 4h {asset}-updown-{w}-{t_start} (t_start % w == 0) ✅ yes
1h bitcoin-up-or-down-may-29-2026-10pm-et ❌ human-readable date slug
1d what-price-will-bitcoin-hit-on-may-29 ❌ different market shape

So a deterministic builder covers 5m/15m/4h, but 1h and 1d need a slug-discovery step (look the event up by date via the Gamma events API, then feed its slug to the scraper) — and the daily is a different price-target market type entirely.

The free build

The open-source version covers the 5m / 15m windows for BTC/ETH/SOL/XRP/DOGE — point it at a market, get back outcome, ptb, final_price:

https://github.com/BlueWhale-Quant-Lab/Polymarket-crypto-updown-PTB-Outcome-Scraper

It's a clean, dependency-light reference (Playwright only) and fully standalone — no API keys, no accounts.

When you need every coin and every timeframe

The free build stops at 5m/15m on purpose — that's the part you can derive from docs. When you hit the 1h / 4h / 1d markets you run into the slug-discovery problem above, plus the daily's noon-ET / different-market-shape edge cases. I packaged the complete version — all coins, all timeframes (5m / 15m / 1h / 4h / 1d) with the slug discovery and the period quirks handled — here:

Polymarket PTB & Outcome Scraper PRO — All Coins, All Timeframes

Good to know

  • The rendered label is an early indicator (~20s), not the final on-chain settlement. If correctness is critical, reconcile against the oracle once it lands — the free build covers the fast read completely; the on-chain reconcile is your call.
  • It reads public, already-rendered content. Keep request rates modest (12 fetches/hour per window has never been rate-limited in testing) and respect Polymarket's Terms of Service.
  • If Polymarket changes its markup, the outcome regex is the one place to update.
  • This is a data primitive — it reads PTB and outcome. It does not place trades or promise profit; what you build on top is yours.

Takeaway

For crypto Up/Down, the API won't hand you the PTB or a timely outcome — but the front-end already computes both, and you can read them ~20s after close by rendering the page yourself. The only real complexity is the slug across timeframes; solve that and you have a clean data feed for the whole BTC/ETH/SOL/XRP/DOGE board.

Source: dev.to

arrow_back Back to Tutorials