# R-009 交易记录本地存储(SQLite)+ 策略关联 · 已完成 > 归档日期:2026-09-01 | 需求状态:**已完成** > 原索引:docs/05-需求池/需求池索引.md(主索引保留 R-009 条目,指向本归档) > 实现迭代:07-交易记录本地存储SQLite与策略关联(验收通过,迭代复盘见 docs/04-迭代记录/07-交易记录本地存储SQLite与策略关联/迭代复盘.md) > 关联计划:PLAN-008(docs/02-计划/计划-交易记录本地存储SQLite与策略关联.md) > 讨论记录:Q1-Q8 定稿(2026-09-01);Q3 二次修正定稿(2026-09-01):归属由用户手动设置(trade_orders 冗余 strategy_id + holding_id,UPSERT 不覆盖归属列,Q1-Q4 确认) --- > 状态:**已定稿**(2026-09-01,老师确认)| 登记日期:2026-09-01 > 来源:老师指令(2026-09-01) > 优先级:P1 > 关联:R-007(交易记录接入)、R-008(SQLite 存储)、docs/03-设计约束/数据存储设计.md §9(holding_id 关联锚点预留)、迭代 06 复盘遗留项 2 ## 需求描述 在 SQLite 中新增**交易记录表**(委托 + 成交),将 QMT Bridge 当日交易数据**本地持久化**(跨日积累,形成本地历史库),并建立与插件**策略体系**的关联,支持**按策略过滤 / 复盘**交易。**归属由用户在交易记录 tab 手动设置(冗余存 strategy_id + holding_id),UPSERT 不覆盖归属列。** - 承接 R-007 的 Q5(本地持久化,本期不做)与 Q10(按策略过滤,后续迭代); - 落实迭代 06 复盘遗留项 2:「strategy_holdings.holding_id 已就绪,可作为 trades 表关联锚点(R-007 实现时建 trades 表)」; - 顺带解决 R-007 历史范围「接口开发中」占位问题(本地积累后历史可查)。 ## 现状(代码审查 2026-09-01) | 项 | 现状 | |---|---| | 交易展示 | TradeRecordsTab 接 QMT 当日委托(/trade/orders)+ 当日成交(/trade/trades),单表合并展示,**仅实时、不落盘** | | 历史范围 | 非今日范围前端占位「历史数据接口开发中」(QMT Bridge 无历史接口,get_trade_detail_data 读客户端缓存仅当日) | | SQLite | 迭代 06 落地:strategy_holdings(持仓生命周期:holding_id 自增 + created_at/closed_at)+ market_quotes_cache 两表;holding_id 预留为交易关联锚点 | | 策略 | 策略=标签(settings),持仓份额存 strategy_holdings;同码可跨多个策略(部分唯一索引仅约束同策略同码一笔当前持仓) | ## 待讨论点(Q1-Q8,AI 建议见各条) ### Q1 存储粒度与表结构(AI 建议:委托 + 成交两表) ```sql -- 交易委托(委托主行;order_id 唯一,UPSERT 幂等;零冗余:不存 strategy_id/holding_id) CREATE TABLE IF NOT EXISTS trade_orders ( order_id TEXT PRIMARY KEY, -- m_strOrderSysID trade_date TEXT NOT NULL, -- 交易日 YYYYMMDD(m_strInsertDate) code TEXT NOT NULL, name TEXT NOT NULL DEFAULT '', exchange TEXT NOT NULL DEFAULT '', direction TEXT NOT NULL DEFAULT '', -- buy / sell direction_code INTEGER, opt_name TEXT NOT NULL DEFAULT '', status INTEGER, order_volume REAL NOT NULL DEFAULT 0, traded_volume REAL NOT NULL DEFAULT 0, limit_price REAL NOT NULL DEFAULT 0, traded_price REAL NOT NULL DEFAULT 0, amount REAL NOT NULL DEFAULT 0, insert_date TEXT NOT NULL DEFAULT '', insert_time TEXT NOT NULL DEFAULT '', insert_ts INTEGER NOT NULL, -- 派生列:insert_date+insert_time 合成毫秒时间戳(join 持仓窗口用) cancel_info TEXT NOT NULL DEFAULT '', error_msg TEXT NOT NULL DEFAULT '', fetched_at INTEGER NOT NULL -- 同步时间戳 ); -- 交易成交(成交明细;trade_id 唯一,order_id 外键关联委托;零冗余:不存 strategy_id/holding_id) CREATE TABLE IF NOT EXISTS trade_fills ( trade_id TEXT PRIMARY KEY, -- m_strTradeID order_id TEXT NOT NULL, -- → trade_orders.order_id trade_date TEXT NOT NULL, code TEXT NOT NULL, name TEXT NOT NULL DEFAULT '', exchange TEXT NOT NULL DEFAULT '', direction TEXT NOT NULL DEFAULT '', direction_code INTEGER, opt_name TEXT NOT NULL DEFAULT '', price REAL NOT NULL DEFAULT 0, volume REAL NOT NULL DEFAULT 0, amount REAL NOT NULL DEFAULT 0, commission_rate_wan REAL NOT NULL DEFAULT 0, trade_time TEXT NOT NULL DEFAULT '', fetched_at INTEGER NOT NULL ); CREATE INDEX IF NOT EXISTS idx_trade_orders_date ON trade_orders (trade_date); CREATE INDEX IF NOT EXISTS idx_trade_orders_ts ON trade_orders (insert_ts); CREATE INDEX IF NOT EXISTS idx_trade_fills_order ON trade_fills (order_id); CREATE INDEX IF NOT EXISTS idx_trade_fills_date ON trade_fills (trade_date); ``` - 理由:委托 1:N 成交,两表忠实 R-007「单表合并」模型(前端 mergeOrdersAndTrades 可复用);两表能保留无成交委托(待报/已撤/废单),复盘完整;避免 JSON 压扁。 - **2026-09-01 老师修正:两表都不冗余 strategy_id / holding_id**(外键链推导,见 Q3);trade_orders 增加派生列 insert_ts(insert_date+insert_time 合成,join 持仓窗口用)。 ### Q2 存储时机 / 同步机制(AI 建议:服务端定时同步 + API 写穿) - 服务端 TradeSync 模块定时(建议 60s)拉取今日 orders+trades → UPSERT 落库(幂等); - 前端今日轮询命中 orders/trades 端点时同步写穿(机会式); - 插件启动时同步一次(预热今日数据); - 理由:不依赖前端开 tab 也持续积累;UPSERT 幂等不重复。 ### Q3 策略关联方式(2026-09-01 二次修正定稿:手动设置归属) **最终定稿(老师拍板)**:关联链 = 成交(fill) → 委托(order) → 策略(strategy) → holding(持仓 ID),但**归属由用户手动设置,不靠算法推导**。 - **信息源 = 人**:交易记录 tab 加载每日交易记录,用户手动设置每笔委托的归属(下拉候选 = 该 code 的当前持仓策略 + 未关联);符合「人机合一」目标-007(AI 给候选,人做最终决定); - **trade_orders 冗余存 strategy_id + holding_id**(Q1 老师确认:加冗余字段,便于过滤/展示/复盘); - **UPSERT 不覆盖归属列**(Q2 老师确认):归属是手动指定的、是插件逻辑,TradeSync 定时同步只更新 QMT 原始字段,保留已有归属; - **全手动选**(Q3 老师确认):不自动预填候选值,用户明确选择后才落库;未设置前归属为空(未关联); - **可修改**(Q4 老师确认):已设置的归属可随时改,以最终修改为准; - 成交明细跟随委托归属(不单独设置); - 归属推导逻辑(方案 A 时间窗 + 份额最大)**降级为候选列表来源**:一票一策略时给出该策略候选、一票多策略时给出全部候选让用户选、无持仓时仅「未关联」。 ### Q4 历史查询能力(AI 建议:新增本地查询端点,策略过滤走 FK 链 join) - 新增端点(如 trades/history):按 { start, end, code, strategyId, direction } 查本地 SQLite,返回 { orders, fills }; - strategyId 过滤 = trade_orders JOIN strategy_holdings(生命周期窗口含委托时间,取份额最大持仓)筛选 strategy_id; - 前端历史范围从「占位」切换为查本地库(数据=插件运行期间逐日积累;插件停运期间无数据,诚实记录不可补); - 今日仍走 QMT 实时(更新鲜),同步写穿本地;QMT 不可用时可用本地兜底(可选)。 ### Q5 同步范围与数据积累(AI 建议) - 只同步「当日」数据(QMT 能力所限,无法回溯补历史); - 数据逐日积累 = 本地历史库;不做自动清理(复盘需要历史),导出/清理后续再说。 ### Q6 UI 扩展(AI 建议:策略过滤) - TradeRecordsTab 加策略过滤下拉(全部 / 各策略 / 未关联); - 历史范围展示本地数据(不再占位);按策略过滤对今日(实时)与历史(本地)均生效。 ### Q7 委托状态更新(AI 建议:UPSERT 覆盖) - 同一 order_id 再次同步时更新 status / 成交量等(当日委托状态流转:已报→部成→已成); - 历史日状态 = 最后一次同步快照(当日收市时基本已终态,可接受)。 ### Q8 文档与约束(后续执行) - 数据存储设计.md 增补交易记录表设计(§10);技术方案约束新增条目;产品约束按需(策略过滤 UI)。 ## 边界(初拟,待讨论收敛) **做**:两表落地(trade_orders + trade_fills,零冗余 strategy_id/holding_id)+ 定时同步落库(幂等)+ 本地历史查询端点(策略过滤 = 委托时间 join 持仓生命周期窗口)+ tab 策略过滤 + 历史范围本地展示。 **不做**(本期):手动修正策略关联入口(后续);导出/清理管理界面(后续);QMT 历史接口接入(老师完善后再接);下单/撤单操作(目标-007 另议)。 ## 讨论与修正记录 - 2026-09-01 老师提出需求,AI 登记(讨论中),提出 Q1-Q8 建议待确认。 - 2026-09-01 老师反馈(Q3 关联方式修正):关联链 = 成交 → 委托 → 策略 → holding。成交只与委托相关(order_id 外键即可);委托与策略相关;策略对应具体 holding_id。**两表均不冗余 strategy_id + holding_id** —— 采用零冗余 + 查询时按持仓生命周期时间窗 join 推导(Q3 定稿)。 - 2026-09-01 老师确认 Q1-Q8 全部定稿:两表零冗余 + 外键链关联(Q3)+ 定时同步 UPSERT + 本地历史查询 + tab 策略过滤 + 只同步当日;**R-009 转已定稿,进入迭代 07 实施**。 - 2026-09-01 老师二次定稿(Q1-Q4):归属由用户在交易记录 tab **手动设置**——trade_orders 冗余存 strategy_id + holding_id(Q1);**UPSERT 不覆盖归属列**(Q2,手动指定为插件逻辑);**全手动选**(Q3,不自动预填候选值);**可随时改、以最终为准**(Q4);算法推导降级为候选列表来源。