Polymarket Bot 教程 · 第 8 章,共 32 章
面向 bot 的 Polymarket CLOB API:用于订单簿快照的 REST endpoints、用于实时更新的 WebSocket subscriptions、bids/asks 解析、mid-price 和 depth 计算、代码示例。
本章内容
CLOB API 是订单被签名、发送、撮合,以及订单簿所在的地方。Polymarket 维护两代 SDK--已弃用的 v1 和当前的 v2。本章仅覆盖 v2;在你 2026 年交付的任何 bot 里都不应出现 v1。我们会讲解 REST snapshot 路径、WebSocket 更新通道、新手最容易出错的解析细节,以及没有它就会让长期运行的 bot 在数小时内与真实状态失去同步的 reconnect 逻辑。
- CLOB v1 vs v2(使用 v2)
- 订单簿 REST snapshot
- WebSocket subscriptions:market 和 user channels
- bids/asks/depth 解析
- 计算 mid-price 和 best-bid/ask
- maker fees、taker fees、rebates
- 代码:连接 WS 并处理 price-change events
- reconnect 和 gap-handling
CLOB v1 vs v2(使用 v2)
Polymarket 维护两代 SDK。v1(npm 上的 @polymarket/clob-client,py-clob-client <0.30)已弃用,并且缺少 2024 年新增的若干订单类型。v2(Node 中的 @polymarket/clob-client-v2 v1.0.2,Python 中的 py-clob-client 0.34.6+)是当前标准。
有三个具体差异。v2 支持多结果市场的 negRisk 标志--这是 NegRisk exchange 在 2024 年底上线后所必需的。v2 为 WebSocket message shapes 提供 TypeScript types;v1 返回 any。v2 原生处理 2025 年 8 月的 Gnosis Safe signature flow;v1 需要自定义 signing glue。
本章其余部分都默认使用 v2。如果你在旧教程里看到 v1 代码,在被证明无误之前都应视为有问题--尤其是在 NegRisk markets 上提交订单时,v1 可能会静默地把订单路由到错误的 exchange contract。
订单簿 REST snapshot
REST snapshot endpoint 会返回某个单一 token 在某一时刻的完整订单簿。
GET https://clob.polymarket.com/book?token_id=<ERC1155_TOKEN_ID>
响应结构:
{
"market": "0x...",
"asset_id": "5413...",
"timestamp": "1715600000000",
"hash": "0x...",
"bids": [{"price":"0.45","size":"120"}, {"price":"0.44","size":"380"}, ...],
"asks": [{"price":"0.47","size":"85"}, {"price":"0.48","size":"210"}, ...]
}
价格是带有 2-3 位小数的字符串;size 是表示份额数量的字符串(不是美元)。bids 按从高到低排序,asks 按从低到高排序。hash 是一个去重标记--对未变化订单簿的重复轮询会返回相同的 hash,你的 bot 可以跳过处理。
REST snapshot 适合一次性查询(例如在入场决策时检查价格)。对于持续监控,请使用下面的 WebSocket channel。
WebSocket subscriptions:market 和 user channels
有两个 WebSocket channel 很重要。
Market channel:wss://ws-subscriptions-clob.polymarket.com/ws/market。订阅一个或多个 token;按发生顺序接收 order-book updates。
{"type":"Market","markets":["0xCondId1","0xCondId2"]}
消息会在每次变化时到达。类型包括 book(完整 snapshot)、price_change(delta)、tick_size_change(较少见)和 last_trade_price(最近一次成交价)。
User channel:wss://ws-subscriptions-clob.polymarket.com/ws/user。需要认证;接收你自己的订单事件--fills、partial fills、cancellations。
{"type":"User","auth":{"apiKey":"...","secret":"...","passphrase":"..."}}
User channel 是检测 fill 最干净的方式。轮询 orders REST endpoint 成本更高,而且可能错过两次轮询之间的状态变化;WebSocket 会在 matcher 确认的瞬间推送事件。
解析 bids/asks/depth
订单簿是按价格层级列出的聚合 size。这里有两个必须正确处理的解析约定。
订单方向:bids 是买单(表示有人想以这个价格 BUY)。当你的 bot 卖出时,你是在吃掉 bid;当你的 bot 买入时,你是在抬升 ask。Polymarket UI 显示的是同样的方向;其他一些交易所则相反。
排序:bids 以降序到达(最佳 bid 在前)。asks 以升序到达(最佳 ask 在前)。最佳 bid 是 bids[0];最佳 ask 是 asks[0]。注意:公共 WebSocket 有时会发送未预排序的部分订单簿更新--在任何 merge 之后都要防御性地重新排序。
某一层的 depth 是可成交的美元价值:price * size。前 5 层 depth 是常见的流动性指标:sum(b.price * b.size for b in bids[:5])。如果前 5 层 depth 低于 100 美元,订单簿就是 illiquid,大多数策略假设都会失效。
计算 mid-price 和 best-bid/ask
你的 bot 需要三个派生价格点。
- Best bid / best ask:
bids[0].price和asks[0].price。这是你实际可以交易到的价格,按 1 份额计算。 - Mid-price:
(best_bid + best_ask) / 2。spread 的数学中心。它适合估值;你永远不会在 mid 上成交。 - VWAP price for size N:沿着订单簿往下吃,直到累计 size 达到 N,返回按 size 加权的平均价格。这是现在立刻 BUY N 份额的真实成本,考虑了向更深层级扫单的影响。
边界情况:某一侧为空的 bid 或 ask(没有人卖,或者没有人买)意味着订单簿是单边的。在 Polymarket 的市场结构中,这通常发生在已结算或接近结算的市场,此时一侧接近 0.999,另一侧没有人提供流动性。把 best-bid = 0 或 best-ask = 1 视为“不要交易”的信号。
maker fees、taker fees、rebates
Polymarket 使用 maker-taker fee model。截至 2026 年 5 月,数值如下:
- Taker fee:0(零)--吃掉现有订单簿流动性的订单不收手续费。注意 proxy operations 仍会产生 gas / network costs。
- Maker rebate:小幅正向、按程序发放,在符合条件的 reward-program markets 中,对已成交的挂单订单进行返还。并非所有市场都有 rewards。
- NegRisk markets:使用相同的 fee structure,但在独立的 exchange contract 上运行;rewards 单独累计。
零 taker fee 让 Polymarket 与传统 CFD venues 有明显不同--大多数交易“成本”来自 bid-ask spread 本身,而不是显式手续费。对于每笔交易都跨越 spread 的策略,spread tax 才是真实成本;在典型订单簿上可假设单次往返约 1-3 美分,流动性差的市场会更高。
只有在 liquidity-rewards-eligible markets 与策略想法重合时,maker rebates 才值得追逐。第 19 章会把 liquidity-rewards farming 作为专门方法来讲。
代码:连接 WS 并处理 price-change events
最小化 Node 示例:连接、订阅、记录某个 token 的每个 price-change event。
import WebSocket from "ws";
const ws = new WebSocket("wss://ws-subscriptions-clob.polymarket.com/ws/market");
ws.on("open", () => {
ws.send(JSON.stringify({ type: "Market", markets: ["<CONDITION_ID>"] }));
});
ws.on("message", (data) => {
const msg = JSON.parse(data.toString());
if (msg.event_type === "price_change") {
console.log("price_change", msg.asset_id, msg.changes);
} else if (msg.event_type === "book") {
console.log("book snapshot", msg.bids?.[0], msg.asks?.[0]);
}
});
ws.on("close", () => console.log("closed"));
ws.on("error", (e) => console.error("err", e.message));
在单个 WebSocket connection 下,舒适地订阅最多大约 30 个 token。超过这个数量后,请拆分到多个 connection--服务器有时会静默丢弃较大的 subscriptions 而不报错,从而导致你读到陈旧的订单簿。
reconnect 和 gap-handling
长期运行的 WebSocket connection 一定会断开。Cloudflare 每隔几小时会轮换 connection;网络会短暂中断;Polymarket 也会偶尔部署更新。要提前做好准备。
reconnect strategy:在 close 或 error 后,等待带抖动的 min(2^attempt, 30) 秒,然后重新订阅。第一次 reconnect 后收到成功消息时,重置 attempt 计数器。
gap handling 比 reconnect 速度更重要。WebSocket 断开期间,订单簿已经发生变化。每次 reconnect 后,都要重新拉取所有已订阅 token 的 REST snapshot 并进行 reconcile:任何 book 发生明显变化的 open positions 都需要重新检查状态,exit 可能需要触发,alarms 也可能已经过时。“我错过了 30 秒的订单簿更新”是长期运行 bot 的无声杀手--它们会继续基于陈旧状态运行,并以已不存在的价格下单。
防御性模式:无论 WebSocket 状态如何,都每分钟对所有已订阅订单簿做一次 snapshot,并把 WS 视为建立在 snapshot 轮询之上的快速路径优化。


