Skip to main content

Price Feed

Streams real-time price updates for subscribed markets. All numeric values are strings.

Stream prices with the SDK

The native client handles the token mint, connect, subscribe, and auto-reconnect for you — prices_stream just yields live price events as they arrive:
A terminal streaming live price ticks: market id, bid, ask, with green up / red down arrows as prices move.

Live prices over /v1/ws/prices — bid/ask updating in real time.

The raw /v1/ws/prices wire protocol — subscribe frames, the full event shape, and a from-scratch asyncio example — is documented below.

Subscribe

After connecting, send a subscribe message:
Server confirms:

Price Updates

The server pushes price changes automatically. All fields are at the top level (not nested under a data key):
The server broadcasts the cached price payload with type and market_id stamped on top. Most fields are conditional — the exact set depends on which writer last produced the snapshot (see Source values below). Always treat extra/missing fields defensively.

Source values

The source field labels how fresh the price is. The only distinctions that matter to a bot are: whether the snapshot came from a live order-book stream (freshest) versus a periodic refresh, and whether the market is a settled Up/Down market you should no longer trade. Bots that want only live-stream-fresh prices can filter on source === "websocket" (or "rtds_websocket"); bots that just want “some price” can accept any source but drop updown_resolved_* frames.

Latency telemetry for HFT bots

Every price frame carries emit_ts_ms — an always-present integer wall-clock (milliseconds since epoch) stamped at the moment the frame is broadcast. It is purpose-built for end-to-end latency measurement: subtract it from your local receive time directly, with no parsing.
Typical observed_lag_ms is 80–500 ms; sustained values above ~2,000 ms indicate backend overload or a TCP retransmission storm on the client side. updated_at is a different timestamp — it is stamped backend-side when the price was written to the cache, which precedes the broadcast. Use it only for backend-internal lag: the gap between the cache write and the broadcast is emit_ts_ms - (updated_at parsed to ms). For latency that matters to your strategy, prefer emit_ts_ms — the client-observable end-to-end lag (recv_ts_ms - emit_ts_ms) is the number that governs how stale your signal is. Recommended bot logic: drop any quote where observed_lag_ms > 2000. For 5-min Up/Down crypto-timer markets, tighten that to 500 ms — these markets resolve in minutes and you don’t want to trade on stale signal.

Tick-size changes

Polymarket shrinks the minimum price tick from 0.01 to 0.001 when a market crosses certain thresholds (price > 0.96 or < 0.04). UpDown 5-minute crypto markets — our headline product — spend much of their last-minute lives in those ranges, so unhandled tick changes mean bot quoters silently emit orders at the wrong precision.
tick_size_change frames are NOT delivered on this /v1/ws/prices WebSocket. When the upstream CLOB WS publishes a tick_size_change, the backend broadcasts it only to subscribers of the SSE /prices/stream feed — a separate manager. The JWT /v1/ws/prices feed emits type:"price" frames only and has no tick-change path.For tick changes on a WS bot, poll GET /v1/tick-size/{token_id} (it consults the WS-fresh cache first, then the DB-synced value) or consume the SSE /prices/stream feed alongside your WS price feed.
On the SSE /prices/stream feed, the tick_size_change frame looks like this (shown for reference — this is the SSE shape, not a /v1/ws/prices frame):
The two directions have asymmetric correctness consequences:
  • Grow (0.001 → 0.01) — previously-valid 0.001-step quotes are no longer multiples of the new tick. PM rejects with INVALID_ORDER_MIN_TICK_SIZE. This is the case where missing the change silently breaks your bot.
  • Shrink (0.01 → 0.001) — 0.01-step quotes are still valid multiples of 0.001, so orders aren’t rejected. The downside is quote-competitiveness: other bots that respect the finer grid can undercut you by 0.001 increments while you still quote at 0.01.
PolySim’s matching engine validates against its own recorded minimum tick for the market, so off-grid orders on grow transitions are usually rejected here too — but reading the WS-fresh tick lets you avoid the gap between the price moving and the next refresh of that recorded value.
You can also read the current tick at any time via GET /v1/tick-size/{token_id} — that endpoint consults the WS-fresh cache first, then falls back to the DB-synced value.

Unsubscribe


Complete Example


Next Steps