Files
kyugao 35f2ad1a23 docs(归档): 迭代 07/08 归档整理(R-009/R-010 归档文件规范化)
- R-009 归档头更新为二次定稿结论(手动归属 + 冗余),清理重复标题
- R-010 归档头补充讨论记录,清理重复标题
- 需求池索引:R-009 描述与最终实现一致(手动归属,非零冗余推导)
2026-09-02 09:42:28 +08:00

145 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 二次修正定稿(2026-09-01):归属由用户手动设置(trade_orders 冗余 strategy_id + holding_idUPSERT 不覆盖归属列,Q1-Q4 确认)
---
> 状态:**已定稿**(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);算法推导降级为候选列表来源。