Skip to content

Repository files navigation

FinHub API · Polymarket 5 分钟涨跌盘数据 API · Python 客户端

本目录可直接 git init 后发布到 GitHub —— 里面只有源码、示例与文档, 不含任何密钥、凭证或私有地址,也不依赖本机环境。

Python License: MIT Deps: none

一句话:这是 FinHub API 提供的 Polymarket 5 分钟涨跌盘(up/down)结算输入数据的官方 Python 客户端 —— 给你官方 Chainlink TWAP60 + 多家现货报价 + CLOB 盘口,约 2 秒一条, 覆盖 7 个市场,零第三方依赖。

接口文档:https://api.wanminguo.top/quant/polymarket/docs.php · 端点清单:https://api.wanminguo.top/quant/polymarket/endpoints.php


目录


这是什么

Polymarket 上的 5 分钟涨跌盘结算时,看的是 「Chainlink 提供的 TWAP 与区间起点价格的比较」,不看任何现货市场。 所以做这类盘,你真正需要的输入只有三件事:

  1. 区间起点价格(price to beat)—— 而且必须是官方 Chainlink 读数;
  2. 区间内的官方 TWAP60 现值 —— 它相对起点的 bp 就是结算方向的直接输入;
  3. 多家现货报价与盘口 —— 用来判断「官方会不会跟过来」以及市场怎么定价。

自己凑这三件事很痛苦:要分别对接 Chainlink 数据流、六家交易所的行情、 Polymarket 的 CLOB,还要把观测时刻对齐到同一拍上。

本 API 把这些直接给你,以约 2 秒一条的节奏,7 个市场全覆盖:

你需要的 接口字段
区间起点(官方) price_to_beat + beat_source + beat_trusted
官方现值距起点的 bp official_bp
现货中位距起点的 bp spot_bp
60 秒动量距起点的 bp momentum_bp
各家现货分别的 bp venue_bp(spot_n_sources 是这一拍实际取到的家数)
盘口与隐含概率 implied_up / yes_bid / yes_ask / no_bid / no_ask / liquidity
现货与官方 TWAP60 之间的基差 spot_basis_bp
结算核对 /v1/settle.php 的 outcome + 四条规则 rules.so/sc/to/tc(可自己复算)

数据是结算输入,不是交易建议;接口不托管资金、不需要下单权限。


7 个市场

?market= 短名,默认 btc:

?market= slug 前缀 现货源数 官方 Chainlink TWAP60
btc(默认) btc-updown-5m 6 ✅
eth eth-updown-5m 6 ✅
sol sol-updown-5m 6 ✅
xrp xrp-updown-5m 6 ✅
doge doge-updown-5m 5 ✅
bnb bnb-updown-5m 5 ✅
hype hype-updown-5m 4(无 coinbase / kraken) ✅

market 参数认四种写法,都归一化到同一个前缀:

?market=eth    ?market=ETH    ?market=ETH-5m    ?market=eth-updown-5m

非法值返回 400 bad_market,响应体里带 markets 数组列出全部可用短名。

现货源数不是恒定的 6。 真实家数看响应里的 spot_n_sources; venue_bp 里出现的键才是这一拍真正取到的源。HYPE 只有 4 家 —— 做「多源中位」类信号时请按实际家数判断,不要写死 6。

官方读数 7 个市场都有,所以 price_to_beat 的 beat_source 正常都是 official;少数窗口官方流没进来时会退化成 computed,用 beat_trusted 判断。


快速开始

1. 注册并领取免费 key

打开 https://api.wanminguo.top/me/register.php, 免费档(free,0 元)当场就能取当前窗口的完整数据(含 2 秒序列), 不需要付费就能验证这份数据是否对你有用。

2. 设置环境变量

# Linux / macOS
export PM_API_KEY=pm_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# Windows (cmd)
set PM_API_KEY=pm_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# Windows (PowerShell)
$env:PM_API_KEY = "pm_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

本仓库不含任何 key,也请不要把你的 key 提交进 Git。

3. 发第一个请求

curl -H "X-Api-Key: $PM_API_KEY" \
  'https://api.wanminguo.top/quant/polymarket/v1/window.php?market=btc'

4. 用这个 Python 客户端

# 直接跑仓库里的示例,不需要 pip install 任何东西
python examples/current_window.py
from pm_api_client import PmApi

api = PmApi()                        # 读环境变量 PM_API_KEY
data, meta = api.window(market='btc')  # 当前窗口快照

print(data.price_to_beat)   # 区间起点(官方 Chainlink 读数)
print(data.official_bp)     # 官方 TWAP60 距起点多少 bp —— 结算方向的直接输入
print(data.spot_bp)         # 六家现货中位距起点多少 bp
print(data.leading_side)    # UP / DOWN / FLAT

# ★ 必须检查这个标记
if data.beat_trusted is False:
    # 该窗口官方读数没进来,price_to_beat 退化成自算值(系统性偏约 3bp)
    # —— 此时不要用它判方向
    pass

print(meta.remaining)               # 剩余配额,低于 10% 就该降频

或者用 curl / 任意语言直接打 HTTP —— 这就是个普通的 JSON API。


为什么 price_to_beat 必须用官方 Chainlink 读数

这是本 API 最核心的卖点,请务必读完这一节。

Polymarket 的市场规则原文写的是:

the TWAP of Bitcoin, generated by Chainlink, of the time range specified in the title … greater than or equal to the price at the beginning of that range … not according to any other sources or spot markets

也就是说:锚点必须是官方读数,现货市场的价格不算数。

于是问题来了 —— 数据里存在两个看起来都像「起点值」的数,语义却完全不同:

字段 语义 能当 beat 吗
twap60_off_start 开盘那一刻的官方 Chainlink 流读数 ✅ 就是它
twap60_at_start 多家平台中位价自算的 TWAP60 起点值 ❌

自算值带来的是系统性偏差,不是随机噪声。 实测 60 个窗口:

  • 官方 − 自算 = 中位 −2.72 bp(区间 −3.37 ~ −1.26 bp)
  • 而 60 秒位移的中位数只有 1.83 bp
  • 有 3% 的窗口,用官方 beat 与用自算 beat 得出的方向是相反的

偏差的量级(2.72 bp)已经超过了信号本身的量级(1.83 bp)。 这意味着:如果你用自算值当锚点,你不是在「稍微不准」, 而是在系统性地把一部分窗口判反。

本 API 优先使用官方值,并把来源如实返回:

字段 含义
price_to_beat 锚点值
beat_source official / computed / none
beat_trusted false = 该窗口官方读数没进来,price_to_beat 退化成自算值(偏约 3bp,判方向不可信)

给调用方的建议:beat_trusted == false 时不要用它判方向。 线上实测官方值覆盖率约 98%,退化是低频事件 —— 但低频不等于可以忽略, 因为它的后果是「方向判反」而不是「数值略偏」。

本客户端如实透传 beat_source / beat_trusted,绝不吞掉或平滑这两个标记。 data.beat_trusted is False 时请自行跳过该窗口。

另外,接口把「官方量」与「自算量」分开存放,不会混淆:

  • 官方组:official / official_bp / official_ts / official_source
  • 自算组:computed_twap60 / computed_at_start / computed_bp

想把「官方 vs 自算」的差异本身做信号?直接用 computed_* 那几个字段就行。 注意 spot_at_start 是「边界点假设」的锚点,不是 price to beat,别混用。


端点表

基线:https://api.wanminguo.top/quant/polymarket/v1/ 鉴权:每个请求都要带 X-Api-Key: <你的 key>(也支持 Authorization: Bearer <key>; ?api_key= 能用但不推荐 —— 会进 access log)

⚠️ 端点路径都带 .php(如 /v1/window.php)。本站没有配置 URL rewrite, 写成 /v1/window 会直接 404。

端点 参数 说明
GET /v1/window.php ★ market, slug, series, n, raw 主力端点。当前(或指定)窗口的快照:price to beat、距 beat 的 bp、剩余时间、隐含概率。series=1 附带时间序列(当前窗口任何套餐可用;历史窗口需样本权限),n 为序列点数 1~2000(超了等距抽样,末点必留),raw=1 带原始盘口字段(需 samples_raw 功能)
GET /v1/settle.php market, slug 或 last 结算核对:官方 outcome + 四条规则读数 rules.A/B/C/D(含 so/sc/to/tc,可自己复算)。last 默认 20,上限 500;market 默认 all
GET /v1/history.php market, from, to, limit, offset, compact 历史窗口列表,新的在前。from/to 接受 unix 秒或 2026-09-22;limit 上限 = 套餐 max_windows_per_call;compact=1 只返 slug/start/outcome/n_samples;older_available 便于翻页。范围超限整体 403,不做静默截断
GET /v1/samples.php slug(必填), limit, offset, tail, raw 原始 2 秒样本。需套餐 max_samples_per_call > 0(免费档为 0)。tail=1 取最后 N 条;raw=1 返回采集器原始 JSONL 字段
GET /v1/stats.php — 预聚合统计 + 完整健康度。免费档可用,用来快速判断数据质量与规则表现
GET /v1/index.php — 端点清单 / 市场清单 / 套餐表(客户端自举用,建议启动时读一次而不是硬编码路径)。★ 免鉴权,但响应是扁平结构(无 data/meta)
GET /v1/signals.php † since, market, wait, limit 信号订阅线(按成交次数计费,不占每日请求配额):长轮询取实时信号,带 token_id。详见 信号订阅
POST /v1/receipt.php † signal_id, filled_shares, … 信号订阅线:下单回执上报 —— 唯一扣次的地方
GET /v1/credits.php † ledger 信号订阅线:次数余额 / 流水 / 价目表 / 当前计次口径

† 这三个属于信号订阅那条产品线:鉴权方式相同(同一个 X-Api-Key), 但计费单位是成交次数而不是请求数,且不消耗 daily_quota。

补充细节:

  • 约 2 秒一条。轮询 window.php 的合理频率是 1~2 秒 —— 数据本身就是 2 秒一条,更密没有意义。
  • 一个 5 分钟窗口大约 150 条样本。
  • /v1/settle.php 的数据源已按 slug 去重(采集器重启会重复结算同一窗口,接口保留最后一条)。
  • /v1/history.php 与 /v1/settle.php 的 market 默认是 all(全部 7 个市场);/v1/window.php 的 market 默认是 btc。
  • 传了 slug 时,market 参数不起作用(按 slug 取数)。
  • /v1/index.php 是唯一免鉴权的端点(不用带 X-Api-Key), 也是唯一不使用标准信封的端点 —— 见 响应信封 末尾那一节。
  • 其它所有 /v1/* 端点都必须带 key,没有第二个免鉴权的公开端点。
  • 上表里标 † 的三个是信号订阅端点:按成交次数计费, 不消耗本节的每日请求配额(两套计量单位互不影响)。

响应信封

成功(ok: true):

{
  "ok": true,
  "data": { "...": "具体载荷,见端点表" },
  "meta": {
    "endpoint": "window",
    "plan": "pro",
    "key_prefix": "pm_live_ab12",
    "quota": { "daily_limit": 500000, "used_today": 8123,
               "remaining": 491877, "reset_at": "2026-09-27T00:00:00Z" },
    "limits": { "qps": 60, "daily_quota": 500000, "history_days": 90,
                "max_windows_per_call": 2000, "max_samples_per_call": 2000,
                "features": { "samples_raw": 1, "stream": 1, "bulk": 0 } },
    "feed": { "ok": true, "latest_slug": "btc-updown-5m-1790389200",
              "latest_age_sec": 43 },
    "server_ts": 1790389243
  }
}

失败(ok: false):

{ "ok": false, "error": "daily_quota_exceeded",
  "message": "今日配额已用完(500/500),UTC 零点重置",
  "quota": { "daily_limit": 500, "used_today": 500,
             "reset_at": "2026-09-27T00:00:00Z" } }

几个要点:

  • meta.quota 每次都会返回 —— 客户端据此提前退避,不用等撞 429。 (唯一例外是 /v1/index.php,它没有 meta,见下一节。)
  • meta.limits 告诉你当前套餐的真实上限,客户端可以据此自适配(本仓库的 iter_history 就是这么做的:先读 max_windows_per_call 再决定页大小)。
  • meta.feed 是采集侧的新鲜度(ok / latest_slug / latest_age_sec), 用来自查「我拿到的是不是陈数据」。注意它不含任何服务器路径。
  • meta.limits.features 是功能开关:samples_raw / stream / bulk。

限速相关响应头:X-Quota-Limit / X-Quota-Used / X-Quota-Remaining / Retry-After。

★ 唯一的例外:/v1/index.php 是扁平结构

全站只有 /v1/index.php 不走上面这个信封 —— 它既没有 data 也没有 meta, 所有字段都在顶层。真实顶层键清单:

ok, service, note, auth, endpoints, markets, market_codes,
response_envelope, error_codes, plans, health, server_ts

所以就这个端点而言:meta.quota / meta.limits 不存在(读不到配额与限额), endpoints / markets / market_codes / plans / error_codes 直接从顶层取。

本客户端对两种形状都能解析 —— 按「响应里有没有 data 层」判断形状, 而不是给 index.php 打特例补丁,所以将来站点把它统一成标准信封也不会坏:

data, meta = api.index()
data["markets"]        # ['btc-updown-5m', 'eth-updown-5m', ...]
data["market_codes"]   # ['btc', 'eth', 'sol', 'xrp', 'doge', 'hype', 'bnb']
data["plans"]          # 套餐表

/v1/index.php 也是唯一免鉴权的端点(不需要 X-Api-Key), 所以还没注册 key 也能先自举看一眼这个 API 有什么:

from pm_api_client import PmApi

api = PmApi.public()          # 不带 key 的实例
data, _ = api.index()         # 免鉴权,能拿到端点清单与套餐表

客户端遇到「扁平形状」时会往 stderr 打一次提示(提醒你 meta 是空的), 设 PM_QUIET_DEVIATION=1 可关掉。


错误码表

HTTP error 含义
400 missing_param / bad_slug / bad_param 参数缺失或非法
400 bad_market ?market= 认不出来(响应带 markets 数组给出可用值)
401 missing_api_key 没带 key
401 invalid_api_key key 不存在
403 key_revoked / key_expired / customer_inactive / plan_retired key 或套餐已停用
403 history_not_in_plan 套餐不含历史(history_days = 0,当前窗口仍可访问)
403 history_depth_exceeded 请求的窗口超出套餐可回溯天数
403 samples_not_in_plan 套餐不含原始样本(max_samples_per_call = 0;历史窗口的 /v1/window.php?series=1 同门槛)
403 plan_feature_denied 套餐不含该功能开关(如 samples_raw)
404 window_not_found / settle_not_found 没有该窗口
429 rate_limited 超过 QPS(带 Retry-After: 1)—— 退避后重试
429 daily_quota_exceeded 当日配额用完 —— 不要按 Retry-After 重试,等到 quota.reset_at(UTC 零点)
503 feed_root_missing / no_window / billing_db_unavailable 服务端数据/计费库不可用

两种 429 必须分开处理

这是最容易写错的地方:

from pm_api_client import PmApi, PmError

api = PmApi()
try:
    data, meta = api.window()
except PmError as e:
    if e.is_rate_limited:
        import time; time.sleep(e.retry_after or 1.0)   # 退避后重试,有意义
    elif e.is_quota_exceeded:
        print("等 %s 再跑" % e.reset_at)                 # 重试没有意义!
    else:
        raise

本客户端只对 rate_limited 自动重试(默认 2 次), daily_quota_exceeded 永远不会被自动重试 —— 这是刻意的设计。


套餐与限额

code U/月 QPS 日配额 历史 样本/次 说明
free 0 1 500 1 天 —(0) 当前窗口 + 1 天历史 + 汇总统计
basic 29 5 20,000 7 天 200 加原始样本
pro 129 60 500,000 90 天 2,000 全量
internal — 500 100,000,000(实际等于无限) 3650 天 100,000 自用

配额按 UTC 天计,UTC 零点重置。

字段语义(别猜):

  • daily_quota = 0 ⇒ 不限量(不是禁用)
  • history_days = 0 ⇒ 只能访问当前窗口,历史端点 403
  • max_samples_per_call = 0 ⇒ 禁用原始样本(/v1/samples.php 与历史窗口的 /v1/window.php?series=1 都拒)

免费档能做什么:free 档就能取当前窗口的完整数据(含 2 秒序列) —— 这是设计上的漏斗口,不受 history_days / max_samples_per_call 限制。 受限的是历史。

★ 但注意免费档的日配额只有 500 次/天:按 2 秒轮询,约 17 分钟就打光 (1 秒轮询还会撞 qps = 1)。免费档适合「看一眼当前窗口」; 要持续轮询请上 basic 以上。套餐详情见 https://api.wanminguo.top/quant/polymarket/endpoints.php。

上表是「数据 API」的套餐(按月 + 按请求数)。 信号订阅那条线不看这张表:它按成交次数计费 (注册送 10 次 / 9.9U=300 次 / 19.9U=800 次),并且信号端点不消耗这里的 daily_quota。


客户端用法

安装

不需要安装,也不需要 pip install —— 只用标准库。

# 把 pm_api_client.py 拷到你的项目里就能用
cp finhub-api-client/pm_api_client.py your_project/

或者让它可被导入:

export PYTHONPATH="$PYTHONPATH:$(pwd)/finhub-api-client"

requirements.txt 是空的(只有注释)—— 这就是真实状态:零依赖。

基本用法

from pm_api_client import PmApi, PmError, MARKETS

api = PmApi()                       # key 从环境变量 PM_API_KEY 读
# api = PmApi(api_key="pm_live_...")                # 显式传
# api = PmApi(timeout=5, max_retries=0)             # 5 秒超时,关掉自动重试
# api = PmApi.public()                              # 不带 key(只能打 index.php)

端点方法

所有方法都返回 (data, meta):

方法 对应端点
api.window(market='btc', slug=None, series=False, n=None, raw=False) /v1/window.php
api.settle(market=None, slug=None, last=None) /v1/settle.php
api.history(market=None, frm=None, to=None, limit=None, offset=None, compact=False) /v1/history.php
api.samples(slug, limit=None, offset=None, tail=False, raw=False) /v1/samples.php
api.stats() /v1/stats.php
api.index() /v1/index.php(★ 扁平结构,无 data/meta,免鉴权)
api.iter_history(...) /v1/history.php 自动翻页(生成器)

data 是一个 dict 子类,既支持下标也支持属性访问:

data['official_bp']        # dict 风格(字段名是 Python 保留字时必须用这种,
data.official_bp           # 属性风格
data.get('beat_trusted')
'series' in data

meta 是一个 Meta 对象:

meta.plan                  # 'free' / 'basic' / 'pro' / 'internal'
meta.quota.remaining       # 剩余配额
meta.quota.remaining_ratio # 剩余比例(0.0 ~ 1.0)
meta.should_back_off()     # 剩余 < 10% 时为 True —— 该降频了
meta.history_days          # 套餐可回溯天数
meta.max_samples_per_call  # 单次样本上限
meta.feed.ok               # 采集侧是否健康
meta.feed.latest_age_sec   # 最新数据多少秒前

处理错误

from pm_api_client import PmApi, PmError, PmQuotaExceeded, PmRateLimited

api = PmApi()
try:
    data, meta = api.window(market='sol')
except PmRateLimited as e:              # 429 rate_limited
    print("超 QPS,%.1fs 后重试" % (e.retry_after or 1.0))
except PmQuotaExceeded as e:            # 429 daily_quota_exceeded
    print("今日配额用完,%s 重置" % e.reset_at)
except PmError as e:
    print("HTTP %s  %s  %s" % (e.status, e.code, e.message))
    if e.available_markets:
        print("可用市场:", e.available_markets)

PmError 上可用的信息:status / code / message / retry_after / quota / reset_at / available_markets / payload(完整原始 body)。

★ 正确读取 beat_trusted

data, meta = api.window(market='eth')

if data.beat_trusted is False:
    # 官方 Chainlink 读数缺失,price_to_beat 是自算退化值(偏约 3bp)
    # 偏差量级 > 60 秒真实位移的中位数 —— 用它判方向会被系统性地判反
    skip_this_window()
else:
    direction_hint = data.leading_side     # UP / DOWN / FLAT
    strength_bp = data.official_bp         # 距 beat 多少 bp

data.beat_trusted 在载荷没有该字段时返回 None; 严格判断请用 data.is_trusted_beat(只有明确 True 才算可信)。

轮询当前窗口

import time
from pm_api_client import PmApi, PmError, SAMPLE_INTERVAL_SEC

api = PmApi()
last_slug = None
while True:
    try:
        data, meta = api.window(market='btc')
    except PmError as e:
        if e.is_quota_exceeded:
            print("配额用完,等到 %s" % e.reset_at)
            break                     # ★ 不要重试
        raise

    if data.get('slug') != last_slug:
        last_slug = data.get('slug')
        print("=== 新窗口 %s ===" % last_slug)

    print("%s  官方 %+.2f bp  现货 %+.2f bp  领先 %s  可信=%s"
          % (last_slug, data.get('official_bp') or 0.0,
             data.get('spot_bp') or 0.0, data.get('leading_side'),
             data.beat_trusted))

    # 配额低于 10% 就降频
    time.sleep(SAMPLE_INTERVAL_SEC * (8 if meta.should_back_off() else 1))

信号订阅(另一条产品线)

本仓库的根目录(pm_api_client.py / examples/)是「数据 API」那条线; 这一节讲的是同站的另一条产品线 —— 实时信号订阅。 两者鉴权方式相同(同一个 key), 但计费口径完全不同:数据 API 按月 + 按请求数,信号订阅按成交次数。 客户端源码在本仓库的 signal-client/,成品安装包在 https://api.wanminguo.top/download/。

它是什么

订阅 BTC-5m 的实时进场信号:平台把信号推送到你自己电脑上的客户端, 客户端在本地下单(私钥只在你机器上,平台不代持、不代下单), 成交后把回执回传给平台,平台按回执扣「次数」。

端点 参数 说明
GET /v1/signals.php since, market, wait(0~15, 默认 10), limit(≤50) 长轮询取新信号。每条带 signal_id(slug:trade_seq)、方向、信号价、limit_price/limit_capped/over_cap、condition_id + Up/Down 的 token_id(实盘下单必需)、ask_sz(那一刻最优档挂单量)
POST /v1/receipt.php signal_id, filled_shares(必填), requested_shares, avg_price, status, order_id 唯一扣次的地方。只接受平台确实投递过的 signal_id;没成交(filled_shares=0)不扣
GET /v1/credits.php ledger(≤200) 次数余额 + 流水 + 订阅市场 + 价目表 + 当前计次口径(credit_rule)

计费与执行口径(写死在这里,避免猜):

  • 1 次 = 一个成功下单回执(不看份数:×1 和 ×5 都只扣 1 次);没成交不扣; status=paper/dry 之类的纸面回执不计次(服务端按 0 成交处理)。
  • 同一个 (signal_id, key_id) 只会扣一次 —— 重发、并发、分笔成交都幂等。
  • 一把 key 只绑一个市场(当前只开放 BTC-5m),绑定后不可改。
  • 客户端默认按「信号价 + 0.05、硬上限 0.85」下 FAK 限价单:挂不上就不成交,不追高。
  • 次数用光时 /v1/signals.php 返回 402 no_credits;信号端点不消耗每日请求配额 (那是数据 API 的计量单位),但仍有秒级限速。
  • 价目:注册送 10 次、9.9U / 300 次、19.9U / 800 次。 ★ 按当前口径,套餐寿命与倍数无关(300 次 ≈ 4.4 天,实测约 68 条信号/天); 平台另留了「按份数」口径(1 次 = 10 份成交、×5 一单扣 5 次)但未启用 —— 以接口返回的 charge_mode / credit_rule 为准。

先说风险(这是本节最重要的一段)

平台不承诺任何收益。用真实历史(93 单 / 1.36 天)按客户端口径回测的结果是:

  • 胜率 78.5%(95% 置信区间 69.1%~85.6%),而盈亏平衡胜率 = 平均成交价 74.8% → 置信区间跨过平衡点,这段历史分不出正负;
  • 滑点从 0 加到 0.05,每单少赚 0.500U;
  • 盘口深度会吃掉倍数:×5 时近 40% 的信号那一档挂单量不够,每单收益从 1.855U 掉到 0.961U,却要多付 5 倍次数费。

完整报告(含分档表格与读取方法):https://api.wanminguo.top/download/backtest-report.txt; 客户端的安装、参数、隧道与常见问题见 signal-client/README.md。

国内客户下单需要出网隧道(平台提供,按 SNI 白名单转发、不解密,私钥不会经过平台), 细节同样写在客户端的 README 里。


因子与信号目录

本站采集器在采数时评估了一批信号(多源基差、盘口滞后、动量、深度、边界点等族), 这些评估出来的因子不在这条数据 API 的响应里、也不是本站推荐的下单依据 —— 它们只是让你知道「这份数据被从哪些角度检查过」;完整清单、每族的判定口径与实测表现见 https://api.wanminguo.top/quant/polymarket/factors.php。

⚠️ 别把两件事搞混:上面那些是数据侧评估的因子(不对外下单); 而 信号订阅 是另一条产品线,有自己的端点与客户端, 按成交次数计费。

本数据 API 卖的是原始结算输入数据,不是策略或建议;因子页上的数字 是研究者自己的信号口径,请结合 ? 号口径自行复算。


示例脚本

四个都能直接跑,都支持 --help,都会在没设 key 时给出清晰提示:

文件 演示什么
examples/current_window.py 取当前窗口快照;打印三个 bp 与 beat_trusted;--series 拉 2 秒序列并用纯标准库画 ASCII 走势图
examples/settle_recent.py 取最近 N 个已结算窗口;--verify 用 rules.so/sc/to/tc 在本地自己复算四条规则并与官方 outcome 对比一致率
examples/history.py 按时间范围拉历史列表;手动 limit/offset 翻页 + iter_history 自动翻页;正确处理 history_depth_exceeded
examples/samples.py 拉某窗口的原始 2 秒样本;--tail 取尾部;--raw 原始字段;--csv 导出
python examples/current_window.py --help
python examples/current_window.py --market eth --series --n 60
python examples/settle_recent.py -n 30 --market btc --verify
python examples/history.py --days 1 --pages 3
python examples/samples.py --slug btc-updown-5m-1790389200 --tail 30 --csv out.csv

常见问题

Q:为什么路径要写 .php? 本站没有配置 URL rewrite,/v1/window 会 404。所有端点路径都带 .php。

Q:没有 key 能试吗? 只能试一个端点 —— /v1/index.php 免鉴权,能拿到端点清单、市场清单与套餐表 (PmApi.public() 就是给这一步用的)。除此之外所有 /v1/* 都要 key。 数据本身仍然要注册(free 档 0 元),注册当场就能取当前窗口的完整数据 (含 2 秒序列),这是唯一能证明数据对你有用的方式: https://api.wanminguo.top/me/register.php

Q:为什么 api.index() 拿不到 meta.quota? 因为 /v1/index.php 是全站唯一扁平的端点 —— 它没有 data 也没有 meta, 字段全在顶层。本客户端两种形状都能解析(按有没有 data 层判断), 见 响应信封 末尾那一节。

Q:meta.quota.remaining 是 0 是不是就彻底不能用了? 看 daily_limit:daily_quota = 0 的语义是不限量,不是禁用。 remaining 才是真实剩余次数。

Q:有 WebSocket / 推送吗? 目前是轮询制。要做实时推送请单独联系。轮询间隔 1~2 秒即可, 更密没有意义(数据本身就是 2 秒一条)。

Q:为什么现货源数有时不是 6? spot_n_sources 是这一拍实际取到的家数(HYPE 只有 4 家)。 做「多源中位」类信号时请按实际家数判断,不要写死 6。

Q:spot_at_start 能当 price to beat 用吗? 不能。 它是「边界点假设」的锚点,不是 price to beat。 price to beat 只有一个正确来源:官方 Chainlink 读数, 见 上面那一节。

Q:遇到 429 就重试,可以吗? 分两种。rate_limited 可以按 Retry-After 退避重试; daily_quota_exceeded 重试没有意义,只会白烧请求,请等到 quota.reset_at。

Q:数据能拿去做什么? 做结算方向的判读、做「官方会不会跟过来」的判断、做市场隐含概率与你的 模型隐含概率之间的偏差分析、做策略回测。数据是结算输入, 不构成任何投资建议。


English

FinHub API · Polymarket 5-minute up/down market data API — official Python client. Zero third-party dependencies (standard library only).

This API provides the settlement inputs for Polymarket's 5-minute up/down markets: the official Chainlink TWAP60, multi-venue spot quotes, and CLOB order-book data, at ~1 sample per 2 seconds, across 7 markets (BTC / ETH / SOL / XRP / DOGE / HYPE / BNB).

Signal subscription (a second product line)

Besides the data API, the same site sells a real-time signal subscription for BTC-5m: the platform pushes signals to a client running on your machine, the client places the order locally (private keys never leave your machine), and posts a fill receipt back — billing is per filled order, not per request.

Endpoint Purpose
GET /v1/signals.php long-poll for new signals (signal_id, side, prices, condition_id, Up/Down token_id, book depth)
POST /v1/receipt.php report a fill — the only place credits are charged (no fill ⇒ no charge)
GET /v1/credits.php credit balance, ledger, subscription, price table, current credit_rule

1 credit = one successful fill receipt (independent of order size: ×1 and ×5 both cost 1 credit); no fill ⇒ no charge; paper/dry receipts are never charged.

Source of the client: signal-client/ in this repo; ready-to-run packages: https://api.wanminguo.top/download/.

No profit is promised. A backtest over real history (93 trades / 1.36 days, same rules the client uses) gives a 78.5% win rate against a 74.8% break-even (average fill price) — the 95% confidence interval straddles break-even, i.e. this sample cannot tell profit from loss. Slippage and thin books matter: at ×5, ~40% of signals were depth-limited and per-trade P&L roughly halved. Full report: https://api.wanminguo.top/download/backtest-report.txt.

Why the official Chainlink reading matters

Polymarket's market rules state that settlement uses "the TWAP of Bitcoin, generated by Chainlink … greater than or equal to the price at the beginning of that range … not according to any other sources or spot markets." So the anchor must be the official reading.

There are two fields that look like "the starting price", with completely different meanings: twap60_off_start (official Chainlink stream reading — ✅ the real price to beat) and twap60_at_start (self-computed median across venues — ❌). Measured over 60 windows, official − computed has a median of −2.72 bp, while the median 60-second move is only 1.83 bp — the bias is larger than the signal, and in 3% of windows the two anchors imply opposite directions.

This API prefers the official value and reports provenance honestly:

Field Meaning
price_to_beat the anchor
beat_source official / computed / none
beat_trusted false = official reading missing, price_to_beat degraded to a computed value (~3 bp bias, unreliable for direction)

Do not use price_to_beat for direction when beat_trusted == false. This client never swallows that flag.

Quick start

export PM_API_KEY=pm_live_xxxx          # get a free key first
python examples/current_window.py       # no pip install needed

Get a free key (the free plan costs nothing and already returns the complete current window including the 2-second series): https://api.wanminguo.top/me/register.php

Endpoints

Base: https://api.wanminguo.top/quant/polymarket/v1/ · Auth header: X-Api-Key

⚠️ All paths end with .php (e.g. /v1/window.php). No URL rewriting is configured — /v1/window returns 404.

Endpoint Params Description
GET /v1/window.php ★ market, slug, series, n, raw Current/specified window snapshot: price to beat, bp vs beat, remaining time, implied probability
GET /v1/settle.php market, slug or last Settlement check: official outcome + four rule readings you can recompute yourself
GET /v1/history.php market, from, to, limit, offset, compact Historical window list, newest first
GET /v1/samples.php slug(required), limit, offset, tail, raw Raw 2-second samples
GET /v1/stats.php — Pre-aggregated stats (available on the free plan)
GET /v1/index.php — Endpoint/market/plan self-description. ★ No auth required, but flat shape (no data/meta)

Response envelope

Success: {"ok": true, "data": {...}, "meta": {"quota": {...}, "limits": {...}, "feed": {...}}}. Failure: {"ok": false, "error": "code", "message": "..."}.

meta.quota is returned on every response so clients can back off before hitting a 429.

One exception: /v1/index.php is flat — it has no data and no meta; every field sits at the top level (ok, service, note, auth, endpoints, markets, market_codes, response_envelope, error_codes, plans, health, server_ts). It is also the only endpoint that needs no API key. This client parses both shapes (it keys off whether a data member exists), so it will keep working if the endpoint is ever normalised.

Error codes

bad_market · missing_api_key · invalid_api_key · key_revoked · history_not_in_plan · history_depth_exceeded · samples_not_in_plan · plan_feature_denied · window_not_found · settle_not_found · rate_limited (back off and retry) · daily_quota_exceeded (do not retry — wait for quota.reset_at) · no_window

Plans

code USD/month QPS Daily quota History Max samples/call
free 0 1 500 1 day —
basic 29 5 20,000 7 days 200
pro 129 60 500,000 90 days 2,000

Quotas reset at 00:00 UTC. Note the free plan's 500/day is exhausted in ~17 minutes at a 2-second polling interval.

Usage

from pm_api_client import PmApi, PmError

api = PmApi()                          # reads PM_API_KEY
data, meta = api.window(market='btc')

print(data.price_to_beat, data.official_bp, data.leading_side)
if data.beat_trusted is False:
    pass                               # unreliable anchor — skip this window
print(meta.quota.remaining)            # back off when this gets low

# No key yet? /v1/index.php needs none (and is flat: no data/meta layer)
pub = PmApi.public()
info, _ = pub.index()
print(info['market_codes'])            # ['btc', 'eth', 'sol', 'xrp', 'doge', 'hype', 'bnb']

Docs: https://api.wanminguo.top/quant/polymarket/docs.php · Endpoints & plans: https://api.wanminguo.top/quant/polymarket/endpoints.php


License

MIT © FinHub API


关于本仓库

  • 零第三方依赖:只用标准库(urllib.request / json / argparse / dataclasses / typing),和站点采集器的风格保持一致。
  • 不含任何密钥:key 只从环境变量 PM_API_KEY 或命令行参数读, 代码里没有任何硬编码凭证、私有地址或内网信息。 (signal-client/ 同理:API key 由 --key 传入,实盘私钥只在客户本机、 既不落盘也不上报。)
  • 目录两条线:
    • 根目录 —— 数据 API 客户端(pm_api_client.py + examples/,本节以上全部内容);
    • signal-client/ —— 信号订阅客户端(实时信号 → 本地下单 → 回执计次), 成品安装包在 https://api.wanminguo.top/download/,源码在此可直接运行/打包。 两者互不依赖,可分别使用。
  • 可直接发布:本目录就是一个完整的、可 git init 后推上 GitHub 的仓库 (发布步骤与 push 前的泄密自查见 PUBLISHING.md, 版本记录见 CHANGELOG.md)。

About

FinHub API —— Polymarket 5 分钟涨跌盘:① 数据 API 客户端(纯标准库)② 信号订阅客户端(实时信号→本地下单→回执计次)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages