# polymarket 数据聚合 API · 接口文档

> 基线：`https://api.wanminguo.top/quant/polymarket/v1/`
> 数据：Polymarket 5 分钟涨跌盘的**结算输入**——官方 Chainlink TWAP60 + 多家现货 + 盘口，**2 秒一条**。
> 覆盖 **7 个市场**，见下节。
>
> ⚠️ **端点路径都带 `.php`**（如 `/v1/window.php`）。本站没有配置 URL rewrite，
> 写成 `/v1/window` 会直接 404。

---

## 市场（★ 2026-09-26 起从「只有 BTC」扩到 7 个）

| `?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` 判断。

---

## 鉴权

每个 `/v1/*` 请求都要带 key：

```http
X-Api-Key: pm_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

也支持 `Authorization: Bearer <key>`；`?api_key=` 能用但**不推荐**（会进 access log）。

没有 key → `401 missing_api_key`。

> **注意例外**：`/v1/` 下**只有 `/v1/index.php`（端点清单）是免鉴权的**
> —— 它就是给客户端自举用的，自己会在 `data.note` 里说明这一点。
> 其余端点一律要 key。
> 但 `free` 档（0 元）就能取**当前窗口的完整数据（含 2 秒序列）**——
> 这是设计上的漏斗口，不受 `history_days` / `max_samples_per_call` 限制。
> 受限的是**历史**：`history_days = 0` 的套餐只能看当前窗口。

---

## 响应信封

成功：

```json
{
  "ok": true,
  "data": { },
  "meta": {
    "endpoint": "window",
    "plan": "pro",
    "key_prefix": "pm_live_ab12",
    "quota": { "mode": "subscription", "daily_limit": null,
               "used_today": 0, "remaining": null, "reset_at": null },
    "credits": { "balance": 10000, "total_in": 10000, "total_used": 0 },
    "limits": { "qps": 60, "daily_quota": 0, "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
  }
}
```

失败：

```json
{ "ok": false, "error": "rate_limited",
  "message": "超过每秒请求上限 20（当前第 21 次）",
  "retry_after_sec": 1 }
```

**`meta.credits.balance` 每次都会返回**——账号额度余额（股），成交 1 股扣 1 额度。
信号订阅制下**没有请求配额**（不再按请求数计费），`meta.quota.mode = "subscription"`。

### 错误码

| 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` | 同一秒请求过多（秒级防护，20 qps 上限；稍等 1 秒重试即可） |
| 503 | `feed_root_missing` / `no_window` / `billing_db_unavailable` | 服务端数据/计费库不可用 |

限速相关响应头：`Retry-After` / `X-RateLimit-Limit` / `X-RateLimit-Remaining`。

---

## `GET /v1/window.php` ★ 主力

当前（或指定）窗口的快照。

| 参数 | 默认 | 说明 |
|---|---|---|
| `market` | 配置的默认市场（`btc`） | `btc` / `eth` / `sol` / `xrp` / `doge` / `hype` / `bnb`（也接受 `ETH-5m` 这种写法）。**只在没传 `slug` 时起作用** —— 传了 `slug` 就按那个 slug 取 |
| `slug` | 最新窗口 | 如 `btc-updown-5m-1790389200` |
| `series` | 0 | `1` = 附带时间序列。**当前窗口**任何套餐都能用（漏斗口）；**历史窗口**需要样本权限（套餐 `max_samples_per_call > 0`） |
| `n` | 240 | 序列最多返回点数，1～2000（超了等距抽样，**末点必留**） |
| `raw` | 0 | `1` = 序列里含原始盘口字段（**需要 `samples_raw` 功能**，体积大） |

```bash
curl -H "X-Api-Key: $KEY" \
  'https://api.wanminguo.top/quant/polymarket/v1/window.php'
```

```json
{
  "ok": true,
  "data": {
    "slug": "btc-updown-5m-1790389200",
    "market": "btc-updown-5m",
    "window_start": 1790389200, "window_end": 1790389500,
    "remaining": 62.4, "last_ts": 1790389437.6, "sample_age_sec": 1.9,
    "n_samples": 149,

    "price_to_beat": 85397.81,
    "beat_source": "official",
    "beat_trusted": true,
    "official": 85740.74,
    "official_source": "official",
    "official_ts": 1790389436.0,
    "official_bp": 40.19,
    "spot_composite": 85750.61,
    "spot_bp": 41.34,
    "momentum_bp": -19.72,
    "leading_side": "UP",
    "venue_bp": { "binance": 41.31, "coinbase": 40.16, "kraken": 38.05,
                  "okx": 41.58, "bybit": 42.02, "bitget": 41.14 },
    "spot_n_sources": 6,

    "computed_twap60": 85760.41,
    "computed_at_start": 85929.06,
    "computed_bp": 42.49,
    "spot_at_start": 85900.0,

    "implied_up": 0.001, "yes_bid": null, "yes_ask": 0.01,
    "no_bid": 0.99, "no_ask": null, "liquidity": 14667.98,

    "settled": { "outcome": "DOWN", "rules": { "A": "DOWN", "...": "..." } }
  }
}
```

### ★ 关于 `price_to_beat` —— 请务必读这一段

市场规则写明是：

> 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）
- 而 60 秒位移的**中位数只有 1.83 bp**
- **有 3% 的窗口，用官方 beat 与用自算 beat 得出的方向是相反的**

本接口优先用官方值，并把来源如实返回：

| 字段 | 含义 |
|---|---|
| `price_to_beat` | 锚点值 |
| `beat_source` | `official` / `computed` / `none` |
| `beat_trusted` | `false` = 该窗口官方读数没进来，`price_to_beat` 退化成自算值（偏 ~3bp，**判方向不可信**） |

**客户侧建议**：`beat_trusted == false` 时不要用它判方向。
（线上实测官方值覆盖率 98%，退化是低频事件。）

### 三个核心数

| 字段 | 含义 |
|---|---|
| `price_to_beat` | 开盘那一刻的**官方**读数 |
| `official_bp` | **官方 TWAP60 现值**距 beat 的 bp —— 这就是结算方向的直接输入 |
| `spot_bp` | 六家现货中位距 beat 的 bp —— 用来判断"官方会不会跟过来" |
| `momentum_bp` | TWAP60 的 60 秒动量距 beat 的 bp |

`leading_side` = 官方现值相对 beat 偏向 `UP` / `DOWN` / `FLAT`。

> 想把"官方 vs 自算"的差异拿来做信号，就用 `computed_*` 那几个字段
> （`computed_twap60` / `computed_bp`）—— 它们与官方量**分开存放**，不会混淆。
> `spot_at_start` 是"边界点假设"的锚点，**不是** price to beat，别混用。

---

## `GET /v1/index.php` —— 端点清单 / 自描述

**免鉴权** —— 这是 `/v1/` 下唯一的公开端点（客户端自举用，不必先有 key）。
返回本站当前支持的端点、参数、市场清单与你的套餐限额，
适合客户端启动时自举（不用把路径硬编码在客户端里）。
带上 key 也不会错，只是没有必要。

> ⚠️ **本端点是全站唯一不走标准信封的**：它**没有** `data` / `meta` 两层，
> 所有字段都在**顶层**。其余 `/v1/*` 一律是 `{ok, data, meta}`。
> 客户端解析时请对本页特例处理，或两种形状都兼容。

```json
{
  "ok": true,
  "service": "polymarket 数据聚合 API",
  "note": "除本页（端点清单）外，其余 /v1/* 端点都需要请求头 X-Api-Key。",
  "auth": { "header": "X-Api-Key: pm_live_…",
            "alt": "Authorization: Bearer <key>",
            "query": "?api_key=<key>  （不推荐，会进 access log）" },
  "endpoints": [ { "path": "/v1/window.php", "desc": "…", "params": "market, slug, series, n, raw" }, … ],
  "markets": [ "btc-updown-5m", "eth-updown-5m", … ],
  "market_codes": [ "btc", "eth", "sol", "xrp", "doge", "hype", "bnb" ],
  "response_envelope": { … },
  "error_codes": { … },
  "plans": [ { "code": "free", "qps": 1, "daily_quota": 500, "history_days": 1,
               "max_windows_per_call": 20, "max_samples_per_call": 0 }, … ],
  "health": { … },
  "server_ts": 1790409000
}
```

字段都在顶层：`service` / `note` / `auth` / `endpoints` / `markets` / `market_codes` /
`response_envelope` / `error_codes` / `plans` / `health` / `server_ts`。

---

## `GET /v1/settle.php` —— 结算核对

| 参数 | 默认 | 说明 |
|---|---|---|
| `market` | `all` | 只看某个市场（`eth` / `ETH-5m` / …）；`all` 或不传 = 全部市场 |
| `slug` | — | 单个窗口的结算记录 |
| `last` | 20 | 最近 N 个已结算窗口 |

响应**没有**单独的 `market` 字段 —— 市场信息在每行的 `slug` 前缀里
（如 `eth-updown-5m-1790401800` 一眼可辨）。
（会在顶层返回 `market` 的是 `/v1/history.php`，别混。）

```bash
curl -H "X-Api-Key: $KEY" \
  'https://api.wanminguo.top/quant/polymarket/v1/settle.php?last=5'
```

```json
{ "ok": true, "data": { "count": 5, "windows": [
  { "slug": "btc-updown-5m-1790389200", "ver": "1.2.15",
    "window_start": 1790389200, "outcome": "DOWN", "n_samples": 149,
    "last_remaining": 0.62, "open_offset": 1.38,
    "orders": 312, "used": 252, "skip": 0, "unfillable": 14,
    "fam": { "boundary": {"n":14,"win":6,...}, "...": {} },
    "rules": { "A":"DOWN","B":"DOWN","C":"DOWN","D":"DOWN","agree":true,
               "n_eval":4, "gap_bp":0.0, "move_bp":6.307, "anchor":"spot",
               "so":85594.295,"sc":85644.75,"to":85594.295,"tc":85634.44 } }
] } }
```

- `outcome` = 官方结算结果。`rules.so/sc/to/tc` = 开盘/收盘的现货与 TWAP，**你可以自己复算，不用信我**。
- 顶部 `rounds.jsonl` 有重复 slug（采集器重启会重新结算同一窗口），本接口**已按 slug 去重，保留最后一条**。

---

## `GET /v1/history.php` —— 历史窗口列表

| 参数 | 默认 | 说明 |
|---|---|---|
| `market` | `all` | 只看某个市场（`eth` / `ETH-5m` / …）；`all` 或不传 = 全部市场 |
| `from` / `to` | 不限 | unix 秒，或 `2026-09-22` 这种日期串 |
| `limit` | `min(200, 套餐 max_windows_per_call)` | 上限 = 套餐 `max_windows_per_call`（free 档默认 20） |
| `offset` | 0 | 分页 |
| `compact` | 0 | `1` = 只返回 slug/window_start/outcome/n_samples |

新的在前。`total` 是符合条件的总数。响应里带 `older_available` 便于翻页。

> **范围超限会整体 403**（`history_depth_exceeded`），不做静默截断——
> 少给数据不报错比报错更糟。

---

## `GET /v1/samples.php` —— 原始 2 秒样本

套餐 `max_samples_per_call > 0` 才能用（免费档是 0）。

| 参数 | 默认 | 说明 |
|---|---|---|
| `slug` | **必填** | |
| `limit` | `min(200, 套餐 max_samples_per_call)` | 上限 = 套餐 `max_samples_per_call` |
| `offset` | 0 | |
| `tail` | 0 | `1` = 取最后 N 条（做实时最常用） |
| `raw` | 0 | `1` = 返回原始 JSONL 字段；否则返回加工后的字段 |

---

## `GET /v1/stats.php` —— 预聚合统计（**免费档可用**）

返回 `stats/summary.json`（定长）+ 完整健康度。用来快速判断数据质量与规则表现。

---

## 套餐

| 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 零点重置。

> 字段语义（和 `01_billing.sql` 的列注释一致，别猜）：
> - `daily_quota = 0` ⇒ **不限量**（不是禁用）
> - `history_days = 0` ⇒ 只能访问**当前窗口**，历史端点 403
> - `max_samples_per_call = 0` ⇒ 禁用原始样本（`/v1/samples.php` 与 `/v1/window.php?series=1` 都拒）

---

## 客户端建议

1. **每次读 `meta.quota.remaining`**，低于 10% 就降频。
2. 轮询 `/v1/window.php` 的**合理频率是 1～2 秒**（数据本身就是 2 秒一条），更密没有意义。
3. ★ 但**先看你的日配额**：`free` 档是 500 次/天，
   **2 秒轮询约 17 分钟就会打光**（1 秒轮询还会撞 `qps = 1`）。
   免费档适合"看一眼当前窗口"，要持续轮询请上 `basic` 以上。
4. 要做实时推送（WebSocket）请单独联系——目前是轮询制。
5. **要的是实时信号而不是原始数据？** 那是同站的另一条线，见下一节
   （按成交次数计费，有现成客户端，不用自己写）。

---

## 信号订阅（另一条产品线，按成交次数计费）

> ★ 2026-09-28 新增。**与上面的数据 API 互不影响**：鉴权用同一个 `X-Api-Key`，
> 但计费单位是**成交次数**，且这三个端点**不消耗**数据套餐的 `daily_quota`。

| 端点 | 参数 | 说明 |
|---|---|---|
| `GET /v1/signals.php` | `since`, `market`, `wait`(0~15，默认 10), `limit`(≤50) | **长轮询**取新信号。当前只开放 `BTC-5m` |
| `POST /v1/receipt.php` | `signal_id`(必填), `filled_shares`(必填), `requested_shares`, `avg_price`, `status`, `order_id`, `raw` | 下单回执 —— **唯一扣次的地方** |
| `GET /v1/credits.php` | `ledger`(≤200) | 次数余额 / 流水 / 订阅市场 / 价目表 / 当前计次口径 |

### 信号长什么样

```json
{
  "signal_id": "btc-updown-5m-1790526000:93",
  "slug": "btc-updown-5m-1790526000", "market": "BTC-5m",
  "window_start": 1790526000, "ts": 1790526549.0,
  "side": "DOWN",
  "entry_price": 0.68,
  "limit_price": 0.73, "limit_capped": 0.73, "over_cap": false,
  "price_cap": 0.85, "exec_delta": 0.05,
  "base_shares": 10, "max_multiplier": 5,
  "condition_id": "0x…",
  "token_up": "877…", "token_down": "864…", "token_id": "864…",
  "ask_sz": 40.0, "src_fill_ok": true,
  "first_delivery": true
}
```

- `side` 是方向；`entry_price` 是信号那一刻的 ask，`limit_capped` 是按硬上限压过的挂单价；
- **`token_id` 是下单必需的**（`condition_id` 对应的 Up/Down token，服务端已解析并缓存）；
- `ask_sz` 是那一刻最优档的挂单量 —— 小于你要买的份数时**只可能部分成交**；
- `signal_id` 稳定不变，客户端**按它去重**（重复投递是正常的，长轮询游标含边界）。

### 计费与执行口径（写死，别猜）

- **1 次 = 10 份成交**（部分成交按 `ceil(成交量/10)` 折算）；**没成交不扣**；
  `status=paper` / `dry` 之类的**纸面回执不计次**。
- 同一 `(signal_id, key_id)` **只扣一次**：重发、并发、分笔成交都幂等。
- 一把 key **只绑一个市场**（当前只开放 `BTC-5m`），绑定后不可改。
- 余额 ≤ 0 时 `/v1/signals.php` 返回 **402 `no_credits`**（客户端会每 60 秒探一次，
  充值到账后自动继续，不用重启）。
- 价目：注册送 10 次、**9.9U / 300 次**、**19.9U / 800 次**。
  ★ `--multiplier 5` 一次下单是 50 份 = **5 次**，倍数越高次数烧得越快。

### 用现成客户端（推荐）

不用自己写：客户端在本站下载页 <https://api.wanminguo.top/download/>，
装好后 `python -m finhub --key … --paper` 就能跑，本地面板看信号与扣次。
国内网络下单需要出网隧道（客户端内置，按域名白名单转发、**不解密**你的流量；
私钥只在你本机，平台不代持、不代下单）。

> ⚠️ **不承诺收益。** 同一套规则在真实历史（93 单 / 1.36 天）上的回测：
> 胜率 78.5%，而**盈亏平衡胜率 = 平均成交价 74.8%** —— 95% 置信区间
> （69.1%~85.6%）**跨过**平衡点，这段历史**分不出正负**。
> 完整报告（含滑点与盘口深度的影响）：<https://api.wanminguo.top/download/backtest-report.txt>


5. 429 时（`rate_limited`，同一秒请求过多）：按 `Retry-After: 1` 退避 1 秒后重试。
   订阅制下无请求配额（额度按成交股数扣），不会再有 `daily_quota_exceeded`。

示例客户端见 `examples/pm_api_client.py`。
