# 计划:持仓内存快照(服务端 10s 定时同步,请求不再穿透 QMT)(阶段航点) > 编号:PLAN-013 | 粒度:阶段航点 | 创建:2026-09-02 | 状态:**已实施(待验收)** > 派生自终极目标:目标-008(真实交易系统接入)、目标-001(DSH 插件形态) > 依据需求:**R-014(已定稿,2026-09-02,老师逐项拍板 4 问)** —— 符合入范围门槛 > 设计约束:技术约束-010(服务端中转 + 缓存模式沿用)、技术约束-012(存储设计)、技术约束-016(分域目录);本次新增 技术约束-017 > 补充说明:本计划为「先实施后补档」——代码随架构梳理讨论当场落地(老师指令),文档链(本计划 + 迭代三件套 + 约束)事后补全,事实以迭代复盘为准 ## 目标 服务端建立全量持仓**内存快照**(PositionSync,10s 定时与 QMT 同步),策略持仓 / 全部持仓 / 未分配三个接口改读快照为准,**消除「QMT 抖动 → 持仓页面白屏」**并去掉每次进 tab 的穿透 HTTP 调用;前端零改动。 ## 范围 **做**: 1. PositionSync(src/position/PositionSync.js):启动预热 + 10s 定时全量拉 QMT → 校验 → 整体替换内存快照;同步失败**保留上次快照**(不清空不报错); 2. 空快照双重确认:/health 可用 + getAsset 账户身份可识别,二者兼备才接受为「真清仓」,否则视为异常保留旧快照; 3. 读穿透兜底:PositionManager.getAllPositions() 快照为空 → 当场拉一次 QMT 并回填;未注入 positionSync 时保持旧穿透行为(兼容); 4. 幽灵持仓自动清仓:QMT 快照连续 3 轮(约 30s)消失的 code,本地全部策略当前持仓 closeHolding 转历史;防抖护栏 = 账户身份守卫(accountId 未知当轮跳过;账户切换当轮重置计数并跳过);部分减持不触发; 5. index.js 装配(start/dispose/注入 manager 与 api runtime);回归脚本(纯内存 mock,技术约束-011 不受影响)。 **不做**: - 不落库(无 positions_cache 表;不建表、不改 SQLite schema)——R-014 讨论 #1 明确; - 不复用 strategy_holdings(账本与对账单分离,holding_id 锚点语义不掺快照); - 前端改动(零改动;同步时间显示本轮不做); - 部分减持的自动修正(负数未分配仍由 UI 暴露,后续可另立需求); - N+1 委托查询、api/trades.js 绕门面等既有观察点(另行登记)。 ## 涉及文件 ``` src/ ├── position/PositionSync.js # 新增:持仓内存快照同步服务 ├── position/PositionManager.js # getAllPositions 改读快照 + 读穿透兜底(兼容旧构造) └── index.js # 装配 PositionSync + 注入 scripts/ └── test-position-sync.mjs # 回归脚本(纯内存 mock 34 项) ``` ## 实现步骤 1. PositionSync 模块(快照 + 定时 + 校验 + 幽灵清仓); 2. PositionManager 读路径切换 + 读穿透 + 兼容分支; 3. index.js 装配; 4. 回归脚本(34 项)+ typecheck + build + r013 回归; 5. 文档链补全(本计划 + 迭代三件套 + 技术约束-017 + 数据存储设计 §11)。 ## 验收要点 - 见 `docs/04-迭代记录/12-持仓内存快照/验收标准.md`。