DEV Community

manja316
manja316

Posted on • Edited on • Originally published at dev.to

Building a free Polymarket screener: how I turned 13,963 markets into a single scannable page

Polymarket has thousands of markets. Their UI is built for depth on a single market — bet slip, orderbook, charts — not for scanning across the universe. If you want to ask "what dropped 20pp overnight" or "which crypto markets are sub-20¢ with real volume," there's no built-in view.

So I built one. It's free, it's static, it rebuilds from the public Gamma API every few hours, and the source is on GitHub. This post is how it works.

The data layer

The Polymarket Gamma API (https://gamma-api.polymarket.com/markets) is paginated and unauthenticated. The "active universe" is much smaller than the lifetime market count Polymarket advertises:

import requests

def fetch_active_markets():
    out, offset = [], 0
    while True:
        r = requests.get(
            "https://gamma-api.polymarket.com/markets",
            params={"closed": "false", "limit": 500, "offset": offset},
            timeout=30,
        ).json()
        if not r:
            break
        out.extend(r)
        offset += 500
    return out
Enter fullscreen mode Exit fullscreen mode

That pulls ~1,200 currently-tradable markets in 3 paginated calls. The other ~12,800 indexed are resolved or expired — useful for backtesting but not for a "what's moving now" screener.

Ranking: movers vs volume

Two top-of-page lists:

Top 24h movers: sorted by abs(one_day_change) descending, filtered to volume24hr > 1000 to drop dust.

Volume leaders: sorted by volume24hr descending. Mostly the same 8-10 megamarkets day-to-day (election props, BTC year-end), which is exactly why you need the movers view.

Crash signal: one_day_change <= -0.15 — a proxy for "fell off a recent local high." Backtested on a separate dataset (5,629 events, see cross-signal-data) at 73% mean-reversion rate, but the live screener column is a proxy not the same signal — caveats are in the repo's methodology discussion.

Why static + GitHub Pages

The screener regenerates as a flat HTML file every few hours via a single Python script. No backend, no database, no auth, no costs. The whole site is docs/index.html + per-market detail pages + a small CSS file. Deploy = git push. Total hosting bill = $0.

The tradeoff: data is up to a few hours stale. For a screener that's a feature, not a bug — you're scanning for setups, not executing in microseconds.

What I'd build next (and won't, alone)

  • WebSocket layer for live price ticks on top of the static base
  • Open-interest column (not in Gamma; needs CLOB orderbook crawl)
  • Alerts: "ping me when any crypto market crosses 20pp overnight"
  • A "movers within volume" intersection view (asked about it in Discussion #5 — feedback welcome)

Try it

The screener itself stays free forever. The historical SQLite dataset (16M+ snapshots, 63+ days of depth) is what funds the hosting.

Top comments (3)

Collapse
 
coolguy110 profile image
Coolguy110

Hello, manja316
I hope you're doing well.
How are you today.
I'm the technical co-founder of the tradoxvps.com
We launched dublin polymarket vps.
I'm impressive about your posts and can we talk a little bit?
I hope to suggest some proposals if you're interesting
Best

Collapse
 
predictandprofit profile image
Steve Farmer

I like the static approach here. For a screener, stale data is not automatically bad as long as the UI makes the staleness obvious.

A few things I would consider adding:

  • last refresh time per market
  • source failure or partial refresh indicator
  • volume/liquidity threshold shown next to the ranking
  • simple snapshot history so you can see whether a move is sudden or slowly drifting

That keeps the tool honest. The user knows whether they are looking at a live execution signal or just a candidate worth investigating.

Collapse
 
coolguy110 profile image
Coolguy110

+1 (215) 410-3220
This is my whatsapp number