18 KiB
PMS 三项对账差异排查与修复规格
2026-10-15,北京时间。对象是 155 模拟 PMS,ws 下发、propose_only 新建仓。188 真实仓不在本次范围。
事实与预判
只读 reconcile(apply_fix=False) 发现 002050.SZ 账本 800 / QMT 700,002979.SZ 账本 0 / QMT 1900,688100.SH 账本 2400 / QMT 1400。 八个委托的 trading_order.order_id 与 pms_qmt_order.instruction_id 相同,qmt_order_id 与 broker_order_id 也全部一致。 WebSocket 逐笔回报均 processed=1,汇总委托全部已进入 PMS_REPLAY_CURSOR.seen。
| 股票 | 当日重复回放的汇总数量 | 结果 |
|---|---|---|
| 002050.SZ | 买入 100 | 净多 100;批次 1361 以委托限价 31.560 重复入账,逐笔实际为 73 股 30.690 和 27 股 30.650 |
| 002979.SZ | 卖出 1300、500、100、100,合计 2000 | 重复核销后数量扣到零,与 QMT 1900 不符;数量下限截断使差额不是 2000 |
| 688100.SH | 买入 700、卖出 400、买入 700 | 重复净买入 1000;部分重复买入又被错误认作外部 BASE |
根因在 ledger_service.replay_fills:注释承诺 trading_order 只兜外部/人工成交,实际没有排除本 PMS 的 WebSocket 委托。 这不是新计划版本造成的;三项当日账本更新时间均早于 10:42 PMS 部署。成交价格也因汇总表回退委托价格而被污染。
修复原理及边界
在 trading_order 回放认领前,以委托编号及券商编号精确查询本实例 pms_qmt_order,不按股票、时间和数量猜测重复。 属于本实例出口表的委托只由 WebSocket 逐笔入账,汇总镜像只推进既有 seen 游标并留跳过原因。 即使逐笔迟到、部分到达、联调单或当前模式已经切回 shadow,也不得从汇总表补记同一委托。 身份查不到的外部/人工成交保留原回放。身份读取失败、两个标识对应不同委托、代码/方向/券商身份冲突时,不入账、不记 seen,留错供下一跳重试。 单表 SQL,不新增表、调度或交易参数。历史账本与历史指令进度不由此补丁自动回写。
验收
先用合成数据复现双来源买入、卖出、部分成交与乱序。新增第 39 批测试;执行配置隔离与禁止联网的全量测试。 部署前再核验实例、在途委托与容器指纹;匹配候选镜像断网全量通过后,按本聊天授权重建 PMS 四进程。 真机检查健康、通道、回放无重复动作与只读身份分类。已有错账单独标记,不能把修复代码通过说成历史账已恢复。
历史修复约束
日终 RECON 只调数量,并会把差额按行情价格当成新增或核销,不能修复被污染的原始成本、已实现盈亏和指令 exec_qty。 因此不为消除告警直接 force 对账。先保存当前批次、原逐笔成交、汇总回放游标及指令快照,评估能否完整重建。 历史已有 RECON 与过往双记相互叠加;冻结证据不足时不能伪造无误差历史。任何后续修复须另存可逆备份与调整依据。
12:05 实测结果
修复提交 e9afc7a 已于 12:02:54 部署 155 四个 PMS 服务。新增 12 例、开发机全量 43 批、候选镜像断网全量 43 批均通过。
真实八笔委托的只读归属核验全部正确。健康、数据库、QMT 通道正常,待入账和在途出口委托为零。
当前为午休,修复后自然新成交回放尚待事件验证;三项历史差异仍保留,不报告为清零。
原始证据文件 ~/pms_logs/reconciliation-evidence-20261015-before-fix-v2.json 的 SHA256 为 8e0badf4d771fbf8a3227250c6cc2c2bf3c8234dda2fc33a31f0ab00920f5501。
原始交易持仓表额外出现 562590.SH 与 688825.SH 属另一项事实源覆盖差异,继续按既有 ws_first 取实际持仓,不混同三项账本差异。
历史修复开发规格与预判(2026-10-15 12:30,北京时间)
用户已明确授权先备份,再逐笔恢复数量、成本、盈亏和指令执行进度。修复限定 155 模拟仓 002050.SZ、002979.SZ、688100.SH。188 不在范围内。首份完整备份为主机 pms_logs/historical-repair-backup-20261015-1225.json,SHA256 为 81cfc59600d7ca07de7fc4b83b7c5b153719d0ea248be21fd9d910bcfc32e636。包含全部持仓、批次、指令、方案、命令、出口单、手续费、策略、提议、日报、净值,三票动作账本,完整留存逐笔成交及下游成交表。写前还要重新备份并校验并发变化。
实现采用纯函数重建与单表 SQL 事务写回。逐笔成交必须能唯一对应出口子单,代码、方向、委托号、累计数量一致;同成交号冲突、漏笔、卖超、外部成交无法解释、来源读取失败均阻断。每笔真实买入以父指令、数量、价格、原入账时间唯一对应原批次。重复买入批次须能聚合对应同一条下游汇总单,RECON 调整须单列;不能直接按数量差删除。真实卖出按既有批次优先级逐笔重算核销量与盈亏。原批次 ID 保留,虚假批次归零并注明备份与修复批号,不删除原证据。
成本使用成交金额或等价的“剩余批次成本减已实现盈亏”,避免三位小数核销均价再乘数量带来的舍入误差。费用不摊进成本;缺失费用保持未知。父指令按真实逐笔量与金额重建 exec_qty/exec_avg_price,同时在 progress 中保存原值、子单证据和剩余量。原误判完成但未足量的指令改为 CANCELLED 并记录“历史修复暂停剩余执行”,前提是所有子单均已终态,绝不恢复自动续单;原在执行的指令保持生命周期。真实超目标成交如 002979 的 1,300 股网格指令成交 2,600 股要如实保留并标异常。方案、命令当前为空,无需伪造。
完整性边界按当前留存批次的起始日验证,之前成交净数量必须为零。002050 从 09-22、002979 从 09-21、688100 从 10-14 重建。688100 的 08-27 至 09-01 已闭合旧轮次缺父指令及原批次,单列成交金额,不混入当前账期。历史安全垫峰值、手续费、过往日报与全组合净值如缺原始材料不宣称恢复;保存原值和修复附注。
写回只在受影响子单无在途、无未处理逐笔时进行。短暂停止 pms-beat 与 pms-worker,保留通道接收能力;所有快照比较和更新位于同一数据库事务,每条 SQL 仍经过表名前缀与单表守卫。事务写入审计记录;备份指纹不符、数据已变或中途失败均不提交。重复执行同一批号返回已完成。回退必须先比较修复后字段,若已有新成交则拒绝覆盖。写完只读核对,再恢复调度。
预判:重建后数量分别为 700、1,900、1,400;全部相关子单逐笔量等于累计量;虚假买入批次和 RECON 调整不再贡献成本或盈亏;指令真实进度不会因修复自动生成新单;原始成交、回放游标、策略阈值和明日启用门槛不变。测试覆盖金额、部分成交、漏笔、匹配歧义、并发、幂等、失败回滚及旧指令不复活。生产读回后逐条判收。
10-15 12:40 历史账本修复完成与判收
本节取代前文“三项历史差异尚未修复”的当前状态。用户明确批准先备份、逐笔核对并恢复数量、成本、盈亏及指令执行进度。已在 155 模拟仓完成,188 未操作。
备份、部署和写回
功能提交 0871885,Python 指纹 01251315d8d1。本地配置隔离禁网和 155 候选镜像 --network none 均通过全部 44 批测试,新批次 40 有 21 例。未将用户原有未提交台账 017、讨论稿或探针纳入提交。
先于任何写账保存完整备份,再在午间暂停 beat/worker,拉取已提交版本并重建四容器。web/ws 于 12:37:27 启动,worker/beat 于 12:39:33 恢复。四容器同镜像 sha256:22422dc46784ff87c8ccfc6f9a2ae05ba8ee111eb18e3f1a369f9d913279ff0b,标签 pms:history-repair-0871885;上一镜像留为 pms:rollback-before-history-repair-20261015。
155 宿主 ~/pms_logs/ 的证据如下,JSON 文件权限为 600。
| 文件 | 内容或 SHA256 |
|---|---|
| historical-repair-backup-20261015-1225.json | 初始完整备份,81cfc59600d7ca07de7fc4b83b7c5b153719d0ea248be21fd9d910bcfc32e636 |
| history-repair-before-write-20261015.json | 12:37:55 写前完整备份,d1b3eeaede0ea418ec071fa593ee6a98177a55433ee0201b502ff80ff6226f02 |
| history-repair-preview-20261015.json | 逐笔映射、每笔卖出分配、逐日数量金额、全部目标列和原值 |
| history-repair-after-write-20261015.json | 写后完整备份,8463f0da2e1d152e7606c38607918b05555481f4ecbd15b482a326951ebd3606 |
| history-repair-apply-20261015.log / history-repair-verify-20261015.json | 实际提交及独立读回判收,全部检查通过 |
| history-repair-build-20261015.log / history-repair-tests-20261015.log / history-repair-deploy-20261015.log | 候选构建、断网测试及容器重建 |
修复批号 HIST_c036a684cab040387321396e。同一事务校验原行、完整逐笔和有效参数,修改 122 行,保存 122 条逐行原值审计和 1 条完成记录。再次执行同一备份返回 already_applied,审计数仍 122+1。原始成交、回放游标、方案、命令、手续费流水、旧日报净值及其他股票原行均未被改写。
实际恢复结果
| 股票 | 恢复数量 | T+1 可卖量 | 摊薄成本(元) | 账期已实现盈亏(元) | 可追溯起点 |
|---|---|---|---|---|---|
| 002050.SZ | 700 | 600 | 44.109 | -6762.70 | 2026-09-22 |
| 002979.SZ | 1900 | 1900 | 55.060 | -1684.25 | 2026-09-21 |
| 688100.SH | 1400 | 0 | 23.704 | 16.86 | 2026-10-14 |
成本沿用 PMS 的含已实现盈亏摊薄口径,盈亏为原批次核销规则下的毛额,不含费用。134 笔成交对应 61 个真实买入批次;33 个重复汇总或 RECON 批次归零留痕,原 ID 保留。逐笔成交与 39 个留存子单的累计数量一致,累计均价差异最大 0.004964 元,处于 QMT 两位小数舍入范围内;其中 2 个子单属于 688100 的已闭合旧轮次,未混入当前账期。
24 条父指令恢复实际成交量、成交均价,并在 progress 中增加逐笔证据、原进度、剩余量及超额量。七条原误判 CONFIRMED 的指令改为 CANCELLED,含义为“历史修复暂停剩余执行”;其子单都已终态,无需撤单,也没有向 QMT 发送撤单或新单。仍在执行的 002050 DCA 指令实际进度为 100/300,保持原生命周期。
002979 的 STR20261012093212_002979SZ_g 目标 1300 股,两个真实子单累计买入 2600 股,保留超额 1300 的事实。这是实际执行异常,无法通过删账撤销,后续须单独排查历史出手并发/重试链路;本次没有反向交易。688100 活跃网格第 3 档恢复为真实剩余 1000 股,批次成本 23506.51 元、均价 23.50651;原仅记 700 股/16471 元。已取消的旧策略未恢复。
12:40 真机健康 200、db_ok=True、calendar_degraded=False,QMT ONLINE、last_seq=acked_seq=1226583、last_error 为空,待入账 0。独立对账返回 diffs=[]、errors=[]、severity=OK。历史 streak=14 是尚未到日终重置的旧计数,不是仍有三项差异。既有 ws_first 源覆盖警告仍为 562590.SH、688825.SH 两票,单列保留,不混同已修复差异。
明确不能还原或未判收的部分
- 134 笔逐笔费用为空,无法还原真实扣费与净盈亏;费用流水原为空,未补零冒充真实费用。
- 缺完整盘中行情和当时账本,历史安全垫峰值无法准确恢复,保留原值并标明限制。
- 688100 的 08-27 买 800、09-01 卖 800 的旧轮次可核对成交毛差 330.47 元,但原父指令和批次已不在当前账本;无法恢复原始批次分配与完整决策链,不把该旧轮次利润写进本轮。
- 历史日报和全组合净值受旧错账影响,缺少重建全组合所需的全部原始状态,本次保持原档案。修复预演提供逐票逐日数量、成交净额和批次盈亏,不能将其冒充当时实际报表或已修复全历史绩效。
- 防双记代码的自然新成交判收仍需真实事件;不得制造成交来验收。真实超目标成交和两个事实源覆盖分歧仍单列待查。
后续和回退
automation-2 已更新修复结论,保持 ACTIVE 和北京时间 10-16 07:25。首个完整晨版通过后启用已创建且停止的 XXL 137,再开启 155 的 PMS_PLAN_VERSION_RECHECK。当前该参数仍 False,ws、propose_only、旧传导排序及档位和仓位参数保持;不增加额外五日模拟观察。版本一致、顺序、自然传播十二分钟、失败守成和无重复指令仍须实际事件判收。
手工入口 scripts/repair_ledger_history.py 开头列有 backup/preview/apply/rollback 跑法。回退须读取审计并比较修复后原行,后续有新交易或状态变化会拒绝覆盖;不得通过切旧镜像恢复旧账,更不得直接用旧备份覆盖新的成交。回退保留新旧审计与计划版本。
父指令进度逐条核验
| 父指令 | 原记成交量 | 真实成交量 | 真实均价 | 写后状态 | 剩余量 / 超额量 |
|---|---|---|---|---|---|
| INS_20260918_002050SZ_OPEN_001 | 622 | 500 | 36.160 | CONFIRMED | 0 / 0 |
| STR20260923100400_002050SZ_g | 3277 | 1900 | 35.936 | CONFIRMED | 0 / 0 |
| STR20260923130400_002050SZ_g | 616 | 500 | 36.031 | CONFIRMED | 0 / 0 |
| STR20260928093600_002050SZ_g | 3544 | 1900 | 35.995 | CONFIRMED | 0 / 0 |
| STR20260928094700_002050SZ_g | 2000 | 1900 | 35.985 | CONFIRMED | 0 / 0 |
| STR20260928094900_002050SZ_g | 3609 | 1900 | 35.844 | CONFIRMED | 0 / 0 |
| STR20260930095702_002050SZ_g | 2183 | 1900 | 36.028 | CONFIRMED | 0 / 0 |
| INS_20260930_002050SZ_OPEN_100000 | 1600 | 800 | 35.032 | CANCELLED | 800 / 0 |
| INS_20261009_002050SZ_EXIT_103100 | 2711 | 1400 | 32.213 | CANCELLED | 1300 / 0 |
| INS_20261012_002050SZ_EXIT_93100 | 1346 | 700 | 33.044 | CANCELLED | 600 / 0 |
| INS_20261015_002050SZ_DCA_001 | 200 | 100 | 30.679 | DISPATCHED | 200 / 0 |
| INS_20260921_002979SZ_OPEN_001 | 307 | 200 | 54.470 | CANCELLED | 100 / 0 |
| STR20260923094502_002979SZ_g | 1401 | 1300 | 54.192 | CONFIRMED | 0 / 0 |
| STR20260923095800_002979SZ_g | 291 | 200 | 54.249 | CONFIRMED | 0 / 0 |
| STR20260924093702_002979SZ_g | 2438 | 1300 | 54.210 | CONFIRMED | 0 / 0 |
| STR20260924094001_002979SZ_g | 1458 | 1300 | 54.351 | CONFIRMED | 0 / 0 |
| STR20260924094200_002979SZ_g | 1683 | 1300 | 54.260 | CONFIRMED | 0 / 0 |
| INS_20261012_002979SZ_EXIT_001 | 2600 | 1300 | 54.178 | CANCELLED | 1300 / 0 |
| STR20261012093212_002979SZ_g | 2600 | 2600 | 53.677 | CONFIRMED | 0 / 1300 |
| INS_20261015_002979SZ_EXIT_95100 | 3900 | 2000 | 52.681 | CANCELLED | 1900 / 0 |
| INS_20261014_688100SH_OPEN_001 | 800 | 400 | 24.240 | CANCELLED | 400 / 0 |
| STR20261015094200_688100SH_g | 1023 | 700 | 23.571 | CONFIRMED | 0 / 0 |
| STR20261015094500_688100SH_g | 524 | 400 | 23.620 | CONFIRMED | 0 / 0 |
| STR20261015100700_688100SH_g | 1236 | 700 | 23.482 | CONFIRMED | 0 / 0 |
10-15 12:50 最终运行版本与均价有效期
最终功能提交为 a56dac5,包含 0871885 的历史修复及指令均价有效期补齐。原增量入账接口只收到数量,不能重算全量均价;后续新成交时同步清空旧 exec_avg_price,固定修复时点的准确金额继续保存在 progress.historical_repair 与操作审计中,避免显示过期均价。未再次改写历史账本,也未恢复暂停指令。
batch40 增至 22 例,内存数据库验证数量增加、均价失效、固定证据保持;本地与候选镜像 --network none 全部 44 批再次通过。12:50:11 四容器重建并恢复,镜像 sha256:f3d4e698bc31be521f59b39588444495e143f9a52a3d5233b42a699f4030093d,标签 pms:mean-validity-a56dac5,Python 指纹 3de5fc3a261e。前一版保留为 pms:rollback-before-mean-validity-20261015。
12:50:47 再次独立逐字段核验通过,三票数量、成本与盈亏保持上一节读数,审计仍为 122+1,对账 diffs=[]、severity=OK。健康 200、db_ok=True、calendar_degraded=False,QMT ONLINE、last_seq=acked_seq=1226717、last_error 为空。PMS_PLAN_VERSION_RECHECK 仍 False。全部备份及不可还原边界继续有效,明日 07:25 接续安排保持。
最终日志位于 155 ~/pms_logs/mean-validity-build-20261015.log、mean-validity-tests-20261015.log、mean-validity-deploy-20261015.log、mean-validity-ledger-verify-20261015.json。四仓文档继续同步;只有文档更新无需再次重启容器。
2026-10-16 自然成交防双记补验
只读核对 10-15 12:50:47 修复后发生的 14 笔深科技卖出。成交发生在 13:31:36—13:41:45,合计 1,000 股、28,270.78 元,14 个成交号唯一且 processed=1。五条子单 D01、D02、D03、D04、D06 的实际成交数量分别为 400、300、100、100、100。汇总表中同五个委托的完成镜像已存在。
父指令 INS_20261015_000021SZ_EXIT_133100 的 exec_qty=1,000,状态 CONFIRMED。原持仓 1,000 股归零。批次 1302、1303 分别核销 400、600 股,其余历史批次逐字段保持。没有出现汇总镜像导致的第二次持仓扣减或执行进度累加。exec_avg_price 为 NULL,符合新增成交后不沿用旧均价的修复约定。
独立按原批次成本 36.182 元逐笔重算,分笔入账舍入到分后,两批已实现毛盈亏为 -3,237.32 和 -4,673.91 元,与账本一致。合计 -7,911.23 元。一次性按总金额减总成本为 -7,911.22 元,一分钱差异来自既有逐笔舍入。该票原批次是历史 RECON 成本,本次只判收修复后成交未双记,不宣称其全部早期历史成本已经重建。费用、旧批次与历史净值边界保持。
证据:155 ~/pms_logs/natural-fills-20261016-1035.json,读取的基线为 history-repair-after-write-20261015.json。核验脚本 verify_natural_fills_20261016.py 仅做 SELECT;没有调用回放、修复写入、采纳或交易。后续 10-16 仍无新成交,本项自然事件证据不再列为完全缺失。