简要概览

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,可以让你的机器人保持快速、简单,并且不触发速率限制。

APIBase URLPurposeAuth Required
CLOB APIclob.polymarket.com下单、取消订单并跟踪订单。读取订单簿。查询持仓。是(交易需要)
Gamma APIgamma-api.polymarket.com浏览市场,获取元数据、图片、结果价格、成交量、到期时间和标签。否(公开)
Data APIdata-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 查询参数

ParameterWhat it does
tag_slug按类别筛选(政治、体育、加密货币、文化等)
active=true仅返回当前可交易的市场
closed=false隐藏已结算的市场
order=volume24hr按近期成交量排序(流动性信号)
end_date_minISO 日期 - 跳过过快结算的市场
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 月通过线上验证。

EndpointMethodWhat it returns
/marketsGET所有市场(通过 next_cursor 分页)。包含 condition_idtokens[]minimum_tick_sizeneg_risk
/sampling-marketsGET仅返回具有实时订单簿的市场——这是查找可交易 token_id 的最快方式。
/book?token_id=GET完整订单簿:bids[]asks[],包含价格和数量。
/price?token_id=&side=buyGET某一方向的最优价格。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 = 1m1h6h1d1wmax
/tradesGET近期交易(查询自己的交易需要认证;查询市场交易为公开接口)。
/orderPOST提交已签名订单(需要认证)。
/orderDELETE按 id 取消单个订单(需要认证)。
/ordersGET你的未成交挂单(需要认证)。
/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 会替你完成这些操作;下面是它在内部构造的内容:

HeaderWhat 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)

订单参数速览

FieldMeaningNotes
token_id你正在交易的结果不是 condition_id——见第 5 部分
sideBUYSELLBUY 需要 USDC;SELL 需要份额
price0.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"])

订单类型

TypeCodeBehaviourWhen 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 classLimitBurst
下单(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 机器人都有相同的六个组件。请将每个组件构建为独立模块,并保持松耦合。

ComponentResponsibilityAPIs 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 驱动的做市商。他们在数千个市场中持续提供紧密的双边报价。

奖励公式偏好接近中间价的订单,并会考虑数量和挂单时长。下面是一个最小化的做市循环:

  1. 读取目标市场的订单簿
  2. 计算公允中间价(例如两侧各前 3 档的 VWAP)
  3. mid − spread_target/2 挂买单,在 mid + spread_target/2 挂卖单
  4. 每次 WebSocket 更新时,如果你的报价相对目标偏离超过一个 tick,就重新定价
  5. 如果订单簿变薄或有突发新闻,撤单并退出

第 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 实战技巧

场景 → 操作速查表

SituationActionWhy
首次调用返回 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,不要使用 GTCA 腿部分成交而 B 腿全部成交,会导致未对冲敞口并立即产生亏损
第一次构建 bot先构建扫描器,再构建定价引擎,最后才是信号——不要一开始就做信号没有干净的订单簿状态,信号很容易变成相关性陷阱;先把数据管道跑通
生产环境 bot 在凌晨 3 点崩溃配置 systemd 自动重启 + Telegram 告警 + 持久化状态无人值守的 bot 迟早会崩溃;关键是它能否干净地重启

接下来做什么?

  • 工具与资源 - 可与 API 配合使用的第三方仪表板、分析工具和数据源
  • 高级策略 - 适合 bot 的多腿套利和类期权结构
  • 流动性奖励 - 赚取做市返佣的精确公式
  • 订单簿指南 - 在编写对接代码前,更深入理解如何阅读订单簿
  • 术语表 - 本指南中每个术语的通俗英文定义

快速自测