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-clientpy-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 channelwss://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 channelwss://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 askbids[0].priceasks[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:在 closeerror 后,等待带抖动的 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 轮询之上的快速路径优化。

常见问题

Polymarket CLOB API endpoint 是什么?
基础 CLOB endpoint 是 https://clob.polymarket.com(REST)和 wss://ws-subscriptions-clob.polymarket.com/ws/market(WebSocket)。这些是 @polymarket/clob-client-v2 和 py-clob-client 使用的 V2 endpoints。
读取订单簿需要 API key 吗?
不需要。读取订单簿(snapshots 和 WebSocket subscriptions)是公开的,不需要认证。只有在下单/撤单以及读取账户专属数据(positions、fills)时才需要 API key。
CLOB WebSocket 推送价格更新有多快?
与订单撮合的速度一样快。活跃市场每隔几百毫秒就会有更新;流动性较薄的市场只会在实际有订单时更新。depth 变化和 trade events 都通过同一个 WS channel 流动--需要通过解析 event type 来正确处理每一种消息。
如何计算 Polymarket 订单簿的 mid-price?
如果两侧都存在,则 mid = (best_bid + best_ask) / 2;否则使用 last_trade_price 作为回退。要注意薄订单簿中 best_bid 远低于 best_ask 的情况--此时 mid 可能没有意义。在把 mid 当作公平价格之前,一定也要考虑 spread。
2026 年 Polymarket 的 maker fee 是多少?
大多数类别为 0%。Maker 可获得相当于 taker fees 20-25% 的 rebates。Taker fees 因类别而异:sports 0.75%、politics 1.00%、economics 1.25%、crypto 1.80%。rebate 与 fee 的不对称性,正是为什么活跃 bot 几乎总是使用 limit orders 而不是 market orders 报价。
如何处理 WebSocket disconnects?
使用指数退避重连(1s、2s、4s,最多 30s),重新订阅相同 markets,并重新拉取 REST snapshot 以补齐缺口。不要相信陈旧的订单簿--如果你已经断开超过 5 秒,在下单前先请求新的 snapshot。