简要概览
Polymarket 提供三个公共 API(API 是程序用于自动读取数据或下单交易的一种方式):CLOB(交易)、Gamma(市场发现)和 Data(分析)。官方 Python SDK 是 py-clob-client 0.34.6。身份验证使用 API 密钥 + ECDSA 签名,订单则通过 Polygon 代理钱包使用 EIP-712 进行签名。速率限制约为每个密钥每分钟 60 个订单。新开发者最容易踩的坑,是 Gamma 与 CLOB 之间的 condition_id → token_id 映射问题。先解决这个问题,后面的流程就会顺畅很多。Polymarket 上通过流动性奖励以及机器人捕获价差获得的收益大约为每月 4000 万美元,其中几乎全部来自 API 用户。
第 1 部分:三个 API
Polymarket 将不同职责清晰地拆分到三个独立服务中。为每项任务选择合适的 API,可以让你的机器人保持快速、简单,并且不触发速率限制。
| API | Base URL | Purpose | Auth Required |
|---|---|---|---|
| CLOB API | clob.polymarket.com | 下单、取消订单并跟踪订单。读取订单簿。查询持仓。 | 是(交易需要) |
| Gamma API | gamma-api.polymarket.com | 浏览市场,获取元数据、图片、结果价格、成交量、到期时间和标签。 | 否(公开) |
| Data API | data-api.polymarket.com | 历史交易、持仓快照、用户分析、排行榜数据。 | 否(公开) |
典型的机器人循环会使用 Gamma 查找市场,使用 CLOB 获取订单簿并执行交易,再使用 Data 离线回测策略表现。你可以把 Gamma 理解为“目录”,CLOB 理解为“交易所”,Data 理解为“数据仓库”。
第 2 部分:身份验证与代理钱包模型
Polymarket 不会使用你主钱包的私钥来签署交易。它使用的是一种 Gnosis Safe 风格的代理钱包。你的主钱包授权一个代理钱包,然后由该代理钱包在 Polygon 上执行所有交易。你的 API 机器人会与这个代理钱包交互。
你需要准备什么
- API 密钥 - 在 Polymarket Settings → Developer 中生成
- 私钥 - 你的交易钱包密钥(不是你的主 MetaMask 助记词)
- Funder 地址 - 你的代理钱包地址(显示在 Settings → Wallet 中)
- Chain ID -
137(Polygon 主网) - Signature type -
1(POLY_PROXY,零售用户的标准类型)
第 3 部分:安装 py-clob-client
官方 Python SDK 是从零开始下出第一笔订单的最快方式。我们将使用 0.34.6 版本——这是 PyPI 上的当前发布版(2026 年 2 月),也是几乎所有线上 bot 都在使用的版本。
# Create a virtual environment first
python3 -m venv venv
source venv/bin/activate # macOS/Linux
venv\Scripts\activate # Windows
# Install the SDK
pip install py-clob-client==0.34.6 requests websocket-client python-dotenv基础客户端配置
import os
from dotenv import load_dotenv
from py_clob_client.client import ClobClient
from py_clob_client.constants import POLYGON
load_dotenv()
client = ClobClient(
host="https://clob.polymarket.com",
key=os.environ["POLY_PRIVATE_KEY"],
chain_id=POLYGON, # 137
signature_type=1, # POLY_PROXY
funder=os.environ["POLY_FUNDER"],
)
# One-time: derive and cache API credentials
client.set_api_creds(client.create_or_derive_api_creds())create_or_derive_api_creds() 调用会使用你的私钥签署一条消息,并用它换取 API key、secret 和 passphrase。首次运行后,请将这些凭据缓存到你的 .env 中,这样每次启动时就不必再请求派生凭据的端点。
第 4 部分:通过 Gamma 发现市场
在开始交易之前,你需要找到值得交易的市场。Gamma 会返回 JSON,其中包含 Polymarket UI 展示的所有信息:问题、结果、价格、24 小时成交量、到期时间、标签和图片。
import requests
resp = requests.get(
"https://gamma-api.polymarket.com/markets",
params={
"active": "true",
"closed": "false",
"tag_slug": "politics",
"limit": 20,
"order": "volume24hr",
"ascending": "false",
},
timeout=10,
)
resp.raise_for_status()
markets = resp.json()
for m in markets:
print(f"{m['slug']:50} Yes ${float(m['outcomePrices'][0]):.3f} Vol24h ${m.get('volume24hr', 0):,.0f}")常用 Gamma 查询参数
| Parameter | What it does |
|---|---|
tag_slug | 按类别筛选(政治、体育、加密货币、文化等) |
active=true | 仅返回当前可交易的市场 |
closed=false | 隐藏已结算的市场 |
order=volume24hr | 按近期成交量排序(流动性信号) |
end_date_min | ISO 日期 - 跳过过快结算的市场 |
limit | 每页最多 500 条(使用 offset 分页) |
第 5 部分:condition_id → token_id 映射
这是构建 Polymarket bot 时最常见的痛点。Gamma 返回 condition_id(每个市场一个)。CLOB 交易使用 token_id(每个结果一个)。你始终需要同时使用两者。
# Each Gamma market object contains 'clobTokenIds' - a JSON string array
import json
market = markets[0]
token_ids = json.loads(market['clobTokenIds']) # ['7410...', '1120...']
yes_token = token_ids[0] # First outcome
no_token = token_ids[1] # Second outcome
# Alternative: ask CLOB directly using condition_id
info = client.get_market(condition_id=market['conditionId'])
yes_token = info['tokens'][0]['token_id']结果顺序的注意事项
Gamma 的 outcomes 数组和 clobTokenIds 数组按索引一一对应。务必读取结果标签。不要假设索引 0 一定是 “Yes”。在多结果市场中(NegRisk、奥斯卡、选举等),索引 0 可能是 “Kamala Harris” 或 “Taylor Swift”。顺序是固定的,但每个市场都可能不同。
第 6 部分:读取订单簿
book = client.get_order_book(token_id=yes_token)
best_bid = float(book.bids[0].price) if book.bids else None
best_ask = float(book.asks[0].price) if book.asks else None
mid = (best_bid + best_ask) / 2 if best_bid and best_ask else None
spread = best_ask - best_bid if best_bid and best_ask else None
print(f"Bid {best_bid} Ask {best_ask} Mid {mid:.4f} Spread {spread:.4f}")订单簿会以排序数组的形式返回(bids 按降序,asks 按升序)。每一档都有一个 price 和一个 size。要估算较大订单的滑点,可以逐档遍历订单簿并累加名义金额,直到达到你的目标成交数量。
第 6b 部分:CLOB v2 REST 端点(原始接口,不使用 SDK)
SDK 对这些端点做了封装,但了解原始端点有助于调试、使用其他语言,或构建一个轻量客户端。Base URL:https://clob.polymarket.com。下面所有读取接口都是公开的 - 无需认证。这些内容已在 2026 年 6 月通过线上验证。
| Endpoint | Method | What it returns |
|---|---|---|
/markets | GET | 所有市场(通过 next_cursor 分页)。包含 condition_id、tokens[]、minimum_tick_size、neg_risk。 |
/sampling-markets | GET | 仅返回具有实时订单簿的市场——这是查找可交易 token_id 的最快方式。 |
/book?token_id= | GET | 完整订单簿:bids[] 和 asks[],包含价格和数量。 |
/price?token_id=&side=buy | GET | 某一方向的最优价格。side 不区分大小写(buy/BUY)。返回 {"price":"0.14"}。 |
/midpoint?token_id= | GET | {"mid":"0.21"}——最优买价和卖价的中点。 |
/spread?token_id= | GET | {"spread":"0.14"}——最优卖价减去最优买价。 |
/tick-size?token_id= | GET | {"minimum_tick_size":0.01}——该 token 允许的最小价格步长。 |
/prices-history?market=&interval= | GET | 历史价格点。interval = 1m、1h、6h、1d、1w、max。 |
/trades | GET | 近期交易(查询自己的交易需要认证;查询市场交易为公开接口)。 |
/order | POST | 提交已签名订单(需要认证)。 |
/order | DELETE | 按 id 取消单个订单(需要认证)。 |
/orders | GET | 你的未成交挂单(需要认证)。 |
/balance-allowance?asset_type= | GET | 你的 USDC 余额和链上授权额度(需要认证)。每次下单前都应检查。 |
以下是直接来自实时 API 的已验证响应:
$ curl "https://clob.polymarket.com/price?token_id=7347...&side=buy"
{"price":"0.14"}
$ curl "https://clob.polymarket.com/midpoint?token_id=7347..."
{"mid":"0.21"}
$ curl "https://clob.polymarket.com/spread?token_id=7347..."
{"spread":"0.14"}
$ curl "https://clob.polymarket.com/tick-size?token_id=7347..."
{"minimum_tick_size":0.01}L2 认证标头(不使用 SDK、直接调用原始 REST 时)
读取类端点是公开的。若要通过原始 REST 提交或取消订单,需要使用你的 API 凭据对每个请求签名。SDK 会替你完成这些操作;下面是它在内部构造的内容:
| Header | What it carries |
|---|---|
POLY_ADDRESS | 你的签名钱包地址 |
POLY_API_KEY | 来自 create_or_derive_api_creds() 的 API key |
POLY_PASSPHRASE | 同一次派生调用返回的 passphrase |
POLY_TIMESTAMP | 当前 UNIX 秒级时间戳(必须与服务器时钟一致——请参阅时钟同步提示) |
POLY_NONCE | 每个请求使用的 nonce |
POLY_SIGNATURE | 以你的 API secret 为密钥,对 timestamp + method + path + body 计算 HMAC-SHA256,并进行 base64-url 编码 |
第 7 部分:下单——买入和卖出
限价单(GTC——默认值)
from py_clob_client.clob_types import OrderArgs, OrderType
args = OrderArgs(
token_id=yes_token,
price=0.45,
size=100, # Shares, not dollars. 100 shares @ $0.45 = $45 max cost.
side="BUY",
)
signed_order = client.create_order(args)
response = client.post_order(signed_order, OrderType.GTC)
print(response)create_order 调用会使用你的私钥对 EIP-712 结构化消息进行签名。随后 post_order 将其提交到 CLOB。你绝不会通过网络发送原始私钥——只会发送已签名的订单。
先将价格对齐到 tick
每个订单价格都必须是该市场 minimum_tick_size 的整数倍(大多数市场为 0.01,价差较小的市场为 0.001)。不符合 tick 的价格会被拒绝。获取一次 tick 并按其取整:
from py_clob_client.clob_types import OrderArgs, OrderType
tick = float(client.get_tick_size(token_id=yes_token)) # e.g. 0.01
def to_tick(p, tick): return round(round(p / tick) * tick, 4)
price = to_tick(0.453, tick) # -> 0.45 on a 0.01 market买入
side="BUY",size 的单位是份额(不是美元)。以 0.45 美元买入 100 份最多花费 45 美元;如果结果胜出,将获得 100 美元。最低订单价值约为 1 美元。
buy = OrderArgs(token_id=yes_token, price=to_tick(0.45, tick), size=100, side="BUY")
resp = client.post_order(client.create_order(buy), OrderType.GTC)
print(resp) # {'success': True, 'orderID': '0x...', 'status': 'live', ...}卖出
卖出使用同一个调用,只需设置 side="SELL"。你只能卖出已经持有的份额;如果卖出数量超过当前持仓,会因 "insufficient balance" 错误而被拒绝。要平仓,卖出你之前买入的同一个 token_id。
sell = OrderArgs(token_id=yes_token, price=to_tick(0.62, tick), size=100, side="SELL")
resp = client.post_order(client.create_order(sell), OrderType.GTC)订单参数速览
| Field | Meaning | Notes |
|---|---|---|
token_id | 你正在交易的结果 | 不是 condition_id——见第 5 部分 |
side | BUY 或 SELL | BUY 需要 USDC;SELL 需要份额 |
price | 0.001-0.999 | 必须是最小报价单位的整数倍 |
size | 份额数量 | 订单价值最低约 1 美元;成本 = price × size |
| 订单类型 | GTC / GTD / FOK / FAK | 传给 post_order(...) |
串起来:你的第一笔 API 交易(一个可运行脚本)
下面是完整的端到端流程:连接、找到一个流动性较好的市场、读取订单簿、按最小报价单位对齐价格、下一个小额真实订单,然后取消它。填入你的两个密钥后运行即可。第一次运行时请从很小的数量开始(几美元)。
import os, json, requests
from dotenv import load_dotenv
from py_clob_client.client import ClobClient
from py_clob_client.constants import POLYGON
from py_clob_client.clob_types import OrderArgs, OrderType
load_dotenv()
# 1) Connect (signing key in your EOA, funds in your proxy/funder)
client = ClobClient(
"https://clob.polymarket.com",
key=os.environ["POLY_PRIVATE_KEY"],
chain_id=POLYGON, # 137
signature_type=1, # 1 = email/Magic proxy, 2 = browser-wallet proxy
funder=os.environ["POLY_FUNDER"],
)
client.set_api_creds(client.create_or_derive_api_creds()) # cache these after first run
# 2) Find the most-traded open market (Gamma, no auth)
m = requests.get(
"https://gamma-api.polymarket.com/markets",
params={"active": "true", "closed": "false", "order": "volume24hr",
"ascending": "false", "limit": 1}, timeout=10,
).json()[0]
token_id = json.loads(m["clobTokenIds"])[0] # index 0 = first outcome (read the label!)
print("Trading:", m["question"])
# 3) Read the book + the tick size
tick = float(client.get_tick_size(token_id))
book = client.get_order_book(token_id)
best_ask = float(book.asks[0].price)
print("best ask", best_ask, "| tick", tick)
# 4) Place a small BUY at the ask (tiny size to start)
price = round(round(best_ask / tick) * tick, 4) # snap to tick
order = OrderArgs(token_id=token_id, price=price, size=5, side="BUY") # 5 shares
resp = client.post_order(client.create_order(order), OrderType.GTC)
print(resp) # {'success': True, 'orderID': '0x...', ...}
# 5) Cancel it (clean up)
# client.cancel(order_id=resp["orderID"])订单类型
| Type | Code | Behaviour | When to use |
|---|---|---|---|
| 取消前有效 | GTC | 挂在订单簿上,直到成交或由你取消 | 默认类型。适用于大多数做市和限价策略。 |
| 截至日期有效 | GTD | 在指定时间戳自动取消 | 事件驱动场景:例如“在美联储公告发布前 5 分钟取消” |
| 全部成交或取消 | FOK | 必须立即全量成交,否则全部取消 | 适用于套利腿,部分成交会破坏交易 |
| 部分成交后取消 | FAK | 在限价内尽可能成交,剩余部分取消 | 激进吃单——效果类似带价格上限的市价单 |
取消订单
# Single order
client.cancel(order_id="0xabc...")
# Cancel all orders on a specific market
client.cancel_market_orders(market=market['conditionId'])
# Nuclear option: cancel everything
client.cancel_all()第 8 部分:WebSocket 流式推送
每秒轮询 Gamma 很浪费,而且很快会触及速率限制。WebSocket 订阅源会以亚秒级延迟推送实时订单簿和交易更新。
import json, websocket
WS_URL = "wss://ws-subscriptions-clob.polymarket.com/ws/market"
def on_open(ws):
ws.send(json.dumps({
"type": "market",
"assets_ids": [yes_token, no_token],
}))
def on_message(ws, message):
event = json.loads(message)
if event.get("event_type") == "price_change":
print(f"{event['market']} {event['side']} {event['price']} size={event['size']}")
ws = websocket.WebSocketApp(
WS_URL,
on_open=on_open,
on_message=on_message,
)
ws.run_forever(ping_interval=20)一共有两个订阅源。/market 订阅源包含公开订单簿和交易数据。/user 订阅源包含你自己的订单和成交事件(需要认证)。生产环境中的机器人通常会同时连接两者,在断开后自动重连,并将 WebSocket 作为当前订单簿的事实来源。
第 9 部分:速率限制与退避
| Endpoint class | Limit | Burst |
|---|---|---|
| 下单(CLOB) | 每个 API key 约 60 次/分钟 | 约 10 次/秒 |
| 取消订单 | 约 120 次/分钟 | 约 20 次/秒 |
| 读取市场数据(CLOB book) | 约 300 次/分钟 | 更高,具体会变化 |
| Gamma API | 较宽松;遵守 429 响应 | - |
| WebSocket 消息 | 入站基本没有实际限制 | - |
遇到 HTTP 429 时,服务器会返回 Retry-After 响应头。请使用带抖动的指数退避:
import random, time
def post_with_backoff(fn, *args, max_retries=6):
for attempt in range(max_retries):
try:
return fn(*args)
except Exception as e:
if "429" in str(e):
sleep = (2 ** attempt) + random.random()
time.sleep(min(sleep, 30))
continue
raise
raise RuntimeError("Too many retries")第 10 部分:参考机器人架构
每个可靠的 Polymarket 机器人都有相同的六个组件。请将每个组件构建为独立模块,并保持松耦合。
| Component | Responsibility | APIs used |
|---|---|---|
| 扫描器 | 定时任务:拉取符合你条件的市场(标签、成交量、距离到期天数) | Gamma |
| 价格引擎 | 通过 WebSocket 维护实时本地订单簿 | CLOB WS |
| 信号生成器 | 纯函数:订单簿状态 + 元数据 → 目标持仓 | -(内存中) |
| 订单管理器 | 对比当前订单与目标状态,以最小变更下单/撤单 | CLOB REST |
| 风险管理器 | 执行单市场上限、每日亏损限制和熔断机制 | -(内存 + DB) |
| 日志与账本 | 持久化每一次决策、成交和撤单。用于税务报告和调试。 | SQLite / Postgres |
第 11 部分:常见故障模式
- 价格不符合 tick 被拒 - 价格必须是该市场
minimum_tick_size的整数倍。通过/tick-size?token_id=获取后,在签名前先取整,否则订单会被退回。 - 404 "No orderbook exists" - 你在已关闭或已结算的 token 上查询了
/book、/price或/midpoint。使用/sampling-markets查找仍有实时订单簿的 token。 - WebSocket 数据过期 - 按资产跟踪最后一条消息的时间;如果活跃市场超过 30 秒没有更新,强制通过 REST 刷新。
- Nonce 冲突 - py-clob-client 会为你处理订单 nonce;但如果你自己实现签名器,每个订单都要递增 nonce。
- 余额不足 - 下单前始终检查 USDC 余额;订单簿可能会显示你的订单,但撮合时会拒绝它。
- 市场暂停或正在结算 - 交易前检查
market.active && !market.closed。在结算前后,Gamma 的更新会比 CLOB 滞后几秒。 - NegRisk 适配器不匹配 - 多结果市场会通过单独的 NegRisk 适配器路由。SDK 会处理这一点,但你仍应确认订单进入了正确的交易场所。
第 12 部分:通过 API 获取流动性奖励
Polymarket 每月提供约 500 万美元的通用流动性奖励,另有每月 500 多万美元的体育专项奖励(见 Liquidity Rewards)。其中大部分流向由 API 驱动的做市商。他们在数千个市场中持续提供紧密的双边报价。
奖励公式偏好接近中间价的订单,并会考虑数量和挂单时长。下面是一个最小化的做市循环:
- 读取目标市场的订单簿
- 计算公允中间价(例如两侧各前 3 档的 VWAP)
- 在
mid − spread_target/2挂买单,在mid + spread_target/2挂卖单 - 每次 WebSocket 更新时,如果你的报价相对目标偏离超过一个 tick,就重新定价
- 如果订单簿变薄或有突发新闻,撤单并退出
第 13 部分:投入生产
- 托管:欧洲或美国东部的一台每月 6 美元 VPS(Hetzner、DigitalOcean)足以运行大多数机器人。如果你需要低于 10 毫秒的延迟,可与 Polygon RPC 部署在同一区域。
- RPC:使用 Alchemy、Infura 或 QuickNode 获取可靠的 Polygon RPC。免费套餐足够使用,直到你每分钟下数百个订单为止。
- 监控:用 Prometheus + Grafana 监控指标;用 Telegram bot 发送告警。记录你发送的每一个订单 ID,以及收到的每一笔成交。
- 备份:每分钟持久化状态。如果 VPS 在成交过程中宕机,你需要在几秒内恢复,而不是手动对账。
- 税务:你的日志同时也是审计轨迹——参见 Tax Guide。
第 14 部分 - 经过验证的 Polymarket API 实战技巧
场景 → 操作速查表
| Situation | Action | Why |
|---|---|---|
| 首次调用返回 401 "invalid api key" | 检查 signature_type 是否与钱包来源匹配,并确认 funder 是代理地址 | 类型 1 和类型 2 不匹配占 401 错误的 80%;其余通常是把 EOA 用作 funder |
| 订单因 "insufficient balance" 被拒绝 | 每次下单前查询 /balance-allowance,并在本地预留余额 | 你一提交订单,CLOB 就会立即锁定抵押品;两个并发订单可能会重复占用同一笔资金 |
| /order endpoint 出现 429 限流 | 使用带抖动的退避策略:2^attempt + random(),上限 30 秒 | Cloudflare 通常会限流而不是直接拒绝;简单重试会放大积压 |
| 交易过程中 WebSocket 断开 | 通过 REST 获取订单簿快照,对齐本地状态,然后重新订阅 | 断线期间的增量更新已经丢失;快照可以重新同步价格档位 |
| 订单已提交但没有成交确认 | 在 5 秒内查询 /data/order/{id};如果是 pending 就等待;如果 not found,则重新下单 | 这种情况少见但可恢复;默认流程应是“先查状态,再采取动作” |
| 活跃报价期间市场完成结算 | 收到结算事件后,取消该 conditionId 下所有未成交订单 | 如果适配器存在边缘行为,结算后的订单可能残留,并产生“僵尸成交” |
| 运行做市 bot | 在 midpoint 2 美分范围内报价,且规模至少 100 股 | 奖励公式会综合考虑报价紧密度、规模和挂单时长;价差窄、规模大且持续挂单更占优 |
| 在多结果市场运行套利 bot | 每条腿都使用 FOK,不要使用 GTC | A 腿部分成交而 B 腿全部成交,会导致未对冲敞口并立即产生亏损 |
| 第一次构建 bot | 先构建扫描器,再构建定价引擎,最后才是信号——不要一开始就做信号 | 没有干净的订单簿状态,信号很容易变成相关性陷阱;先把数据管道跑通 |
| 生产环境 bot 在凌晨 3 点崩溃 | 配置 systemd 自动重启 + Telegram 告警 + 持久化状态 | 无人值守的 bot 迟早会崩溃;关键是它能否干净地重启 |
接下来做什么?
- 工具与资源 - 可与 API 配合使用的第三方仪表板、分析工具和数据源
- 高级策略 - 适合 bot 的多腿套利和类期权结构
- 流动性奖励 - 赚取做市返佣的精确公式
- 订单簿指南 - 在编写对接代码前,更深入理解如何阅读订单簿
- 术语表 - 本指南中每个术语的通俗英文定义











