- R-009 归档头更新为二次定稿结论(手动归属 + 冗余),清理重复标题 - R-010 归档头补充讨论记录,清理重复标题 - 需求池索引:R-009 描述与最终实现一致(手动归属,非零冗余推导)
10 KiB
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 建议:委托 + 成交两表)
-- 交易委托(委托主行;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);算法推导降级为候选列表来源。