DEV Community

BlueWhale-Quant-Lab
BlueWhale-Quant-Lab

Posted on • Originally published at github.com

Polymarket's price WebSocket can stall while connected (and the docs won't warn you)

Polymarket streams live crypto prices over its Real-Time Data Socket (RTDS) at wss://ws-live-data.polymarket.com. If you're building a bot on it, two things will bite you that the docs don't prepare you for. Here's both, with fixes, and a small open-source client that handles them.

Gotcha 1: the documented symbol filter is rejected by the live server

The docs show filtering like this:

{ "topic": "crypto_prices", "type": "update", "filters": "btcusdt,ethusdt" }
Enter fullscreen mode Exit fullscreen mode

Send that comma-separated string to the live endpoint and you get:

invalid Subscription.Filters: value does not match regex ...
Enter fullscreen mode Exit fullscreen mode

A JSON-array value is accepted but then silently returns nothing. The approach that actually works: subscribe with no server-side filter (it streams every symbol fine) and filter client-side.

sub = {"action": "subscribe", "subscriptions": [{"topic": "crypto_prices", "type": "update"}]}
# then keep only the symbols you care about as messages arrive
Enter fullscreen mode Exit fullscreen mode

Gotcha 2: the feed can stall while staying "connected"

The docs reassure you:

Connection errors will trigger automatic reconnection attempts.

The problem is the failure that hurts most isn't a connection error. The RTDS server sometimes stops pushing data but keeps the socket open — no close frame, no exception:

ws.closed        # False
# ...and no message has arrived in 90 seconds
Enter fullscreen mode Exit fullscreen mode

Since there's no error, error-based auto-reconnect never fires. Your bot thinks it has a live price; it's frozen on a value from minutes ago. On Polymarket up/down markets that means you never lock the period's reference price and the strategy silently stops working.

You can't trust the transport to tell you it's alive. Track liveness yourself:

last_data_ts = time.time()      # update on every valid price
# Chainlink pushes every 2-30s, so 45s of silence = stalled:
if connected and time.time() - last_data_ts > 45:
    await force_reconnect()     # close the socket; let your loop reconnect
Enter fullscreen mode Exit fullscreen mode

Bonus: push latency

Chainlink ticks arrive 2–30s apart. Near a settlement boundary, a 25-second-old tick is a stale price. Gate on the message's own timestamp:

if now_ms - payload_timestamp_ms > 30_000:
    skip()   # too old to act on
Enter fullscreen mode Exit fullscreen mode

The client

I packaged the connect/subscribe/parse/reconnect parts (verified against the live feed) into a small MIT module — including the no-filter-then-filter-client-side fix above:

import asyncio
from polymarket_rtds_client import RTDSClient

client = RTDSClient(topics=["crypto_prices"], symbols=["btcusdt", "ethusdt"])
asyncio.run(client.stream_prices(lambda s, v, ts: print(s, v, ts)))
Enter fullscreen mode Exit fullscreen mode

Repo (free, MIT, tests + live demo):
https://github.com/BlueWhale-Quant-Lab/polymarket-realtime-price-websocket-rtds

The liveness guards (silent-stall detection, a staleness-gated price store) are in a complete version for when you're trading on the feed — but the free client fully covers reading live prices.

Takeaway

On RTDS: don't use the documented comma filter, and don't trust the socket's liveness — track your own last_data_ts and reconnect on silence, not just on errors.

Top comments (0)