docs(迭代07/08): 交易记录本地存储 + 策略持仓展开全量文档 + 需求归档(R-009/R-010)

- PLAN-008/PLAN-009 计划(已完成)
- 迭代 07/08 四件套(目标/技术方案/验收标准/复盘)
- 设计约束:数据存储设计 §10/§10.1、技术约束-012/013、产品约束-008
- R-009/R-010 需求归档(含二次定稿:手动归属、Q4 成本价/成交价)
- 需求池索引同步
This commit is contained in:
2026-09-02 01:21:10 +08:00
parent 859938c4f1
commit 2aacd8a25d
16 changed files with 821 additions and 6 deletions
+146
View File
@@ -0,0 +1,146 @@
# R-009 交易记录本地存储(SQLite)+ 策略关联 · 已完成
> 归档日期:2026-09-01 需求状态:**已完成**
> 原索引:docs/05-需求池/需求池索引.md(主索引保留 R-009 条目,指向本归档)
> 实现迭代:07-交易记录本地存储SQLite与策略关联(验收通过,迭代复盘见 docs/04-迭代记录/07-交易记录本地存储SQLite与策略关联/迭代复盘.md)
> 关联计划:PLAN-008docs/02-计划/计划-交易记录本地存储SQLite与策略关联.md)
> 讨论记录:Q1-Q8 定稿(2026-09-01),Q3 关联方式修正(外键链 + 两表零冗余)
---
# R-009 交易记录本地存储(SQLite)+ 策略关联
> 状态:**已定稿**(2026-09-01,老师确认)| 登记日期:2026-09-01
> 来源:老师指令(2026-09-01
> 优先级:P1
> 关联:R-007(交易记录接入)、R-008SQLite 存储)、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, -- 交易日 YYYYMMDDm_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_tsinsert_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_idQ1);**UPSERT 不覆盖归属列**(Q2,手动指定为插件逻辑);**全手动选**(Q3,不自动预填候选值);**可随时改、以最终为准**(Q4);算法推导降级为候选列表来源。