Polymarket WebSocket:不掉线的实时数据
轮询订单簿是白白耗尽速率限制额度最常见的方式。下面介绍四个数据流、没人写进文档的订阅选项,以及那种让你的机器人收到一片沉默、却以为市场很平静的失效模式。
如果你的机器人用定时器轮询 GET /book,这一页能帮你省下大部分速率限制额度和一部分延迟。Polymarket 通过 WebSocket 推送订单簿更新,一个订阅只需一个连接,而不是成千上万个请求。
简要版本
连接到 wss://ws-subscriptions-clob.polymarket.com/ws/market,发送一个指明 assets_ids 的订阅,然后每 10 秒发送一次 PING,否则会被断开。如果你想要 best_bid_ask、new_market 或 market_resolved 事件,请设置 custom_feature_enabled: true:它们默认是关闭的,而它们的缺席看起来和一个平静的市场一模一样。
四个数据流
| 数据流 | URL | 内容 | 认证 |
|---|---|---|---|
| Market | wss://ws-subscriptions-clob.polymarket.com/ws/market | 公开的订单簿、价格和生命周期更新 | 无 |
| User | wss://ws-subscriptions-clob.polymarket.com/ws/user | 你的订单和成交更新 | 需要 |
| RTDS (旧版) | wss://ws-live-data.polymarket.com | 评论:参考价格已迁移到 PolyBolt | 无 |
| PolyBolt | wss://ws-live-v2.polymarket.com/ws | 加密货币、股票和 TWAP 参考价格 | 需要 |
| Sports | wss://sports-api.polymarket.com/ws | 实时比赛状态和比分 | 无 |
User 频道是大家最容易忽略的
如果你的机器人轮询 /data/orders 来确认订单是否成交,你就是在为 User 频道会在第一时间推送给你的信息消耗速率限制额度,而且延迟还更高。任何会下单的机器人都应该订阅这个数据流。
参考价格已于 2026 年 9 月 15 日迁移
Polymarket 上线了 PolyBolt,并将 RTDS 的参考价格功能列为旧版。crypto_prices、crypto_prices_chainlink 和 equity_prices 现在位于 wss://ws-live-v2.polymarket.com/ws,来源从 Binance/Chainlink 改为 Pyth,交易对代码改为小写的 btcusd,而不是 btcusdt 或 btc/usd,并且现在需要认证。完整映射见 PolyBolt 迁移指南。本页的其他内容,即 CLOB 的 market 和 user 频道,不受影响。
订阅 market 频道
建立连接,然后发送一条 JSON 订阅消息。assets_ids 接收代币 ID,也就是你从市场元数据中拿到的那些,type 必须为 "market"。
{
"assets_ids": ["65818619657568813474341868652308942079804919287380422192892211131408793125422"],
"type": "market",
"initial_dump": true,
"level": 2,
"custom_feature_enabled": true
}| 字段 | 必填 | 默认值 | 作用 |
|---|---|---|---|
assets_ids | 是 | — | 要订阅的代币 ID |
type | 是 | — | 必须为 "market" |
initial_dump | 否 | true | 订阅时发送一份完整的订单簿快照 |
level | 否 | 2 | 订阅级别:1、2 或 3 |
custom_feature_enabled | 否 | false | 启用 best_bid_ask、new_market 和 market_resolved 事件 |
custom_feature_enabled 默认为 false
这是让人白白浪费一个下午的订阅陷阱。三种事件类型(best_bid_ask、new_market 和 market_resolved)除非你主动开启,否则不会推送。没有错误,也没有警告;你只是永远收不到它们。如果你在等一个永远不来的 market_resolved 事件,原因就在这里。
除非有特别的理由,否则保持 initial_dump: true。没有它,你一开始就没有订单簿状态,必须等增量更新来重建,这意味着你的机器人一启动看到的就是不完整的画面。
消息类型
每条消息都带有一个 event_type。对它进行穷尽的分支处理:无法识别的类型应该记录到日志,而不是悄悄丢弃,因为这正是你发现协议发生变化的方式。
event_type | 含义 | 需要开启 |
|---|---|---|
book | 完整订单簿快照 | 否 |
price_change | 增量价格/档位更新 | 否 |
last_trade_price | 发生了一笔成交 | 否 |
tick_size_change | 市场的最小价格变动单位改变了 | 否 |
best_bid_ask | 盘口最优价更新 | 是 |
new_market | 创建了一个市场 | 是 |
market_resolved | 一个市场已结算 | 是 |
不要忽略 tick_size_change
最小价格变动单位(tick size)是按市场设置的,并且可能随着市场成熟而收紧。如果你在启动时缓存了 tick size,而市场改变了它,之后的每一笔限价单都会因违反最小 tick size 而被拒绝:一个看起来像是你定价逻辑出 bug 的 400。处理这个事件并更新你的缓存。
保持连接:每 10 秒 PING 一次
这是每个人都会踩到的要求。客户端必须每 10 秒发送一次 PING;服务器回复 PONG。错过心跳,连接就会被断开。
注意方向:这是客户端心跳。你不是在等服务器来 ping 你。库自带的、以其他间隔运行的 WebSocket 保活机制不能替代它,你需要自己发送应用层的 PING。
import asyncio, json, time
import websockets
URL = "wss://ws-subscriptions-clob.polymarket.com/ws/market"
STALE_AFTER = 30 # seconds without any message = treat as dead
async def stream(token_ids, on_event):
backoff = 1
while True:
try:
async with websockets.connect(URL) as ws:
await ws.send(json.dumps({
"assets_ids": token_ids,
"type": "market",
"initial_dump": True,
"custom_feature_enabled": True, # or lose 3 event types
}))
backoff = 1 # reconnected cleanly
last_msg = time.monotonic()
async def heartbeat():
while True:
await asyncio.sleep(10) # required cadence
await ws.send("PING")
hb = asyncio.create_task(heartbeat())
try:
while True:
# Silence is a failure mode, not a quiet market.
raw = await asyncio.wait_for(ws.recv(), timeout=STALE_AFTER)
last_msg = time.monotonic()
if raw == "PONG":
continue
for msg in json.loads(raw) if raw.startswith("[") else [json.loads(raw)]:
on_event(msg)
finally:
hb.cancel()
except Exception as exc:
# Reconnect with capped exponential backoff + jitter.
wait = min(backoff, 30) * (0.5 + 0.5 * asyncio.get_event_loop().time() % 1)
print(f"ws down ({exc}); reconnecting in {wait:.1f}s")
await asyncio.sleep(wait)
backoff = min(backoff * 2, 30)除非有理由,否则使用 SDK
polymarket-client(Python)和 @polymarket/client(TypeScript)实现了心跳、重连和消息帧处理,并把多个数据流合并成一个类型化的事件流。只有在你需要 SDK 未暴露的控制能力时,自己手写才值得。注意订阅仅支持异步:同步客户端没有实现 subscribe()。
无需重连即可更改订阅
五分钟市场不断到期,所以一个跟踪“当前 BTC 5m 市场”的机器人需要持续更改它的订阅集合。你不需要为此断开连接:market 频道支持在活动连接上订阅和取消订阅。
这比听起来更重要。每次市场轮换都重连,意味着反复付出建立连接的成本、重新请求初始快照,并在新市场开盘的那一刻恰好出现一个什么都收不到的空窗。
会让你亏钱的失效模式
掉线的 WebSocket 不会抛出异常。它只是不再推送消息。对一个简单的机器人来说,“没有更新”和“市场没有变动”无法区分,所以一个持有仓位的机器人会无限期地守着一个过时的价格,还坚信什么都没变。
- 跟踪最后一条消息的时间。如果在一个活跃市场上约 30 秒没有收到任何东西,那你是掉线了,而不是市场平静。上面的
asyncio.wait_for超时做的正是这件事。 - 把数据过时当作错误状态处理。停止报价,如果风控规则要求就平仓,然后重连,不要在冻结的订单簿上继续交易。
- 重连时退避,并加入随机抖动。在故障期间紧密循环重连,会让你在掉线之外再被限流;而一批机器人同步重连会让情况更糟。
- 重连后重新同步状态。你离开期间错过了更新。以新的
initial_dump为准,丢弃之前的订单簿,而不是把增量更新合并到一个过时的快照上。 - 任何中断之后都要核对持仓。如果你从 User 频道掉线了,你可能错过了一笔成交。在基于假设的状态采取行动之前,重新读取持仓。
WebSocket 与速率限制
这是一个常见问题,而它的答案正是这一切的意义所在:订阅不会像轮询那样消耗按请求计算的速率限制额度。Polymarket 文档中的限制是按端点计算的 HTTP 限制,流式推送几乎完全绕开了它们。
| 方式 | 跟踪 20 个市场一小时的成本 |
|---|---|
每秒轮询 /book | 72,000 次请求,额度为每 10 秒 1,500 次 |
每秒轮询 /books(批量) | 3,600 次请求,额度为每 10 秒 500 次 |
| WebSocket 订阅 | 一个连接,外加 360 次心跳 |
你仍然需要发起 HTTP 调用,比如市场发现、tick size、下单、持仓核对,这些仍受文档中的速率限制约束。流式推送只是去掉了请求量中最大、也最没用的那部分来源。
延迟是伪装起来的限流
由于 Cloudflare 层是通过延迟而不是拒绝来限流的,一个超出限制的轮询机器人看不到 429,它看到的是响应时间悄悄地上升。如果你的订单簿数据延迟从 40ms 漂移到了 3 秒,你遇到的不是故障,而是你自己的请求速率。
数据流给不了你的东西
有一条边界值得直说,因为这是人们接下来会尝试的事。WebSocket 给你的是从你订阅那一刻起的订单簿。它给不了你上周二的订单簿。
Polymarket 不保留历史订单簿。市场一旦结算,它的订单簿就返回 404,prices-history 只保留大约每分钟一个点。如果你想用市场的真实交易情况来测试策略,就需要当时有人记录下来的快照。PolyTest 为加密货币 Up/Down 市场做这件事,每边 8 档深度,已结算的市场也会保留。或者今天就启动你自己的采集程序,接受从零历史开始的事实,然后等待。
常见问题
- Polymarket 的 WebSocket URL 是什么?
- 公开的 market 频道是 wss://ws-subscriptions-clob.polymarket.com/ws/market,需要认证的 user 频道是 wss://ws-subscriptions-clob.polymarket.com/ws/user。另外还有两个数据流:位于 wss://ws-live-data.polymarket.com 的 RTDS,用于参考价格、评论和交易动态;以及位于 wss://sports-api.polymarket.com/ws 的体育数据源。
- Polymarket WebSocket 有速率限制吗?
- WebSocket 订阅不会像 HTTP 轮询那样消耗按请求计算的速率限制额度:Polymarket 文档中的限制是按端点计算的 HTTP 限制。流式推送正是推荐用来避开这些限制的方式。连接上真正的约束是心跳:每 10 秒发送一次 PING,否则连接会被断开。你仍需为市场发现、tick size 和下单发起的 HTTP 调用,依然受常规限制约束。
- 如何保持 Polymarket WebSocket 连接不断开?
- 每 10 秒发送一条 PING 消息;服务器会回复 PONG。这是客户端的应用层心跳,所以 WebSocket 库自带的、以不同间隔运行的保活机制不能替代它。还要跟踪最后收到消息的时间:掉线的连接会停止推送数据而不报错,所以在活跃市场上约 30 秒的沉默应被视为断线,而不是市场平静。
- 为什么我收不到 best_bid_ask 或 market_resolved 事件?
- 这些事件默认是关闭的。在订阅消息中把 custom_feature_enabled 设为 true,即可启用 best_bid_ask、new_market 和 market_resolved。缺少这个标志时既没有错误也没有警告,事件只是永远不会到来,很容易被误认为市场不活跃。
- 我的 Polymarket 机器人应该用 WebSocket 还是轮询 REST API?
- 实时订单簿和价格数据用 WebSocket。每秒轮询一次 /book 来跟踪 20 个市场,每小时要 72,000 次请求,而额度是每 10 秒 1,500 次;订阅则只需一个连接加上心跳。而且轮询返回的结果更陈旧,因为你看到的是上次轮询时的订单簿,而不是最近一次变化时的订单簿。REST 留给市场发现、tick size、下单和持仓核对。
- 能在不重连的情况下更改订阅的市场吗?
- 能。market 频道支持在活动连接上订阅和取消订阅资产,这对 5 分钟 Up/Down 合约这类不断轮换的短期市场很重要。每次轮换都重连,会反复付出建立连接的成本,并在新市场开盘的那一刻恰好留下覆盖空窗。
获取 Polymarket 不保留的历史数据
PolyTest 在 Polymarket 加密货币 Up/Down 市场运行期间实时记录:240M+ 条快照,包含 8 档订单簿深度、亚秒级时间戳,并保留已结算的市场。从 3 天免费试用开始。
继续阅读
Polymarket 替换了 RTDS:迁移到 PolyBolt
9 月 15 日,Polymarket 悄悄地把 RTDS 的参考价格功能变成了旧版。如果你在给机器人推送现货价格,有六件事同时改变了,包括价格来源和交易对代码格式,而且其中大多数会悄无声息地失效,而不是明确报错。
如何构建 Polymarket 交易机器人
大多数 Polymarket 机器人教程都基于已经不存在的 SDK。这是一条最新的端到端路径:2026 年该安装什么、认证实际如何运作、数据缺口在哪里,以及在第一周就让机器人挂掉的失效模式。
Polymarket API 速率限制:完整参考
Polymarket 把它的速率限制分散在好几个页面上,分属两套完全不同的系统,而且使用的是几乎没人预料到的 10 秒窗口。这里把它们全部整理成一组表格,并说明超出限制时实际会发生什么。
2026 年的 Polymarket Python SDK(以及如何迁移出 py-clob-client)
如果你的代码还在从 py_clob_client 导入 ClobClient,那它就建立在一个已被取代的包之上,AI 助手为你写的大部分 Polymarket 代码也是如此。下面介绍当前的 SDK 以及如何迁移。