# Trend 当前工作树重新审计 审计日期:2026-09-05。审计基线:`2f93fe2`,本轮读取时工作树干净。以当前磁盘文件为准,仅审计 Trend 及直接依赖;不修改策略代码,不连接交易接口。 ## 结论 当前发现 **P0:0 项,P1:4 项,P2:3 项**。本报告只保留当前存在的问题与待确认行为,不保留已修复问题清单。不建议仅凭语法检查通过直接运行实盘。 - **P0(紧急)**:无需特定边界条件即可造成全面故障或迫切严重损害,需要立即处理。本轮未发现符合此定义的问题。 - **P1(优先修复)**:在明确场景下影响资金约束、订单权限或交易决策,建议实盘前处理。 - **P2(常规修复)**:异常场景下影响局部执行或可靠性,需要安排修复。 工作树中 `strategy/trend/state.py` 已删除,`Runtime` 也已移除 `state` 字段;`docs/arch/state.py` 是归档,不是当前运行模块。因此此前围绕 `merged_order()` 的结论不作为当前策略结论。 ## P1-1:清仓后没有清理峰值,新持仓会继承旧回撤基准 - 位置:`positions.py:105`、`:182`;`boot.py:122` 起的快照处理;`libs/grid_take_profit.py:71`。 - 证据:峰值键只有账户和证券代码;当前 Trend 没有调用 `clear()`,也没有在持仓消失时清理对应键。 - 触发:同一进程内清仓后重新买入同一证券。 - 影响:新持仓达到最低收益门槛后,可能按上一笔持仓的高峰立即触发止盈,而不是建立自己的峰值;补仓改变成本时也未明确重置基准。 - 验证:真实网格组件以同一键记录 12% 峰值,再输入新持仓的 9% 盈利,返回 `RETREAT`;代码中没有清仓清理步骤隔离两次持仓。 - 建议:根据后续快照确认持仓消失后清理;撤单或部分成交继续保留峰值,成本变化另行明确重置规则。 ## P1-2:自动撤单未限定为本策略订单 - 位置:`order.py:60`、`:68`、`:78`;`boot.py:57`;`sdk/portfolio.py:9`。 - 证据:将组合接口的订单列表直接传入 `refresh()`,只检查状态与时间,没有检查本地订单号前缀、策略归属或排除名单。 - 触发:组合接口返回同账户其他策略或手工订单,且符合超时条件。 - 影响:启动及每轮刷新都可能撤销非 Trend 订单,包括排除证券的订单。 - 建议:账户级活动订单可用于防重,但主动撤单必须单独限定所有权。若业务确实授权管理整个账户,需明确记录该权限。 ## P1-3:大盘过滤实际始终放行 - 位置:`py-client/libs/market.py` 的 `market_allow_open()`;`boot.py:151`;`positions.py:67`。 - 证据:真实状态比较被注释,函数直接 `return True`。 - 影响:下跌或未知状态仍允许开仓及补仓,外层看似存在的风险限制不生效。该问题属于 Trend 直接依赖,不是 Trend 文件内的新修改。 - 建议:恢复真实状态判断,或以明确、可见的配置表示主动关闭过滤。 - 验证:将本地测试状态设为 `DOWN`,仍返回 `True`;未请求网络。 ## P1-4:买入没有统一资金预算,最小整手还可能突破单笔额度 - 位置:`boot.py:146`、`:176`;`open.py:51`;`positions.py:160`;`libs/calc.py:10`。 - 证据:开仓只依据本轮起始资金比例决定是否启动,不逐单扣减预算;补仓线程独立使用相同快照资金。`calc_buy_volume()` 用 `max(1, ...)` 强制至少买一手。 - 影响:多个开仓与补仓可能同时消耗同一份可用资金;可能跌破配置的现金安全线,或产生资金不足拒单。并非断言券商一定允许超额成交。 - 示例:价格 100 元、单笔额度 5,000 元,计算出 100 股,即 10,000 元。 - 建议:开仓和补仓共享本轮可预留预算;不足一手返回零;预留考虑手续费和行情变化,保留最小现金要求。 ## P2-1:撤单异常会中断整轮刷新和交易管理 - 位置:`order.py:78`、`:86`;`boot.py:122` 起的快照处理。 - 证据:撤单在遍历中直接调用,未逐单隔离;`busy_keys` 和 `data` 在全部遍历结束后才赋值。 - 影响:某笔撤单失败即退出 `refresh()`,快照不更新;常规轮次返回而跳过全部交易管理,初始化阶段则退出启动(客户端会关闭)。持续失败的订单可能持续阻断后续轮次。 - 建议:撤单逐项记录异常,同时发布完整活动订单快照;撤单失败的订单继续作为在途订单防重。 ## P2-2:信号前置处理没有逐项异常隔离 - 位置:`open.py:16` 至 `:55`。 - 证据:异常捕获仅包围 `do_open()`;配置、价格、数量计算、观察器调用不在逐候选保护边界中。 - 触发:一个候选包含异常数据,例如非有限价格导致数量计算异常,或配置字段类型不符合预期。 - 影响:该候选之后的所有开仓信号本轮不再处理;工作线程最外层只能记录整个任务失败。 - 建议:把一个候选的完整处理放入同一异常边界,记录证券与信号键后继续;保持必要校验即可。 ## P2-3:补仓请求结果不确定时未保留资金预算 - 位置:`positions.py:175`、`:75`;`order.py:119` 起的异常处理。 - 证据:`place()` 对请求超时等异常返回 `False`,保留证券方向缓存,但 `handle_loss()` 返回的 `reserved_cash` 默认为零。 - 触发:券商已受理补仓但响应丢失,本轮继续处理后续证券。 - 影响:方向缓存只能防同证券重复下单,不能阻止其他证券重复使用这笔可能已消耗的资金。该问题即使只启用持仓管理、不开新仓也存在,与 P1-4 的跨线程预算问题不同。 - 验证:模拟 `place()` 返回 `False`,补仓结果 `reserved_cash == 0.0`。 - 建议:区分明确未提交与结果不确定;不确定时保留本轮预计金额,或停止本轮后续买入并刷新资金。 ## 需要确认的行为(不直接判为错误) - `positions.py:102` 在盈利低于最低收益门槛时不再观察网格:若从高峰直接跌破该门槛,不会触发回撤卖出。需要确认门槛是仅用于首次激活,还是任何时候都禁止低于门槛卖出。 - `positions.py:122` 向下取整到百股,可用持仓不足一手将一直跳过。是否需要支持零股清仓取决于交易标的及产品要求,本次未核验外部市场规则。 - 当前 `LOSS_TIERS = [-50.0]`,且 `get_add_num()` 依据手数/当前市值判断,不再记录真实补仓次数。若是主动放弃状态机后的新设计,应更新策略说明;不能等同于历史成交次数。 - `boot.py:84` 使用 `>= 15:00:00`;与此前“严格大于 15:00”的表述存在边界差别。启动初始化发生在此检查之前,仍可能查询或撤单。 ## 已确认的保护与验证范围 - `busy()` 和 `place()` 都检查券商快照的 `busy_keys` 及本地 `SimpleCache`;`place()` 在互斥区内检查并写入,不再报告旧版“下单未使用防重”的问题。 - 开仓与持仓管理都检查排除名单;未知信号配置有独立日志后跳过。 - `place()` 捕获 API、HTTP 请求和解码类异常;持仓管理有逐证券异常边界。 - 启动生命周期有 `finally`,等待线程池后关闭客户端。 - 当前 7 个 Trend 源文件全部重新通过 AST 语法检查。本轮真实网格组件的最小测试验证同一键沿用旧峰值;重新执行资金数量计算、大盘状态判断,分别得到 10,000 元买入金额和 DOWN 状态仍放行。 - 资金预算、大盘过滤、撤单和信号异常边界本轮已重新读取;失败补仓零预算预留的实现与此前最小测试对应代码一致。 - 未执行真实下单、撤单、完整 SDK 联调或券商回报测试;语法通过不代表策略可运行。归档文件和历史审计测试统计不计入当前验证。 优先顺序:先处理撤单权限、市场过滤、资金预算及止盈峰值生命周期,再完善异常边界。