{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreicn6qvqd7llntdyozfvrviqitu3x2smlvuz4m7nn6qnzrt4qpfrn4",
"uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mohlw4ut76h2"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreickx7qibtrzhlfguirqt7tvyp3iz2hwrfaovd246pl6cocwqvooqu"
},
"mimeType": "image/webp",
"size": 77880
},
"path": "/bluewhale-quant-lab/polymarket-clob-websocket-in-python-real-time-order-book-without-polling-4e6n",
"publishedAt": "2026-06-17T04:52:41.000Z",
"site": "https://dev.to",
"tags": [
"python",
"websocket",
"tutorial",
"trading",
"my Amsterdam VPS"
],
"textContent": "If your Polymarket bot polls `GET /book` in a loop, your view of the market is as stale as your interval — and you'll lose to anyone using the **WebSocket feed**. This tutorial builds a real-time local order book in Python from the CLOB WebSocket stream.\n\n## Why WebSocket beats polling\n\n * **Polling:** you ask every N ms → your data is up to N ms old, and you burn REST rate-limit budget.\n * **WebSocket:** the server **pushes** changes → your information latency drops to roughly your network round-trip.\n\n\n\nFor a reactive strategy, this is the difference between trading the market that _is_ and the market that _was_.\n\n## Connect and subscribe\n\n\n import asyncio, json, websockets\n\n WS_URL = \"wss://ws-subscriptions-clob.polymarket.com/ws/market\"\n\n async def subscribe(token_ids):\n async with websockets.connect(WS_URL, ping_interval=20, ping_timeout=20) as ws:\n await ws.send(json.dumps({\"assets_ids\": token_ids, \"type\": \"market\"}))\n async for raw in ws:\n yield json.loads(raw)\n\n\n_(Confirm the exact URL, subscription payload, and message shapes against current Polymarket docs — they change.)_\n\n## Maintain a local book from snapshot + deltas\n\nThe feed typically sends an initial **book snapshot** then incremental **price-change** messages. Apply them to a local structure:\n\n\n\n class LocalBook:\n def __init__(self):\n self.bids = {} # price -> size\n self.asks = {}\n\n def apply_snapshot(self, msg):\n self.bids = {float(l[\"price\"]): float(l[\"size\"]) for l in msg.get(\"bids\", [])}\n self.asks = {float(l[\"price\"]): float(l[\"size\"]) for l in msg.get(\"asks\", [])}\n\n def apply_delta(self, msg):\n for ch in msg.get(\"changes\", []):\n side = self.bids if ch[\"side\"] == \"BUY\" else self.asks\n price, size = float(ch[\"price\"]), float(ch[\"size\"])\n if size == 0:\n side.pop(price, None)\n else:\n side[price] = size\n\n def best_bid(self): return max(self.bids) if self.bids else None\n def best_ask(self): return min(self.asks) if self.asks else None\n def mid(self):\n b, a = self.best_bid(), self.best_ask()\n return (b + a) / 2 if b and a else None\n\n\n## Wire it together\n\n\n async def run(token_id):\n book = LocalBook()\n async for msg in subscribe([token_id]):\n t = msg.get(\"event_type\") or msg.get(\"type\")\n if t in (\"book\", \"snapshot\"):\n book.apply_snapshot(msg)\n elif t in (\"price_change\", \"delta\"):\n book.apply_delta(msg)\n # react immediately on fresh state\n m = book.mid()\n if m is not None:\n on_mid(m, book)\n\n asyncio.run(run(TOKEN_ID))\n\n\n## Measure your information lag\n\nAlways know how fresh your book is:\n\n\n\n import time\n lag_ms = (time.time() - float(msg[\"timestamp\"])) * 1000\n if lag_ms > 50:\n print(f\"⚠ stale: {lag_ms:.0f}ms behind server\")\n\n\n## The ceiling you can't code around\n\nHere's the catch: your information latency can never beat your **network round-trip to the server**. The CLOB WebSocket terminates in **Amsterdam** — I measured ~1.2 ms from an AMS box vs ~88 ms from US-East. From the US, even a perfect WebSocket implementation is ~90 ms behind reality, because that's how long the packets take to arrive.\n\nSo the prerequisite for a fast feed is a server _near_ the feed. I run mine on an Amsterdam-metro VPS: **my Amsterdam VPS**\n_Disclosure: affiliate link, I earn a referral. It's the box behind the 1.2 ms._\n\n## Robustness checklist\n\n * **Reconnect with backoff** — WebSockets drop; resubscribe and re-snapshot on reconnect.\n * **Detect gaps** — if deltas reference prices you don't have, request a fresh snapshot.\n * **Heartbeat** — use `ping_interval` so dead connections are detected fast.\n * **One process, many assets** — subscribe to multiple `assets_ids` on one socket.\n\n\n\n## Recap\n\nWebSocket + a maintained local book gives you near-real-time perception. But the floor on \"real-time\" is your distance to Amsterdam. Get the server location right, then this code makes you genuinely fast.\n\n_Code is illustrative — verify against current docs. Latency from my own 2026 tests. Not financial advice._",
"title": "Polymarket CLOB WebSocket in Python — Real-Time Order Book Without Polling"
}