Metadata-Version: 2.1
Name: finhub-signal-client
Version: 0.1.0
Summary: FinHub 信号客户端：订阅 BTC 信号、本地下单、回执回传（纯标准库，实盘可选 py-clob-client）
Home-page: https://api.wanminguo.top/
License: Proprietary
Requires-Python: >=3.8
Description-Content-Type: text/markdown

# FinHub 信号客户端（finhub-signal-client）

**一句话**：这是一个跑在**你自己电脑**上的小程序。它连 FinHub 平台，接收 BTC-5m 的实时信号，
按你设定的倍数下单，并把成交回执回传给平台（平台按回执扣次数）。
你的私钥、你的资金、你的订单**都在你本机**，平台看不到也拿不到。

> ⚠️ **先读风险**：这是**工具**，不是收益承诺。历史上同一套规则的表现见
> 「[策略表现与风险](#策略表现与风险)」一节，请先看完再决定投入多少钱。

---

## 0. 两种拿法

| 方式 | 怎么做 | 适合 |
|---|---|---|
| **平台下载页**（推荐给客户） | 下载 `finhub-signal-client-<版本>.tar.gz`，解压即用；页面同时给 `.whl` 与 `sha256.txt`：<https://api.wanminguo.top/download/> | 只想要能跑的程序 |
| **本仓库源码** | 本目录就是源码：`cd signal-client && python -m finhub --help` | 想看代码 / 自己打包 |

> 本客户端**不发 PyPI**（国内访问不了）。仓库根目录的 `pm_api_client.py` 是**另一条线**
> （数据 API 客户端），两者互不依赖。

---

## 1. 三分钟上手

**Windows**（PowerShell）：

```powershell
# 1) 进入目录
#    · 用下载页的 tar.gz：目录名带版本号
cd finhub-signal-client-0.1.0
#    · 或者用本仓库的源码：
# cd finhub-api-client/signal-client

# 2) 存下 key 并以**纸面模式**跑起来（不会真下单，也不扣次数）
python -m finhub --key pm_live_你的KEY --save-key --paper

# 3) 浏览器打开本地面板（第 2 步的进程会一直跑着）
#    http://127.0.0.1:8787
```

**macOS / Linux**：命令一样，把 `python` 换成 `python3`。

> 只保存 key、先不跑：把第 2 步换成 `--save-key-only`。
> 跑的是包不是脚本 —— **没有顶层 `finhub.py`**，请用 `python -m finhub`（在解压目录里执行）。

要求：**Python 3.8+**（标准库即可，纸面模式零依赖）。
实盘额外需要官方 SDK：`pip install py-clob-client`。

### 它需要什么

| 东西 | 从哪来 | 说明 |
|---|---|---|
| API Key（`pm_live_…`） | 平台「用户中心 → 我的 API Key」 | 免费注册即送 **10 次**；一把 key 绑定一个市场 |
| 次数 | 平台充值（9.9U=300 次 / 19.9U=800 次） | **1 次 = 一次成功下单回执**（没成交不扣） |
| Polymarket 私钥 | 你自己在 Polymarket 的账户 | **只有实盘需要**；只存在你本机 |
| USDC（Polygon 链） | 你自己充值到 Polymarket | 实盘下单用的钱，平台完全不接触 |

---

## 2. 两种运行模式

```bash
# 纸面（推荐先用它跑一天）：本机模拟成交，回执不计费，用来验证网络和链路
python -m finhub --key pm_live_xxx --paper

# 实盘：真的下单（需要私钥；先小额）
python -m finhub --key pm_live_xxx --live --private-key 0x你的私钥 --funder 0x你的Polymarket地址

# 只看次数和订阅市场，不下单
python -m finhub --key pm_live_xxx --status

# 只打印不下单（生产演练）
python -m finhub --key pm_live_xxx --dry
```

跑起来之后浏览器打开 **http://127.0.0.1:8787** —— 本地面板，每 3 秒刷新：
连接状态、剩余次数、收到的信号、每笔的下单/成交/扣次，全都在上面。数据不出你的机器。

实盘时注意：**隧道只给 Polymarket SDK 用**，平台 API 永远直连（客户端内部做了隔离）——
所以 `--live` 下平台请求不会因为隧道出错而失败。

---

## 3. 国内网络：出网隧道

Polymarket 在国内直连不了。客户端会在本机起一个**只监听 127.0.0.1 的代理**
（默认 `127.0.0.1:8788`），实盘下单的流量经由它转发到 FinHub 的出网隧道：

```
你的电脑 ──外层 TLS(SNI=api.wanminguo.top)──▶ FinHub 隧道 ──▶ Polymarket
              └─ 通道里发 CONNECT clob.polymarket.com:443
              └─ 之后是你与 Polymarket 的**内层 TLS**（端到端）
```

* **为什么要套一层到平台域名的 TLS**（2026-09-28 实测）：如果直接把目标的
  ClientHello 发出去，里面的 `SNI=clob.polymarket.com` 是**明文**的，国内链路上的
  DPI 看见就注入 RST（对照实验：同一个端口，`SNI=example.com` 能握手、
  `SNI=clob.polymarket.com` 0.0 秒被掐）。现在内层 ClientHello 藏在外层加密通道里，
  DPI 只能看到平台自己的域名。
* 隧道按**域名白名单**只放行 Polymarket / Polygon RPC 相关域名，其它一律拒绝，且**只允许 443**；
* 客户端的**私钥与下单签名是与 Polymarket 之间内层 TLS 端到端加密的**，平台看不到内容
  （外层 TLS 只用于隐藏 SNI；平台能看到的是"连了哪个域名、多少字节"，和普通中转一样）；
* 客户端这一侧也有一道同样的白名单，两道都过了才转发。

不需要隧道时（例如你在能直连的地区）加 `--no-tunnel` 即可。

**域名解析兜底**：隧道域名解析失败时客户端会自动回退到备用 IP（日志里会写明
"★ 回退到备用 IP …"），外层 TLS 仍然用域名校验证书，所以**回退不影响安全性**。

看到日志里是**连接超时（timed out）**，那是服务端的 8443 端口没对外开放
（云安全组/防火墙），请联系平台方放行 **TCP 8443**；那不是客户端的问题。

> **服务端隧道的源码也放在本仓库**：`signal-client/tools/tls_tunnel.py`（约 300 行）。
> 上面说的"域名白名单、只允许 443、单 IP 并发上限、内层端到端加密"都可以直接在
> 代码里核对；你想在自己服务器上搭一个同样的隧道也用它（需要一张你自己的域名证书）。

---

## 4. 命令行参数

```
`--key KEY             API Key（必填；--save-key 之后可省略）
--base URL            平台地址，默认 https://api.wanminguo.top/quant/polymarket
--market M            市场，默认 BTC-5m（当前只开放 BTC）
--paper / --live / --dry / --status   运行模式
--base-shares N       基础份数，默认 10
--multiplier N        倍数 ×1~×5，默认 1（实际份数 = 基础份数 × 倍数）--wait N              长轮询秒数，默认 10（服务端上限 15；越小越灵敏，越大越省资源）
--since TS            从某个时间戳（epoch 秒）开始补信号，默认接着上次的游标
--port N              本地面板端口，默认 8787
--no-dashboard        不起本地面板（服务器上跑时用）
--skip-thin           最优档挂单量小于我方份数时**跳过**该信号（默认不跳过，只提示）
--tunnel HOST:PORT    出网隧道地址，默认 api.wanminguo.top:8443（外层 TLS 的证书名）
--no-tunnel           不起本地隧道代理（你在能直连 Polymarket 的地区时用）
--proxy-port N        本地代理端口，默认 8788
--private-key 0x…     实盘私钥（也可用环境变量 FINHUB_PRIVATE_KEY）
--funder 0x…          实盘的 Polymarket 代理钱包地址（用邮箱/谷歌登录的账户需要填）
--save-key            把 key 存到本地配置（**不退出**，继续按选定模式跑）
--save-key-only       只保存 key 然后退出（第一次配置时用）
```

配置文件与日志：`~/.finhub/`（`config.json` / `cursor.json` / `unsent.jsonl` / `client.log`）。
若该目录不可写，客户端会**在 stderr 明确提示**并退到系统临时目录。
POSIX 上 `config.json` 会被设成 `0600`；**Windows 上 chmod 无效**，若你机器是多用户共用，
建议手动收紧该文件的 ACL（私钥不会落盘，只有 API key 在配置里）。

---

## 5. 计次规则（怎么扣、什么时候扣）

* **只有成功下单回执才扣**：没成交（挂单没吃到）**不扣**；部分成交按实际成交量折算。
* 纸面（`--paper`）与演练（`--dry`）**永不扣次** —— 服务端对这两类回执按 0 成交处理。
* 一次信号里客户端下**一单**；同一信号的重复回执（网络重试、分笔成交）**只扣一次**。
* 余额为 0 时平台返回 `402`，客户端**不会退出**：它每 60 秒探一次，充值到账后自动继续。

> ✅ **当前平台口径：1 次 = 一个成功下单回执**（**不看份数** —— `--multiplier 5` 也只扣 1 次）。
> 也就是说：**套餐寿命与倍数无关**，9.9U/300 次 ≈ 4.4 天（实测信号频率 68 条/天）。
> 平台另留了一个「按份数」口径（1 次 = 10 份成交，×5 一单扣 5 次），**当前未启用**；
> 用 `--status` 或 `curl /v1/credits` 看 `charge_mode` 与 `credit_rule` 字段即可确认你买到的是哪种。

---

## 6. 策略表现与风险

**这一节请务必看完。** 下面数字来自对真实历史（采集器落盘的信号 + 结算结果）的
离线回测，报告原文：<https://api.wanminguo.top/download/backtest-report.txt>；
回测脚本放在**本仓库** `signal-client/tools/client_bt.py`（它读的是平台侧数据目录，
客户机器上跑不了 —— 放出来是为了让**口径可审计**）。

口径：只做 BTC-5m、按信号价成交（并测试 +0.01/+0.02/+0.05 滑点）、硬上限 0.85、
固定 10 份 × 倍数、**持有到结算**（不设止损止盈）。

| 场景（×1 / 滑点 +0.05） | 数值 |
|---|---|
| 成交单数 | 122 |
| 胜率 | 77.0%（95% 置信区间 68.8% ~ 83.6%） |
| 平均信号价 → 平均**成交价** | 0.698 → **0.748** |
| 盈亏平衡胜率（= 平均成交价） | **74.8%** |
| 每单净额 | +0.227 U（含次数费） |
| 滑点从 0 加到 0.05 的代价 | 每单 −0.500 U |
| 次数费占盈亏 | 14.5% |

**盘口深度会吃掉倍数**（同一份回测，但按 `ask_sz` 限制成交量 —— 这才接近 FAK 实盘）：

| 倍数 | 无深度约束（每单） | 按 `ask_sz` 限流（每单） | 受限的信号数 |
|---|---|---|---|
| ×1 | +0.227 U | +0.229 U | 8 / 122 |
| ×3 | +0.681 U | +0.591 U | 28 / 122 |
| ×5 | +1.135 U | **+0.454 U** | **47 / 122** |

**所以"倍数越大赚越多"是错的**：×5 时近 40% 的信号那一档挂单量不够，实际只成交一部分，
每单收益反而低于 ×3。**建议先用 ×1 实测一段时间。**

**结论**：置信区间**跨过了**盈亏平衡点（上界 83.6% 距平衡点 74.8% 还有 8.8 个百分点）——
这段历史（122 单 / 1.76 天）**还分不出正负**，既不能证明能赚，也不能证明会亏。
**平台不承诺任何收益**。

必须知道的事：

1. **5 分钟盘的价格里已经含了做市商的边际**，越多人按同一信号交易，成交价越差（你的滑点越大）。
2. **次数费本身就是成本**：在 ×1/+0.05 场景下，费用约为策略盈亏的 8.9%。
3. **滑点吃掉一切**：+0.05 的滑点把每单收益从 0.87U 压到 0.37U —— 所以客户端的
   执行方式是"**限价 FAK + 硬上限 0.85**"，宁可挂不上，也不追高。
4. **不要用你亏不起的钱**。倍数只线性放大**盈亏方向**，不改变方向；但受盘口深度限制，
   **收益并不会线性放大**（×5 每单 0.454U 反而低于 ×3 的 0.591U）。
5. 起步资金建议：×1 约 **100 U**（10 份/单 ≈ 7 U，留出同时多单的余量）；
   想用 ×5 建议 ≥ **300 U**。低于这个数会因为单笔保证金不足而频繁下不了单。
6. **平台侧只开放 BTC-5m**，信号是稀疏事件（实测 69 条/天，但成串出现，
   有时 20~40 分钟没有一条）—— 客户端空转是正常的，不是坏了。

---

## 7. 已知限制（诚实列表）

* **必须保持客户端运行**：平台不代持你的私钥，所以不存在"关掉电脑也让服务器帮你下单"。
* **所有用户共用一个出网 IP**：平台侧风控可能因此收紧；极端情况下隧道可能被封，届时需要换出口。
* **本地面板只监听 127.0.0.1**：别人访问不到，但也意味着你只能在本机看。
* **信号有时效**：超过 10 分钟的信号不会被推送（5 分钟盘早就结算了），客户端重启时会自动跳过。
* **token id 由平台解析**：极少数情况下市场刚创建、CLOB 还没返回 token 时，信号会带 `token_id=null`，
  客户端会**跳过该信号**（不扣次），日志里会写明原因。
* **盘口可能不够厚**：信号里带 `ask_sz`（那一刻最优档的挂单量）。如果它小于你要买的份数，
  日志会提示"可能只成交一部分" —— 部分成交**只按实际成交量扣次**。想直接跳过这类信号，
  加 `--skip-thin`。
* **回执不会丢**：回执上报遇到限速/网络抖动会自动退避重试；仍失败且**含真实成交**的，会写进
  `~/.finhub/unsent.jsonl`，下次连上自动补交（平台按信号幂等，不会重复扣次）。
* **没有"本金上限"开关**：单笔金额 = `--base-shares × --multiplier × 限价`，
  控制投入靠这两个参数和你自己放进 Polymarket 的钱。

---

## 8. 常见问题

**Q：`network` 错误 / 连不上平台？**
先确认能打开 `https://api.wanminguo.top/`；公司网络可能拦 HTTPS。平台地址可用 `--base` 覆盖。

**Q：`402 no_credits`？**
次数用完了。去平台用户中心充值 —— 客户端**不会退出**，每 60 秒探一次，到账后自动继续。
也可以 `python -m finhub --key … --status` 看余额与流水。

**Q：`403 key_revoked` / `403 market_locked`？**
key 被吊销或市场绑定冲突（一把 key 只能绑一个市场，绑定后不可修改）。
客户端会明确打印原因并按最长 5 分钟一次退避重试 —— 这类错误要你去平台处理。

**Q：实盘报 `token_id` 相关错误？**
用 `--status` 看平台返回；若信号里 `token_id` 为 null，说明平台当时没能从 CLOB 解析出该市场的
token（客户端已自动跳过，不会扣次）。稍等下一批信号即可。

**Q：日志里说"最优档只有 N 份"？**
那条信号那一刻的买一挂单量比你要买的份数少，可能只成交一部分 —— 部分成交只按实际成交量扣次。
不想交易这类信号就加 `--skip-thin`。

**Q：怎么升级？**
下载新的安装包覆盖旧文件即可；`~/.finhub/` 里的配置和游标会保留（不会重复下单）。
先核对一下 `sha256`（下载页有 `sha256.txt`）。

**Q：怎么彻底卸载？**
删掉程序目录 + `~/.finhub/` 即可。

---

## 9. 开发者：从源码跑

```bash
cd client
python -m finhub --help          # 包形态运行（**没有顶层 finhub.py**）
python -m finhub --key … --paper
```

打包发布（**不含 PyPI**，产物直接放平台下载页）。仓库自带 `build_artifacts.py`，
它用标准库手写 wheel/sdist，不依赖 `build`/`setuptools`/网络：

```bash
cd client
python build_artifacts.py        # 产出 dist/*.whl、dist/*.tar.gz、dist/sha256.txt
```

把 `dist/` 里的文件连同 `sha256.txt` 一起放到平台 webroot 的 `download/` 目录即可。

