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:
@@ -0,0 +1,120 @@
|
||||
# 技术实现方案:07-交易记录本地存储(SQLite)+ 策略关联
|
||||
|
||||
> 依据:PLAN-008 | 需求:R-009(Q1-Q8 定稿)| 设计约束:技术约束-012(数据存储设计)、技术约束-011(测试隔离)
|
||||
|
||||
## 技术选型
|
||||
|
||||
- **存储**:沿用 SqliteStore(node:sqlite)——新增 trade_orders + trade_fills 两表(R-009 Q1);
|
||||
- **同步**:服务端 TradeSync 模块(定时 60s + 启动预热 + 前端今日轮询写穿;UPSERT 幂等);
|
||||
- **关联**:外键链推导(Q3 老师定稿):成交→委托(order_id 外键)→策略/holding(委托时间 join strategy_holdings 生命周期窗口)——两表零冗余 strategy_id/holding_id;
|
||||
- **历史查询**:api/trades.js 新增 trades/history 端点(查本地 SQLite);
|
||||
- **前端**:TradeRecordsTab 加策略过滤 + 历史范围查本地。
|
||||
|
||||
## 表结构(数据存储设计.md §10 落实)
|
||||
|
||||
```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);
|
||||
```
|
||||
|
||||
**策略归属推导(查询期,FK 链)**:
|
||||
|
||||
- 委托归属:`trade_orders JOIN strategy_holdings ON o.code=h.code AND h.created_at <= o.insert_ts AND (h.closed_at IS NULL OR o.insert_ts < h.closed_at)`;
|
||||
- 一码多策略消歧:命中多笔持仓时取 shares 最大者(确定性规则);
|
||||
- 成交归属 = 所属委托归属(fill → order → holding → strategy);
|
||||
- 无命中 → 未关联(strategy_id 返回 null)。
|
||||
|
||||
## 架构设计
|
||||
|
||||
```
|
||||
SqliteStore(改)
|
||||
├─ init() 建表:+trade_orders/trade_fills + 索引
|
||||
├─ upsertOrders(orders) / upsertFills(fills) # UPSERT 幂等
|
||||
├─ getOrderHistory({start,end,code,strategyId,direction}) # 委托 + 策略归属推导
|
||||
├─ getFillHistory({start,end,code,strategyId,direction}) # 成交(按委托归属 join)
|
||||
└─ close()
|
||||
|
||||
DataStore(改)
|
||||
└─ 委托交易记录方法(upsertTradeOrders/upsertTradeFills/queryTradeHistory)
|
||||
|
||||
TradeSync(新增)
|
||||
├─ start() : 启动预热同步一次 + 60s 定时
|
||||
├─ syncNow() : 拉今日 orders+trades → upsertOrders/upsertFills(幂等)
|
||||
└─ stop() : 清理定时器
|
||||
|
||||
api/trades.js(改)
|
||||
└─ trades/history → storage.queryTradeHistory(时间段/code/策略/方向过滤)
|
||||
|
||||
index.js(改)
|
||||
└─ 装配 TradeSync(dispose 停止)+ storage 注入 api runtime
|
||||
|
||||
client/views/TradeRecordsTab.jsx(改)
|
||||
├─ 策略过滤下拉(全部/各策略/未关联)——今日实时过滤前端、历史走服务端过滤
|
||||
└─ 历史范围:调 trades/history 查本地库(不再占位)
|
||||
```
|
||||
|
||||
## 实现步骤
|
||||
|
||||
1. **文档骨架**:PLAN-008 + 迭代 07(本步);
|
||||
2. **设计约束**:数据存储设计.md §10 + 技术方案约束新增条目;
|
||||
3. **SqliteStore**:建表 + upsertOrders/upsertFills + getOrderHistory/getFillHistory(策略归属 join);
|
||||
4. **DataStore**:委托交易记录方法;
|
||||
5. **TradeSync**:定时同步(60s + 启动预热 + UPSERT 幂等);
|
||||
6. **api/trades.js**:trades/history 端点;
|
||||
7. **index.js**:装配 TradeSync + storage 注入 runtime;
|
||||
8. **前端**:TradeRecordsTab 策略过滤 + 历史本地展示;
|
||||
9. **构建测试**:pnpm run build + typecheck + 独立数据目录回归(技术约束-011);
|
||||
10. **验收 + 复盘**。
|
||||
|
||||
## 涉及设计约束
|
||||
|
||||
| 约束 | 内容 |
|
||||
|---|---|
|
||||
| 技术约束-012 | 数据存储遵循数据存储设计.md(本迭代增补 §10 交易记录表设计) |
|
||||
| 技术约束-011 | 测试/回归脚本禁止在真实数据上写操作:ODL_TEST_DATA_DIR 独立数据目录 |
|
||||
| 技术约束-001/003/004/010 | 数据源抽象 / 直连 REST / webServer 自开路由 / 服务端中转+前端轮询(沿用) |
|
||||
| 产品约束-007 | 方向语义红买绿卖(沿用 R-007) |
|
||||
@@ -0,0 +1,61 @@
|
||||
# 迭代复盘:07-交易记录本地存储(SQLite)+ 策略关联
|
||||
|
||||
> 复盘日期:2026-09-01 | 迭代状态:**已完成(验收通过)**
|
||||
> 关联需求:R-009(交易记录本地存储 + 策略关联,已定稿)
|
||||
> 关联计划:PLAN-008(计划-交易记录本地存储SQLite与策略关联)
|
||||
|
||||
## 结果
|
||||
|
||||
迭代 07 达成:QMT 当日交易数据(委托 + 成交)本地持久化到 SQLite(trade_orders + trade_fills 两表,跨日积累成历史库),按外键链(成交→委托→策略→holding)与策略体系关联,支持按策略过滤 / 复盘交易;交易记录 tab 历史范围从「接口开发中」占位切换为本地历史查询。
|
||||
|
||||
## 过程事实
|
||||
|
||||
1. **需求定稿(R-009)**:老师提出 → AI 登记(讨论中)提出 Q1-Q8 → 老师修正关联方式(外键链 + 两表零冗余)→ 确认定稿进入迭代 07;
|
||||
2. **表结构**:trade_orders(委托主行,order_id 主键 + insert_ts 派生时间列)+ trade_fills(成交明细,trade_id 主键、order_id 关联委托)两表;**零冗余 strategy_id/holding_id**(Q3 老师定稿);
|
||||
3. **策略归属推导(FK 链)**:查询期委托时间(insert_ts)join strategy_holdings 生命周期窗口(created_at ≤ t < closed_at)推导;一码多策略取份额最大;未命中=未关联;attachStrategyAttribution 供今日实时委托附加归属;
|
||||
4. **TradeSync 同步**:启动预热 + 60s 定时拉当日 orders+trades → UPSERT 落库(幂等,状态覆盖更新);前端今日轮询命中 orders 端点时写穿(机会式);
|
||||
5. **本地历史查询**:api/trades.js 新增 trades/history 端点(时间段/code/策略/方向过滤,策略过滤走 FK 链 join);
|
||||
6. **前端**:TradeRecordsTab 加策略过滤下拉(全部/各策略/未关联)+ 历史范围查本地库(不再占位);
|
||||
7. **验证**:typecheck 通过、构建成功、独立数据目录回归测试 17/17 通过 + TradeSync 集成测试 5/5 通过(幂等/归属推导/过滤/写穿)。
|
||||
|
||||
## 经验教训(复盘沉淀)
|
||||
|
||||
### 1. 时间基准必须一致(本次踩坑)
|
||||
- **教训**:insert_ts 合成先用 Date.UTC(),而 strategy_holdings.created_at 是本地 Date.now() —— 时区差导致持仓窗口匹配失败(回归测试 6 项失败);
|
||||
- **沉淀**:同一库内时间戳必须同一基准(本地时间);跨模块时间比较前先核对基准。
|
||||
|
||||
### 2. 测试数据要构造真实时间窗
|
||||
- 第一次归属推导测试失败是因为用「当前时间」建仓(23:32)而委托是 09:30 —— 窗口本就不覆盖,代码是对的、测试数据不对;
|
||||
- **沉淀**:生命周期窗口类测试必须用 SQL 精确控制 created_at/closed_at,模拟真实时间关系。
|
||||
|
||||
### 3. 大改组件优先整体重写(前端)
|
||||
- 迭代中对 TradeRecordsTab 做多次定点替换时部分替换未生效,产生中间态(引用未定义组件);
|
||||
- **沉淀**:结构性大改(新增过滤/分支重构)直接整文件重写更稳,避免局部替换残留。
|
||||
|
||||
|
||||
### 4. QMT 委托/成交 code 无后缀 + 无独立交易日字段(真实数据发现)
|
||||
- **发现**:重启后启动预热同步的真实委托/成交,code 为无后缀 `001330`(持仓体系是 `001330.SZ`),且委托无 `m_strTradeDate`(交易日 = `m_strInsertDate`)—— 直接导致 FK 链 join 匹配不上(博纳影业被判未关联)+ 历史按时间段过滤漏委托;
|
||||
- **修复**:QmtBridgeRestDataSource 加 `normalizeInstrumentCode`(6 位数字 + 交易所后缀;SH/SZ/BJ;无交易所按首位推断 6/9→SH、0/3→SZ;已带后缀保留)+ mapOrder 补 tradeDate(insertDate 兜底)+ mapTrade code 归一化;
|
||||
- **沉淀**:QMT 各接口的证券代码格式不一致(委托/成交无后缀、持仓带后缀),语义化映射层必须统一归一化;交易「日」概念在委托接口 = insertDate(无独立 tradeDate 字段)。
|
||||
|
||||
|
||||
### 5. 迁移持仓 created_at = 迁移时间戳 → FK 链窗口失真(真实数据发现 + 方案 A 修正)
|
||||
- **发现**:迁移自 JSON 的持仓 created_at 全部是迁移时刻时间戳(2026-09-01 17:36),晚于当日真实交易时间 → 当日委托 join 窗口不匹配 → 全部判「未关联」;
|
||||
- **决策(方案 A,2026-09-01 老师确认)**:当前持仓(closed_at IS NULL)只做同 code 匹配(不要求 created_at ≤ 委托时间,现在持有=当日交易可归);已清仓(closed_at 非空)才按时间窗口(created_at ≤ t < closed_at)判断;
|
||||
- **实现**:_resolveStrategyAttribution 窗口条件分支;回归测试覆盖迁移时间戳场景(17/17 通过);
|
||||
- **沉淀**:迁移数据的 created_at 语义 = 迁移时间而非真实建仓时间,时间窗口类推导必须考虑该失真;当前持仓用「存在即归属」更贴合业务语义。
|
||||
|
||||
|
||||
### 6. 归属判定改「手动设置」(R-009 二次定稿,老师拍板)
|
||||
- **发现**:算法推导(时间窗 + 份额最大)无法区分一票多策略(300057.SZ 同时分属 grid-supermarket 与 manual-t 各 1000 股,明天有成交不知道该归谁);
|
||||
- **决策(老师二次定稿 Q1-Q4)**:归属由用户在**交易记录 tab 手动设置**(全手动选、可随时改、以最终为准);trade_orders 冗余存 strategy_id + holding_id;**UPSERT 不覆盖归属列**(手动指定为插件逻辑);
|
||||
- **实现**:trade_orders 加 strategy_id/holding_id 列(存量库 ALTER 迁移,插件启动时执行,只读打开容忍);setOrderAttribution + getAttributionCandidates(候选 = 该 code 当前持仓策略)+ api 两端点(orders/set-attribution、orders/attribution-candidates);前端 TradeRecordsTab 归属列下拉(候选含策略名 + 份额);
|
||||
- **验证**:37 项测试通过(核心 14 含 UPSERT 不覆盖归属/归属可改/候选列表/持久化过滤);
|
||||
- **沉淀**:算法只能给候选,归属是人的决定(符合「人机合一」目标-007);手工指定的数据不能被自动同步覆盖——UPSERT 语义要区分「系统字段」与「用户字段」。
|
||||
|
||||
## 遗留/后续
|
||||
|
||||
1. **QMT Bridge 历史接口**:老师完善后,今日实时可扩展为历史接口查询(本地库仍为兜底/积累);
|
||||
2. **手动修正策略关联入口**:本期未做(自动推导 + 未关联兜底已满足复盘),后续按需;
|
||||
3. **导出/清理**:本地历史库持续积累,导出/清理管理界面后续迭代;
|
||||
4. **盘中行情写 SQLite 验证**(迭代 06 遗留):开盘后确认行情防抖写回 store.db(updated_at / mtime)。
|
||||
@@ -0,0 +1,28 @@
|
||||
# 迭代目标:07-交易记录本地存储(SQLite)+ 策略关联
|
||||
|
||||
> 迭代编号:07 | 创建:2026-09-01 | 状态:进行中
|
||||
> 依据计划:PLAN-008 | 需求:R-009(已定稿,2026-09-01)
|
||||
|
||||
## 目标描述
|
||||
|
||||
将 QMT 当日交易数据(委托 + 成交)本地持久化到 SQLite(trade_orders + trade_fills 两表,跨日积累成历史库),并按外键链(成交→委托→策略→holding)与插件策略体系关联,支持按策略过滤 / 复盘交易;同时解决 R-007 历史范围「接口开发中」占位问题(本地积累后历史可查)。
|
||||
|
||||
## 目标分解
|
||||
|
||||
1. **两表落地**:trade_orders(委托主行)+ trade_fills(成交明细),两表零冗余 strategy_id/holding_id,委托表含派生时间列 insert_ts(join 持仓窗口用);
|
||||
2. **定时同步**:服务端 TradeSync(启动预热 + 60s 定时 + UPSERT 幂等);
|
||||
3. **外键链关联**:策略/持仓归属 = 委托时间 join strategy_holdings 生命周期窗口推导(一码多策略取份额最大,未命中=未关联);
|
||||
4. **本地历史查询**:trades/history 端点(时间段/code/策略/方向过滤);
|
||||
5. **前端**:策略过滤下拉 + 历史范围查本地库。
|
||||
|
||||
## 讨论过程
|
||||
|
||||
- 2026-09-01 老师提出需求(在 SQLite 添加交易记录表,存储与策略关联的交易记录);
|
||||
- 2026-09-01 AI 登记 R-009(讨论中),提出 Q1-Q8 技术建议(两表结构/同步机制/策略关联/历史查询/积累范围/UI/状态更新/文档约束);
|
||||
- 2026-09-01 老师修正(Q3):关联链 = 成交→委托→策略→holding,两表零冗余,查询期按持仓生命周期窗口推导;
|
||||
- 2026-09-01 老师确认 Q1-Q8 定稿,R-009 转已定稿,进入本迭代。
|
||||
|
||||
## 对老师的配合需求
|
||||
|
||||
- 无阻塞依赖;QMT Bridge 历史接口未就绪不影响本迭代(本地历史库 = 插件运行期逐日积累);
|
||||
- 验收时可提供当日真实交易数据做端到端验证(沿用 R-007 模式)。
|
||||
@@ -0,0 +1,26 @@
|
||||
# 验收标准:07-交易记录本地存储(SQLite)+ 策略关联
|
||||
|
||||
## 验收标准线
|
||||
|
||||
1. **两表落地**:store.db 存在 trade_orders(order_id 主键 + insert_ts 派生列)+ trade_fills(trade_id 主键、order_id 关联委托)两表;**两表均无 strategy_id / holding_id 列**(零冗余,Q3 老师定稿);
|
||||
2. **同步落库**:启动预热 + 60s 定时同步今日 orders+trades 入库;UPSERT 幂等(重复同步不产生重复行、已存在委托状态/成交量覆盖更新);
|
||||
3. **策略归属推导(FK 链)**:查询时委托时间 join strategy_holdings 生命周期窗口(created_at ≤ t < closed_at)得到策略/持仓归属;一码多策略取份额最大;无命中=未关联;历史委托归属稳定(清仓/删策略转历史不删行);
|
||||
4. **本地历史查询**:trades/history 端点按 { start, end, code, strategyId, direction } 过滤返回 { orders, fills }(策略过滤走 FK 链 join);
|
||||
5. **前端**:交易记录 tab 加策略过滤下拉(全部/各策略/未关联);历史范围展示本地库数据(不再「接口开发中」占位);今日仍走 QMT 实时 + 写穿本地;
|
||||
6. **不回归**:今日实时展示(单表合并/费用/排序/展开)与持仓/策略/行情/连接配置功能正常;
|
||||
7. **测试隔离**:回归测试在独立数据目录(ODL_TEST_DATA_DIR)执行,真实数据目录无写操作(技术约束-011)。
|
||||
|
||||
## 验收方法
|
||||
|
||||
- 独立数据目录(ODL_TEST_DATA_DIR)构造今日委托/成交样例 → 启动插件 → 确认同步落库、UPSERT 幂等(重复同步行数不变、状态覆盖);
|
||||
- 构造持仓生命周期样例(当前持仓 + 已清仓)→ 确认策略归属推导正确(时间窗口命中、一码多策略取份额最大、未关联兜底);
|
||||
- API 实测:trades/history 按时间段/code/策略/方向过滤返回正确;
|
||||
- 前端:策略过滤下拉生效(今日实时 + 历史本地);历史范围展示本地数据;
|
||||
- 回归:今日实时展示与现有功能正常;
|
||||
- 手动核对真实数据目录:store.db 中 trade_orders/trade_fills 持续积累,无 JSON 文件新生。
|
||||
|
||||
## 验收目标
|
||||
|
||||
- 交易记录本地持久化闭环:当日实时 + 跨日历史本地可查;
|
||||
- 外键链策略关联正确:按策略过滤 / 复盘交易就绪(R-007 Q10 落地);
|
||||
- 设计约束同步落地:数据存储设计.md §10 + 技术方案约束新增条目。
|
||||
@@ -0,0 +1,71 @@
|
||||
# 技术实现方案:08-策略持仓行展开关联交易记录(Holding → 交易汇总)
|
||||
|
||||
> 依据:PLAN-009 | 需求:R-010(Q1-Q3 定稿)| 设计约束:技术约束-012、技术约束-011(测试隔离)
|
||||
|
||||
## 技术选型
|
||||
|
||||
- 复用 R-009 的 holding_id 关联(trade_orders.holding_id);
|
||||
- 服务端:strategy-positions 附加 holding_id + trades/by-holding 端点;
|
||||
- 前端:StrategyTab 持仓行展开(懒加载内嵌汇总表)。
|
||||
|
||||
## 数据流
|
||||
|
||||
```
|
||||
策略持仓 tab:
|
||||
load() → strategy-positions(每行含 holding_id)
|
||||
用户点击持仓行 → 展开 → trades/by-holding?holdingId=13 → 委托汇总列表
|
||||
懒加载:不展开不请求
|
||||
```
|
||||
|
||||
## 实现细节
|
||||
|
||||
### 1. PositionManager.getStrategyPositions 附加 holding_id
|
||||
|
||||
```js
|
||||
async getStrategyPositions(strategyId) {
|
||||
const [positions, dataset, holdings] = await Promise.all([
|
||||
this.getAllPositions(),
|
||||
this.storage.getDataset(strategyId),
|
||||
this.storage.getCurrentHoldings(strategyId), // 含 holding_id
|
||||
]);
|
||||
const shareMap = new Map(dataset.map(d => [d.code, d.shares]));
|
||||
const holdingMap = new Map(holdings.map(h => [h.code, h.holdingId]));
|
||||
return positions.map(p => {
|
||||
const shares = shareMap.get(p.code) ?? 0;
|
||||
return shares > 0 ? { ...p, shares, holdingId: holdingMap.get(p.code) ?? null } : null;
|
||||
}).filter(Boolean);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. SqliteStore.getOrdersByHolding
|
||||
|
||||
```js
|
||||
getOrdersByHolding(holdingId) {
|
||||
this.init();
|
||||
return this.db.prepare(
|
||||
'SELECT * FROM trade_orders WHERE holding_id=? ORDER BY insert_ts DESC'
|
||||
).all(holdingId).map(r => this._mapOrderRow(r));
|
||||
}
|
||||
```
|
||||
|
||||
### 3. api/trades.js:trades/by-holding 端点
|
||||
|
||||
```js
|
||||
case 'trades/by-holding':
|
||||
return await storage.getTradeOrdersByHolding(args.holdingId);
|
||||
```
|
||||
|
||||
### 4. StrategyTab 展开 UI
|
||||
|
||||
- 持仓行加展开箭头(类似交易记录 tab);
|
||||
- 展开时调 trades/by-holding,内嵌小表格显示委托汇总:
|
||||
时间 / 方向 / 状态 / 委托量 / 成交量 / 均价 / 金额 / 费用;
|
||||
- 只显示委托汇总(ORDER_STATUS 中文映射复用);
|
||||
- 懒加载:展开时才请求,折叠清空。
|
||||
|
||||
## 涉及设计约束
|
||||
|
||||
| 约束 | 内容 |
|
||||
|---|---|
|
||||
| 技术约束-012 | 数据存储遵循数据存储设计.md(复用 trade_orders.holding_id) |
|
||||
| 技术约束-011 | 测试用独立数据目录(ODL_TEST_DATA_DIR) |
|
||||
@@ -0,0 +1,38 @@
|
||||
# 迭代复盘:08-策略持仓行展开关联交易记录(Holding → 交易汇总)
|
||||
|
||||
> 复盘日期:2026-09-02 | 迭代状态:**已完成(老师确认)**
|
||||
> 关联需求:R-010(策略持仓行展开关联交易记录,已定稿)
|
||||
> 关联计划:PLAN-009(计划-策略持仓行展开关联交易记录)
|
||||
|
||||
## 结果
|
||||
|
||||
迭代 08 达成:策略持仓 tab 每个持仓行(Holding)可展开,展开显示与该 holding 关联的交易记录(委托汇总,不分笔成交);持仓行新增成本价(avgPrice)+ 最后一笔成交价(lastTradePrice)两列;神之一手全部 tab 激活时隐藏 AI 对话输入框(纯 CSS)。实现「持仓 ↔ 交易」双向追溯。
|
||||
|
||||
## 过程事实
|
||||
|
||||
1. **需求定稿(R-010)**:老师提出持仓行展开看关联交易(只要委托汇总)→ AI 登记 Q1-Q3(附加 holding_id / by-holding 端点 / 展开 UI)→ 老师确认 → 补充 Q4(成本价 + 最后一笔成交价,取最新有成交的 tradedPrice,无则默认成本价);
|
||||
2. **服务端**:strategy-positions 附加 holding_id(PositionManager 查 strategy_holdings)+ 计算 avgPrice/lastTradePrice(查该 holding 关联委托,取最新有成交的 tradedPrice);SqliteStore.getOrdersByHolding + DataStore 委托;api trades/by-holding 端点;
|
||||
3. **前端**:StrategyTab 持仓行展开(箭头 + 懒加载 + 内嵌委托汇总表,含交易日/时间列);成本价/最后一笔成交价两列;
|
||||
4. **隐藏输入框**:借鉴 dsh-context 插件的纯 CSS 方案(:has(.odl-root) 命中时隐藏 composer)——神之一手 tab 激活时隐藏输入框,chat/其他 tab 正常显示;
|
||||
5. **验证**:回归测试(by-holding 查询 6 项 + lastTradePrice 计算 4 项)通过、真实数据验证(001330.SZ holding 13 → 博纳影业卖出委托;成本价 6.84 / 最后成交 6.00)、构建 + typecheck 通过。
|
||||
|
||||
## 经验教训(复盘沉淀)
|
||||
|
||||
### 1. 隐藏宿主 UI 优先借鉴成熟插件方案
|
||||
- 曾深挖 DSH composer chain / selector 机制(复杂度高、依赖内部 store),后经老师提示参考 dsh-context 插件——它用一行纯 CSS(:has() 选择器)实现「特定 tab 激活时隐藏输入框」,零宿主改动、零风险;
|
||||
- **沉淀**:改宿主 UI 前先看同类插件怎么做的;纯 CSS :has() 是最优雅的 view 条件渲染方案。
|
||||
|
||||
### 2. 前端声明顺序 TDZ(本次多次踩坑)
|
||||
- 迭代 07/08 多次遇到「Cannot access X before initialization」(TDZ):useEffect 依赖数组/回调在渲染时求值,引用了后声明的 const;
|
||||
- **沉淀**:React 组件内所有 useCallback/useEffect 的依赖引用必须在其声明之后;新增代码时严格核对声明顺序,构建后浏览器验证。
|
||||
|
||||
### 3. 服务端 vs 客户端生效机制
|
||||
- 服务端改动(PositionManager/API/SqliteStore)需重启 DSH 生效;客户端 bundle 刷新即载(rev 变化自动同步);
|
||||
- **沉淀**:改完先确认服务端端点/响应是新行为,再让老师重启。
|
||||
|
||||
## 遗留/后续
|
||||
|
||||
1. **持仓展开的交易汇总**:当前只显示该 holding 关联的委托汇总;后续可按 holding 聚合复盘(目标-006);
|
||||
2. **关注列表 tab**:仍为占位(PlaceholderTab),后续迭代实现;
|
||||
3. **全部持仓 tab**:未做展开(老师只要求策略持仓 tab);如需可复用同样模式;
|
||||
4. **迭代 07 遗留**:QMT Bridge 历史接口(老师完善后接入)、盘中行情写 SQLite 验证(开盘后确认)。
|
||||
@@ -0,0 +1,24 @@
|
||||
# 迭代目标:08-策略持仓行展开关联交易记录(Holding → 交易汇总)
|
||||
|
||||
> 迭代编号:08 | 创建:2026-09-02 | 状态:进行中
|
||||
> 依据计划:PLAN-009 | 需求:R-010(已定稿,2026-09-02)
|
||||
|
||||
## 目标描述
|
||||
|
||||
策略持仓 tab 每个持仓行(Holding)可展开,展开显示与该 holding 关联的交易记录(仅委托汇总,不分笔成交),实现「持仓 ↔ 交易」双向追溯(R-009 反向)。
|
||||
|
||||
## 目标分解
|
||||
|
||||
1. **strategy-positions 附加 holding_id**:持仓行带出关联锚点;
|
||||
2. **trades/by-holding 端点**:按 holding_id 查委托汇总;
|
||||
3. **持仓行展开 UI**:展开显示委托汇总表(懒加载)。
|
||||
|
||||
## 讨论过程
|
||||
|
||||
- 2026-09-02 老师提出需求(持仓行展开看关联交易,只要委托汇总);
|
||||
- 2026-09-02 AI 登记 R-010(讨论中),提出 Q1-Q3(附加 holding_id / by-holding 端点 / 展开 UI);
|
||||
- 2026-09-02 老师确认 Q1-Q3 定稿,进入本迭代。
|
||||
|
||||
## 对老师的配合需求
|
||||
|
||||
- 无阻塞依赖;验收时用真实持仓(如 001330.SZ holding 13)验证展开效果。
|
||||
@@ -0,0 +1,23 @@
|
||||
# 验收标准:08-策略持仓行展开关联交易记录(Holding → 交易汇总)
|
||||
|
||||
## 验收标准线
|
||||
|
||||
1. **strategy-positions 附加 holding_id**:每个持仓行含 holding_id(同策略同 code 当前持仓);
|
||||
2. **trades/by-holding 端点**:按 holding_id 返回该 holding 的委托汇总(时间/方向/状态/量/价/金额/费用;无成交委托也显示);
|
||||
3. **持仓行展开**:策略持仓 tab 每行可展开(箭头指示),展开显示委托汇总表;
|
||||
4. **仅汇总不分笔**:展开内容只显示委托汇总,无分笔成交明细;
|
||||
5. **懒加载**:不展开不请求 trades/by-holding;
|
||||
6. **不回归**:策略持仓份额操作(添加/移出/全部移入)、交易记录 tab、行情等现有功能正常;
|
||||
7. **测试隔离**:回归测试在独立数据目录执行(技术约束-011)。
|
||||
|
||||
## 验收方法
|
||||
|
||||
- 独立数据目录构造持仓 + 委托(holding_id 关联)→ 启动 → 验证 strategy-positions 含 holding_id、trades/by-holding 返回正确;
|
||||
- 真实数据:网格超市 tab 展开 001330.SZ(holding 13)→ 看到博纳影业卖出委托汇总;
|
||||
- 前端:展开/折叠、懒加载、汇总表列正确;
|
||||
- 回归:份额操作、交易记录、行情不回归。
|
||||
|
||||
## 验收目标
|
||||
|
||||
- 持仓 ↔ 交易双向追溯闭环(R-009 交易→持仓 + 本迭代持仓→交易);
|
||||
- 为按 holding 复盘交易铺路(目标-006)。
|
||||
Reference in New Issue
Block a user