137 lines
6.8 KiB
Markdown
137 lines
6.8 KiB
Markdown
# ws 联调进度与交接(截至 2026-07-29 收盘)
|
||
|
||
> 给下一次接手的人(或下一个对话)用。三分钟读完就能接着干。
|
||
> 入口文档仍是 `README.md` / `POSITION_MGMT_DESIGN.md`(V0.4 定稿,勿改)/
|
||
> `QMT_WS_PROTOCOL.md`(V1.0 定稿)。本文只记**当前状态**和**踩过的坑**。
|
||
|
||
---
|
||
|
||
## 1. 一句话状态
|
||
|
||
**ws 通道全线打通并实测通过;账本被一次对账事故清空,待重建;`PMS_DISPATCH_MODE` 仍是
|
||
`shadow`,一次真实指令都没发过。**
|
||
|
||
---
|
||
|
||
## 2. 已验证(实测,非推演)
|
||
|
||
| 项 | 依据 |
|
||
|---|---|
|
||
| Ed25519 双向签名 | 与对端 `crypto.py` 逐字比对 + 双向验签实测 |
|
||
| 握手 / 心跳 / 重连退避 | 一天累计近百次重连,seq 水位每次无缝续接 |
|
||
| `ack_seq` 累积确认(§4.5) | 对端补上实现后,PMS 不再降级停发 |
|
||
| **§6.1 序号补发** | `ws_smoke rewind --by 60` 人为造缺口,对端从 `last_seq+1` 起严格按序补回 60 条,约 15ms/条,一条不漏 |
|
||
| 委托全链路 | `place_order → ack → order_update(SUBMITTED) → trade → persist_result → order_update(FILLED) → funds_update`,状态推进与字段全对 |
|
||
| 入账分流 | 只有 `trade` 进账本(`processed=0`),其余一律「已消化」 |
|
||
| 页面 ws 监控页签 | 真浏览器实测(含告警渲染、pong 过滤、轮询起停) |
|
||
|
||
协议三条安全底座(签名、补发、对账兜底)前两条已齐。
|
||
|
||
---
|
||
|
||
## 3. 当前状态与未决项
|
||
|
||
### 3.1 账本是空的
|
||
|
||
15:10 `daily_settle` 的 `reconcile()` 读到**下游持仓表空集**(对端模拟环境重启),
|
||
按铁律「以下游为准」把 22 个持仓、约 126 万市值全部核销。
|
||
|
||
- 机制已由代码确认(`reconcile` 无条件应用 fixes,且空结果集会跳过数量列校验)
|
||
- **未逐条核实 `pms_action_ledger` 里的 RECON 留痕**——想追认的话查
|
||
`SELECT * FROM pms_action_ledger WHERE action='RECON' ORDER BY decided_at DESC`
|
||
- `pms_lot` 是 `close_lot_qty` 核销的,行还在没删,理论上可反推,但**没有真相源可校验**
|
||
- 结论:接受损失,等对端模拟环境装上像样持仓后重新认领
|
||
|
||
### 3.2 下游只有垃圾
|
||
|
||
`trading_position` 现在只剩 `999999.SH × 2000`——我们自己测拒绝路径用的假代码。
|
||
现已被代码合法性校验过滤,不会再流进账本。
|
||
|
||
### 3.3 7 张风控 EXIT 已撤销
|
||
|
||
`origin_type=system` / `origin_id=intraday` / `from_signal=true`,理由「风控 SELL 置信度
|
||
95% ≥ 85%, 清仓」。持仓归零后指令悬空,已全部 cancel。**不是用户下的命令。**
|
||
|
||
### 3.4 组合基线(事故前,供重建时参考)
|
||
|
||
22 只 · 市值 1,258,357 / 成本 1,515,139(−16.9%)· 62.9% vs 60% 上限 ·
|
||
22 家 vs 15 家上限 · 16 只 ≤ −15%。
|
||
`688825.SH` 成本 8.66 / 现价 48.5 是**新股中签**,数据正确,非异常。
|
||
|
||
---
|
||
|
||
## 4. 今天新加的防线(都有测试锁死)
|
||
|
||
| 防线 | 位置 | 起因 |
|
||
|---|---|---|
|
||
| 对账爆炸半径 | `ledger_service._recon_blast_guard` | 下游读空清了 22 个持仓 |
|
||
| 下游代码合法性 | `command_spec.is_stock_code` | `999999.SH` 差点被补进账本 |
|
||
| 联调单不入账 | `ledger_service.consume_ws_trades` + `qmt_repo.SMOKE_PREFIX` | 对端按限价全成,每张测试单都在赌账本 |
|
||
| 卖出认领不到时告警 | `recon.map_trades_to_book` | 买入告警而卖出静默核销,不对称 |
|
||
| 补发期告警限流 | `runner._persist_upstream` | 一次补发刷 60 行 WARNING,真乱序会被埋掉 |
|
||
| 时延采样跳过补发件 | `runner._track_skew` | 补发原样重放旧 ts,误报 345 秒卡顿 |
|
||
|
||
---
|
||
|
||
## 5. 待对端(QMT 侧)
|
||
|
||
`QMT_TEST_ENV_REQUIREMENTS.md` 里的 R1–R4。**用户 2026-07-29 收盘时反馈「已实现模拟环境」,
|
||
下次开工第一件事是验证到底实现了哪几条。**
|
||
|
||
另有一项在 `QMT_SIDE_CONTROL_PATH.md` §5.1,未得到明确答复:
|
||
|
||
`_sender_loop` 的队列绑定(每轮重读 `self._send_queue`)与 socket 绑定(创建时绑死)
|
||
生命周期不一致,重连频繁时旧 sender 会从新队列取消息发往已关闭 socket,消息静默丢失。
|
||
S3 密集测试正是高发场景,值得再确认一次。
|
||
|
||
---
|
||
|
||
## 6. 下一步
|
||
|
||
1. **确认对端实现了 R1–R4 的哪几条**(`ws_smoke place` 发一张远离市价的单,看是否还秒成)
|
||
2. **重建账本**:对端装上像样持仓 → `reset_ledger.py` → 认领
|
||
3. **S3 五类覆盖**:全成 ✅ / 部分成交 / 主动撤单 / 到期过期 / 各类拒绝
|
||
4. **盘中**跑 `scan-proposals?dry_run=true` + `exec-tick?dry_run=true`,看真实候选清单
|
||
5. 两边都确认,再切 `PMS_DISPATCH_MODE=ws`
|
||
|
||
第 4 步必须在**交易时段**做,原因见下。
|
||
|
||
---
|
||
|
||
## 7. 已知的坑(别再踩)
|
||
|
||
**`docker compose restart` 不重载代码。** 源码是 `COPY . .` 打进镜像的,`restart` 只是
|
||
重启老容器。改完代码要生效:`./scripts/deploy.sh`,或在开发机上
|
||
`cp docker-compose.dev.yml docker-compose.override.yml` 挂载源码(此后 `restart` 才有效,
|
||
但改 `requirements.txt` 仍需 build)。**这个坑吃掉过一整轮排查。**
|
||
|
||
**`pms_ws_state.last_seq` 运行时手改无效。** 两条机制会撤销它:`_shutdown` 会把内存水位
|
||
刷回库;`_boot` 会用 `inbox_recover_watermark` 顺着 inbox 连续段走回去。要造缺口用
|
||
`ws_smoke rewind`(它会拒绝在 pms-ws 运行时执行,也会拒绝跨过已入账的成交)。
|
||
|
||
**对端模拟环境按限价全成、不校验代码。** 一张 99.99 的浦发卖单(实际价 10 元)会全成,
|
||
`999999.SH` 也会全成。**任何测试单都会产生成交回报**——所以联调单隔离(`SMOKE_` 前缀)
|
||
是必需的,不是可选的。
|
||
|
||
**收盘后的 dry run 看不出东西。** 取不到实时价时 `positions_view` 用摊薄成本兜底当现价,
|
||
安全垫齐刷刷归零,DCA/ADD/TRIM 三个评估器全不触发,结果永远是 `candidates: 0`。
|
||
判断"切了 ws 会发生什么"必须在交易时段跑。
|
||
|
||
**`pong` 带 seq(符合 §5)。** 5 秒一条、一天约 1.7 万行进 `pms_qmt_inbox`。
|
||
看 inbox 记得用 `ws_smoke inbox --type trade` 过滤,否则全是 pong。
|
||
|
||
**协议 `reject` 不带 `instruction_id` 时是「协议级拒绝」**——拒的是消息本身不是委托,
|
||
走完全不同的分支。对端曾因未实现 `ack_seq` 而回 `unknown type ack_seq`,形成
|
||
「我们 ack → 对端拒 → 拒绝带 seq → 我们又要 ack」的 2 秒自激循环,通道显示 ONLINE
|
||
但只在刷 reject。
|
||
|
||
---
|
||
|
||
## 8. 文档欠账
|
||
|
||
`QMT_WS_PROTOCOL.md`(V1.0 定稿)的版本历史表里,**V1.0 那行排在 V0.9.2 上面**,
|
||
而 `ack_seq` 恰恰是 V0.9.2 补进来的。对端照着表从上往下读到「V1.0 定稿」就停了,
|
||
`ack_seq` 因此被漏掉,直接导致了上面那个自激循环。
|
||
|
||
**顺序修正(纯顺序,不动条款)需要用户许可,问过三次未得答复,仍待处理。**
|