Files
big-qmt/docs/strategy-audit-2026-09-12.md
2026-09-15 20:02:05 +08:00

110 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# IPO、Trend、ZT 策略审计
> **2026-09-15 更新:本文的 ZT 部分已被重构取代。** ZT 改为正T/反T 轮次状态机,
> 持仓数量只以券商快照为准,本地不再重算持仓,因此"数量等式对账"这条路径整体
> 消失。下文 P1-07、P1-09、P2-01 的载体(`libs/state.py` 的 base_/added_ 分桶与
> `strategy/zt/positions.py` 的分支已随模块删除P2-07 所列的旧测试入口也已清理。
> 当前 ZT 设计见 [zt.md](zt.md)。
> **P1-08Trend 超时撤单覆盖手工单与其他策略单)仍未修复**:修复会改变 Trend 的
> 撤单范围ZT 侧已限定为 `zt-` 前缀Trend 侧待单独决定。
复审日期2026-09-13。基线`67b46ca0492cccdda8a41263b727a56df4a637c1` 加本次读取的工作区代码,包含手工修改及未提交文件。保留原文件名以便沿用引用。
范围三个策略及直接相关的委托簿、状态存储、SDK、服务端成交映射、网格和快照模块。本次只更新报告不修改策略、测试或生产数据不连接交易账户。
仅保留复审后仍需处理的问题。已修复及用户已标记忽略的条目、历史复现和过时统计已移除;编号沿用原报告。部分修复的问题仅描述尚未解决的策略范围。
## 结论
**待处理P0 0 项、P1 3 项、P2 2 项。** IPO 本次未确认新的待处理问题。
| 编号 | 级别 | 范围 | 问题 |
|---|---|---|---|
| P1-07 | P1 | ZT | 跨轮分批归档覆盖已有持仓数量及成本 |
| P1-08 | P1 | Trend | 超时撤单仍覆盖手工单及其他非 IPO 策略订单 |
| P1-09 | P1 | ZT | 首次初始化重复处理已包含在快照中的成交 |
| P2-01 | P2 | Trend | 止盈峰值跨仓位沿用 |
| P2-07 | P2 | 共用模块测试 | 旧构造调用及 State 契约变化导致回归验证失效 |
P0 表示已确认的紧急严重风险P1 表示主要交易控制或记账问题P2 表示特定条件下的可靠性和回归验证问题。本次未确认 P0。
## P1
### P1-07ZT 跨轮分批归档覆盖已有持仓数量及成本
**位置:** [state.py](../py-client/libs/state.py) 的 `sync_deals()``merge_deals()` 及第 264265 行;[ZT 同步适配](../py-client/strategy/zt/boot.py) 第 97115 行。
**证据:** 本轮按用户明确的上游保证,视 `order_sys_id` 为每笔成交的唯一值、本地委托编号非空,不再将重复编号假设作为确定缺陷。`merge_deals()` 仅汇总未归档成交,但买入归档将其数量和价格直接覆盖已有仓位。逐笔等待超过 30 秒不能保证同一委托的所有成交落在同一轮。
**本轮复现:** 使用临时数据库和真实 `_sync_state()`,两笔成交分别使用不同且唯一的 `order_sys_id`,共享同一个本地委托编号。第一轮买入 40 股并归档,第二轮加入 60 股成交,账户快照为 100 股。最终保存两笔原始成交,持仓却仅为 60 股,证券进入 `blocked_codes`。两笔均已归档,重复查询不会重新汇总为 100 股。
**影响与边界:** 原始成交保留,但派生持仓数量及成本不正确。当前适配会在数量不符时暂停对应证券交易,降低继续下单的风险,但不能修复已归档的错误状态。本项不依赖成交编号重复,也不要求改变上游身份契约。
**修改建议:** 将未归档成交作为持仓增量处理,按已有数量和金额计算加权成本,或建立等价且可重算的订单汇总机制。不要将 30 秒延迟当作订单全部成交的证明。当前授权不允许修改 `state.py`,本项仅记录问题,不实施修复。
**验收:** 同一本地委托的 40、60 股分别在不同轮次归档后合计 100 股;不同价格成交得到正确加权成本;重复同步及重启不重复记账;原始成交全部保留;数量一致后证券恢复交易。
### P1-08Trend 超时撤单仍覆盖手工单及其他策略订单
**位置:** [Trend boot.py](../py-client/strategy/trend/boot.py) 第 40、119 行;[OrderBook.refresh()](../py-client/libs/order.py) 第 61、7788 行。
**证据:** Trend 启动及每轮运行均传入完整账户委托,未指定 `cancel_prefix`,默认值为 `None`。所有满足超时及可撤状态的非 `IPO-*` 委托都会进入撤单路径,包括空备注手工单和 `zt-*` 订单。
**本轮复现:** 四笔超时、状态 50 的模拟订单分别使用 `TREN-BUY-*``zt-base-*``IPO-*` 和空备注。按 Trend 当前调用方式刷新Trend、ZT 和手工订单触发撤单IPO 未触发;四笔均保留缓存。
**影响与边界:** 运行 Trend 可能干扰人工或其他策略希望继续等待的委托。本项仅保留 Trend 范围;当前 IPO 排除有效,不再沿用旧报告的 IPO 误撤结论。Mock 只能证明撤单调用,不能证明柜台受理。
**修改建议:** 若 Trend 只管理自己的订单应明确归属后限制自动撤单同时保留全账户在途防重。Trend 开仓使用信号前缀、持仓管理使用 `TREN-*`,不能简单改为单一 `trend-` 前缀。如果全账户非 IPO 撤单是预期行为,应明确记录这一范围后再决定是否关闭本项。
**验收:** Trend 自有超时订单能撤,手工单和其他策略订单不被 Trend 自动撤销;全部活动委托继续参与防重。
### P1-09ZT 首次初始化重复处理已包含在快照中的成交
**位置:** [ZT 同步适配](../py-client/strategy/zt/boot.py) 第 97103 行;[state.py](../py-client/libs/state.py) 的 `sync_state()``archiving()`
**证据:** 初始化依次调用 `sync_state(positions)``sync_deals(deals)``archiving()`。持仓快照已经包含历史成交,但归档仍重新处理这些成交,没有初始化快照与后续增量的边界。该问题位于本轮策略适配流程,不应沿用旧 `sync_account()` 的初始化验收结论。
**本轮复现:** 空数据库初始化,账户净持仓 100 股,当日历史为买入 200、卖出 100两笔均超过 30 秒且编号唯一。真实 `_sync_state(..., initialize=True)` 先写入 100 股快照,随后买入归档将底仓覆盖为 200 股,卖出 100 股因不符合当前卖出分支而归档失败。最终记录 200 股,证券被暂停交易。
**影响与边界:** 首次启动即可能产生错误持仓和未归档记录。数量核对可以阻止对应证券继续交易,但不能自动恢复正确基准。该复现包含买卖混合历史,不能由单笔买入初始化测试覆盖。
**修改建议:** 明确已核对快照覆盖的成交集合与后续增量边界,使历史成交能够保存而不重复改变初始持仓;同时设计初始化失败后的重试行为。保持 `state.py` 不变的限制下,先明确策略侧能够使用的初始化接口,不能把三个独立调用视为原子同步的等价替代。
**验收:** 净持仓 100、历史买入 200/卖出 100 初始化后仍为 100已包含的历史成交不重复记账延迟超过 30 秒才进入存储的历史回报不重复影响基准;初始化失败、重复启动及重启恢复均保持一致。
## P2
### P2-01Trend 止盈峰值跨仓位沿用
**位置:** [Trend positions.py](../py-client/strategy/trend/positions.py) 第 120121、198199 行;[Trend boot.py](../py-client/strategy/trend/boot.py) 第 60、113119 行;[网格跟踪器](../py-client/libs/grid_take_profit.py) 第 4474 行。
**证据:** Trend 使用普通 `GridTrailingTracker`,键仅包含账户及证券。清仓、重新建仓和补仓成本变化时,没有清理旧峰值;每轮快照更新也未同步网格生命周期。
**本轮复现:** 使用真实 `manage_positions()`、真实 tracker 和模拟委托簿,依次输入成本 10 元、价格 12 元的持仓,价格 11.9 元的同一持仓,空持仓,再输入成本 10 元、价格 11 元的新持仓。各步新增卖出调用次数为 `0、1、0、1`。新仓位第一次观察仍沿用旧峰值触发卖出。
**影响:** 重新建仓或成本基准变化后,当前收益率可能被当成旧仓位的回撤,提前触发止盈。本项仅保留 Trend 范围。
**修改建议:** 根据已确认的清仓、重新建仓及成本变化同步止盈基准。不要仅在提交成功时清理,避免撤单或部分成交丢失有效峰值。
**验收:** 新仓位首次观察只建立峰值;同一仓位正常回撤仍触发卖出;未成交提交、撤单及同一成本下部分卖出不误清理。
### P2-07旧构造调用及 State 契约变化导致回归验证失效
**位置:** [test_deal_model.py](../py-client/tests/test_deal_model.py) 第 71 行;[OrderBook 构造函数](../py-client/libs/order.py) 第 3538 行;[ZT 状态测试](../py-client/tests/test_zt_state.py)、[ZT 审计回归测试](../py-client/tests/test_zt_audit_fixes.py)、[归档测试](../py-client/tests/test_state_archiving.py)。
**本轮复现:** `ActiveOrders('trend')` 仍在 `max(1, lock_timeout_sec)` 处抛出 `TypeError`。另有多项测试调用已删除的 `sync_account()`、使用当前成交表不接受的 23/24 方向,或直接写库后未刷新 `archiving()` 依赖的缓存。完整发现并执行 86 项测试47 项通过,记录 13 条断言失败、30 条错误;后两项含子测试记录,不能直接相加推算失败用例数。
**影响与边界:** 原报告“85/86 通过、ZT 相关全部通过”已失效。当前生产入口使用无参 OrderBookZT 已通过 `_sync_state()` 对接现有接口,不再因调用已删除的 `sync_account()` 而启动失败。失败记录既包含契约过时,也包含真实记账问题,不能全部视为生产缺陷或全部通过改断言消除。
**修改建议:** 按现有接口更新测试入口和数据准备,保留有业务意义的持仓一致性、原始成交保留、失败回滚及重启断言。新增对真实 ZT 适配入口的分批成交和初始化混合历史验证。不要为旧测试恢复已删除的构造参数或简单放宽业务断言。
**验收:** 回归测试适配当前契约默认刷新、ZT 范围过滤、IPO 排除和在途防重断言有效P1-07、P1-09 在修复前能失败、修复后通过;统计明确区分测试用例和子测试错误记录。
## 本轮验证及边界
- 完整执行 `unittest` 发现的 86 项测试47 项通过13 条断言失败、30 条错误,错误统计包含子测试,详情见 P2-07。IPO 专项另行执行 15 项,全部通过。
- 使用真实 ZT `_sync_state()`、State、OrderBook 和 Trend `manage_positions()`/tracker配合模拟客户端及临时数据库复现 P1-07、P1-08、P1-09、P2-01。
- 验证使用模拟客户端及临时 SQLite 数据库,未发送真实委托、撤单或采集请求,未修改生产数据库和锁文件。
- 按用户确认采用上游成交编号唯一、本地编号非空的前提;不再把重复编号假设列为确定缺陷。验证未证明真实柜台受理结果。
- 不新增旧 IPO 编号兼容要求,不恢复已忽略事项,不将已修复内容作为待办重复列出。