tradingSystem/WS_INTEGRATION_STATUS.md

6.8 KiB
Raw Blame History

ws 联调进度与交接(截至 2026-07-29 收盘)

给下一次接手的人(或下一个对话)用。三分钟读完就能接着干。 入口文档仍是 README.md / POSITION_MGMT_DESIGN.mdV0.4 定稿,勿改)/ QMT_WS_PROTOCOL.mdV1.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_settlereconcile() 读到下游持仓表空集(对端模拟环境重启), 按铁律「以下游为准」把 22 个持仓、约 126 万市值全部核销。

  • 机制已由代码确认(reconcile 无条件应用 fixes且空结果集会跳过数量列校验
  • 未逐条核实 pms_action_ledger 里的 RECON 留痕——想追认的话查 SELECT * FROM pms_action_ledger WHERE action='RECON' ORDER BY decided_at DESC
  • pms_lotclose_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,13916.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 里的 R1R4。用户 2026-07-29 收盘时反馈「已实现模拟环境」, 下次开工第一件事是验证到底实现了哪几条。

另有一项在 QMT_SIDE_CONTROL_PATH.md §5.1,未得到明确答复:

_sender_loop 的队列绑定(每轮重读 self._send_queue)与 socket 绑定(创建时绑死) 生命周期不一致,重连频繁时旧 sender 会从新队列取消息发往已关闭 socket消息静默丢失。 S3 密集测试正是高发场景,值得再确认一次。


6. 下一步

  1. 确认对端实现了 R1R4 的哪几条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.mdV1.0 定稿)的版本历史表里,V1.0 那行排在 V0.9.2 上面ack_seq 恰恰是 V0.9.2 补进来的。对端照着表从上往下读到「V1.0 定稿」就停了, ack_seq 因此被漏掉,直接导致了上面那个自激循环。

顺序修正(纯顺序,不动条款)需要用户许可,问过三次未得答复,仍待处理。