# weatherbot — Polymarket 每日温度市场交易 bot

Rust 单 crate。Phase 1（纸面交易验证）已完成并部署在 AWS 上 7×24 运行。
理论依据见 docs/STRATEGY.md（必读）；服务器/部署/备份见 docs/OPS.md。

**生产实例在服务器上**：本机的 weatherbot.db 只是测试残留，真实数据在
AWS ~/predit-bot/weatherbot.db（每 6h 备份到 GitHub data 分支）。
改代码后要 rsync + 重编译 + restart 才生效（流程见 OPS.md），别只在本机改完就以为上线了。

## 核心设计原则（违反任何一条 = 策略失效）

1. **预报必须对准结算站**：结算源是市场 description 里指定的 Wunderground/NOAA 站点页
   （站点码解析见 src/station.rs），不是城市坐标。stations.toml 是坐标/时区映射。
2. **原始 ensemble 有偏且过度自信**：粗网格对特定站点有系统偏差（实测东京偏暖 2-6°C），
   概率输出 1.00 是欠散布假象。Phase 2 校正前，纸面 edge 不可信为真实 edge。
3. **只交易 edge ∈ [阈值, edge_cap) + 次日盘 + 只买 NO**：当日盘由实测温度驱动（METAR），
   预报无优势；吃单成本下 edge 阈值必须覆盖点差；edge ≥0.30 是模型失真信号不是机会，
   YES 单在原始 ensemble 校正前全停（FINDINGS 发现 15）。例外：已持仓的市场进入当日后
   仍留在扫描里**只刷报价不开新仓**（quote-only，src/paper.rs），这是给 mark-to-market
   和 METAR 击穿退出（发现 16，死仓抢救残值）用的，别当 bug 修掉。
4. **每个新预报都落库**（forecasts 表）：这是 Phase 2 偏差校正的原料，删库 = 重头攒。

## 阶段门槛（不可跳）

- Phase 1（当前）：纸面循环跑 2-4 周，结算 ≥100 单且净收益率 ≥3% 才进 Phase 2
- Phase 2：按（站点 × lead time）偏差校正（EMOS/MOS 思路）。固定 D+1/D+2/D+3
  Previous Runs + IEM 实测链路已上线，每日刷新 `station_lead_bias`，影子概率
  `model_prob_corr` 自 corr_gate_v1（2026-07-28）起**参与门禁与 Kelly 定仓**（raw 仍是
  必要条件，corr 只收窄不放宽）。但按全候选流回放它是 ROI 中性的（-6.87% vs 基线
  -6.82%），当初上线的反事实依据已撤回（发现 25），所以别再把它当作有效性证据；
  下一步以 walk-forward MAE/Brier 验证后再研究风向、风速和站点状态等条件化维度
- Phase 3（仅当 edge 为正）：真实下单。maker 限价单管理；EIP-712 签名用 alloy 自实现或
  逐行审计过的 SDK（小众 crate 有供应链风险）；绝不把私钥交给未审计代码

## 常用命令

```bash
cargo run --release -- scan            # 列出活跃市场（滤僵尸后）
cargo run --release -- edge --city X   # 概率 vs 盘口报告（只读）
cargo run --release -- paper           # 常驻纸面循环（10 分钟/轮）
cargo run --release -- calibrate       # 刷新固定 lead 影子校正（服务器每日 timer 自动跑）
cargo run --release -- maker-observe --minutes 60 # 公共 L2 + 保守 maker 队列回放；0=常驻
cargo run --release -- settle          # 结算已 resolve 市场
cargo run --release -- report          # 纸面战绩
cargo run --release -- web --port 8080 # 只读状态看板
sqlite3 weatherbot.db                  # 策略主库；maker 原始 L2 在 weatherbot-maker.db
```

## 数据源速查

- 市场/盘口：Gamma API（分页必须 order=id，endDate 排序会漂移）+ CLOB book（深度）
- 预报：Open-Meteo ensemble 端点（ECMWF 51m/GEFS 31m/ICON 40m）+ forecast 端点
  （CMA GRAPES，确定性 1m，ensemble 端点无 CMA 数据只返回 null）
- 结算实时上游：NOAA aviationweather.gov METAR（ZSPD 半小时级），整数 °C
- 历史预报归档：historical-forecast-api.open-meteo.com（偏差回测用，含 CMA/ECMWF）

## 已知近似（改动时别"顺手修复"，它们是有意的）

- 纸面成交按 taker（吃 ask）模拟 → 测得 edge 是下界
- 小时级预报取日极值系统性低估极值 ~0.2-0.4°C → 归入 Phase 2 校正
- 香港天文台结算精度 0.1°C，按整数近似
- bucket 标签变体：or below/lower/above/higher 都存在（src/prob.rs）
- maker observer 只读公共 market channel，不含认证、签名、私钥或真实下单；回放成交
  不能替代 user channel `size_matched` 的实盘校准。成交判定自 2026-07-19 起为 v2
  量守恒（一笔打印最多按其 size 计入，部分成交存 `filled_shares`），v1/v2 边界在
  `bot_meta.maker_fill_v2_start`，跨边界 fill rate 不可比（发现 22）

## 反事实与回放（发现 25，2026-07-31）

**别在 `paper_orders` 上做反事实过滤。** 哪些候选变成订单由 `max_open_exposure_usd`
腾出空间决定，样本被"要测量的那个东西"选择过——corr_gate_v1 就是这样被误判为有效的。
用 `tools/replay_rules.py`，在 `snapshots` 的完整候选流上回放（每个 market×side 的首个
signal），CI 按**事件** bootstrap（同站点日的 bucket 互斥且共用一次预报误差，按订单
重采样会把有效样本量高估约 3 倍）。

按此口径 Hold 规则基线 -6.82%，95% CI [-8.88, -4.79]，每个价格档实际胜率都低于盘口
隐含保本胜率——**不要再靠加过滤器去调 ROI**，没有哪个区间是正的。

**永远不要按当前策略重跑并写回历史订单**，也不要追溯改写已开仓位的金额。看板上的
前瞻 ROI 之所以有意义，正因为它是一条没被编辑过的记录。
