diff --git a/docs/02-计划/计划-交易记录本地存储SQLite与策略关联.md b/docs/02-计划/计划-交易记录本地存储SQLite与策略关联.md new file mode 100644 index 0000000..0e6a9fb --- /dev/null +++ b/docs/02-计划/计划-交易记录本地存储SQLite与策略关联.md @@ -0,0 +1,66 @@ +# 计划:交易记录本地存储(SQLite)+ 策略关联(阶段航点) + +> 编号:PLAN-008 | 粒度:阶段航点(大粒度) | 创建:2026-09-01 | 状态:**已完成(2026-09-01 迭代 07 验收通过)** +> 派生自终极目标:目标-006(交易复盘)、目标-003(按策略监控市场) +> 依据需求:**R-009(已定稿,2026-09-01,Q1-Q8 全部确认)** —— 符合入范围门槛 +> 设计约束:技术约束-012(数据存储设计)、技术约束-001/003/004/010(沿用)、产品约束-007 方向语义 + +## 目标 + +在 SQLite 新增**交易记录表**(trade_orders 委托 + trade_fills 成交),将 QMT Bridge 当日交易数据**本地持久化**(跨日积累成本地历史库),并按**外键链**(成交→委托→策略→holding)建立与插件策略体系的关联,支持**按策略过滤 / 复盘**交易。 + +- 承接 R-007 的 Q5(本地持久化)与 Q10(按策略过滤); +- 落实迭代 06 复盘遗留项 2(holding_id 关联锚点); +- 解决 R-007 历史范围「接口开发中」占位(本地积累后历史可查)。 + +## 范围 + +**做**: +1. **两表落地**:trade_orders(委托主行,order_id 主键,UPSERT 幂等)+ trade_fills(成交明细,trade_id 主键、order_id 外键关联委托)两表;**两表均不冗余 strategy_id / holding_id**(Q3 老师定稿:外键链推导);trade_orders 加派生列 insert_ts(insert_date+insert_time 合成毫秒时间戳,join 持仓窗口用); +2. **定时同步**:服务端 TradeSync 模块 —— 定时(60s)拉当日 orders+trades → UPSERT 落库(幂等);插件启动同步一次(预热今日数据);前端今日轮询写穿(机会式); +3. **策略关联(外键链推导)**:策略/持仓归属不在写入期计算,查询期由委托时间(insert_ts)JOIN strategy_holdings 生命周期窗口(created_at ≤ t < closed_at,closed_at 为 NULL=当前持仓)推导;同码多策略取份额最大持仓;无命中视为未关联; +4. **本地历史查询端点**:trades/history —— 按 { start, end, code, strategyId, direction } 查本地 SQLite,返回 { orders, fills }(策略过滤走 FK 链 join); +5. **前端**:TradeRecordsTab 加策略过滤下拉(全部/各策略/未关联);历史范围从「占位」切换为查本地库;今日仍走 QMT 实时 + 写穿本地。 + +**不做**: +- 手动修正策略关联入口(后续迭代); +- 导出/清理管理界面(后续迭代;复盘需要历史,不做自动清理); +- QMT 历史接口接入(老师完善后再接); +- 下单/撤单等交易操作(目标-007 另议)。 + +## 程序结构(改造后) + +``` +src/ +├── component/ +│ ├── SqliteStore.js # 改:+trade_orders/trade_fills 建表 + 交易 UPSERT/查询 + 策略归属推导 +│ ├── DataStore.js # 改:委托交易记录方法 +│ └── TradeSync.js # 新增:服务端定时同步(60s + 启动预热 + UPSERT 幂等) +├── api/ +│ └── trades.js # 改:+trades/history 端点 +└── index.js # 改:装配 TradeSync + storage 注入 api runtime +src/client/views/ +└── TradeRecordsTab.jsx # 改:策略过滤下拉 + 历史范围查本地库 +``` + +## 实现步骤(建议顺序) + +1. **文档骨架**:PLAN-008 + 迭代 07 子目录(本步); +2. **设计约束落地**:数据存储设计.md §10 增补交易记录表设计 + 技术方案约束新增条目; +3. **SqliteStore**:建表(trade_orders/trade_fills)+ 交易 UPSERT(幂等)+ 本地历史查询 + 策略归属推导(insert_ts join 持仓窗口); +4. **DataStore**:委托交易记录方法; +5. **TradeSync**:定时同步模块(60s + 启动预热 + 今日范围 UPSERT); +6. **api/trades.js**:trades/history 端点(+ 现有 orders/trades 响应附加策略归属信息可选); +7. **index.js**:装配 TradeSync(dispose 清理)+ storage 注入 api runtime; +8. **前端**:TradeRecordsTab 策略过滤 + 历史范围本地展示; +9. **构建测试**:pnpm run build + typecheck + 独立数据目录回归测试(技术约束-011); +10. **验收**:对照迭代 07 验收标准逐条核验,记录迭代复盘。 + +## 验收要点 + +- trade_orders + trade_fills 两表落地(零冗余 strategy_id/holding_id,insert_ts 派生列); +- 同步:启动预热 + 60s 定时 UPSERT 幂等(重复同步不重复行、状态覆盖更新); +- 历史查询:按时间段/code/策略/方向查本地库,策略过滤 = 委托时间 join 持仓生命周期窗口(一码多策略取份额最大); +- 前端:策略过滤下拉 + 历史范围展示本地数据(不再占位);今日实时 + 写穿; +- 现有功能不回归(持仓/策略/行情/连接配置/交易记录今日实时); +- 技术约束-011:回归测试用独立数据目录。 \ No newline at end of file diff --git a/docs/02-计划/计划-策略持仓行展开关联交易记录.md b/docs/02-计划/计划-策略持仓行展开关联交易记录.md new file mode 100644 index 0000000..13878ed --- /dev/null +++ b/docs/02-计划/计划-策略持仓行展开关联交易记录.md @@ -0,0 +1,55 @@ +# 计划:策略持仓行展开关联交易记录(Holding → 交易汇总)(阶段航点) + +> 编号:PLAN-009 | 粒度:阶段航点(大粒度) | 创建:2026-09-02 | 状态:**已完成(2026-09-02 迭代 08 老师确认)** +> 派生自终极目标:目标-006(交易复盘)、目标-003(按策略监控市场) +> 依据需求:**R-010(已定稿,2026-09-02,Q1-Q3 确认)** —— 符合入范围门槛 +> 设计约束:技术约束-012(数据存储设计)、技术约束-001/003/004(沿用) + +## 目标 + +策略持仓 tab 每个持仓行(Holding)可展开,展开显示与该 holding 关联的交易记录(仅委托汇总,不分笔成交),实现「持仓 ↔ 交易」的双向追溯(R-009 反向:交易→持仓已实现,本迭代持仓→交易)。 + +## 范围 + +**做**: +1. **strategy-positions 附加 holding_id**:PositionManager.getStrategyPositions 返回时,查 strategy_holdings 附加当前持仓的 holding_id(Q1); +2. **trades/by-holding 端点**:按 holding_id 查 trade_orders,返回委托汇总列表(时间/方向/状态/委托量/成交量/均价/金额/费用)(Q2); +3. **持仓行展开 UI**:StrategyTab 持仓行加展开(类似交易记录 tab 展开效果),展开后内嵌小表格显示该 holding 的委托汇总;懒加载(展开时才请求)(Q3)。 + +**不做**: +- 分笔成交明细展示(老师明确只要汇总); +- 交易记录 tab 改动; +- 归属设置入口(R-009 已有)。 + +## 程序结构 + +``` +src/ +├── component/ +│ ├── PositionManager.js # 改:getStrategyPositions 附加 holding_id +│ ├── SqliteStore.js # 改:+getOrdersByHolding(holdingId) +│ └── DataStore.js # 改:+getTradeOrdersByHolding +├── api/ +│ └── trades.js # 改:+trades/by-holding 端点 +src/client/views/ +└── StrategyTab.jsx # 改:持仓行展开 + 内嵌委托汇总表(懒加载) +``` + +## 实现步骤 + +1. **文档骨架**:PLAN-009 + 迭代 08(本步); +2. **SqliteStore**:getOrdersByHolding(WHERE holding_id=? ORDER BY insert_ts DESC); +3. **DataStore**:委托方法; +4. **PositionManager**:getStrategyPositions 附加 holding_id; +5. **api/trades.js**:trades/by-holding 端点; +6. **StrategyTab**:持仓行展开 + 懒加载 + 内嵌汇总表; +7. **构建测试**:pnpm run build + typecheck + 独立数据目录回归(技术约束-011); +8. **验收 + 复盘**。 + +## 验收要点 + +- strategy-positions 每行含 holding_id(同策略同 code 当前持仓); +- trades/by-holding 返回该 holding 的委托汇总(无成交的委托也显示); +- StrategyTab 持仓行可展开,展开显示委托汇总表(不分笔成交); +- 懒加载:不展开不请求; +- 现有功能不回归。 \ No newline at end of file diff --git a/docs/03-设计约束/产品功能约束.md b/docs/03-设计约束/产品功能约束.md index d0e5e69..e60e627 100644 --- a/docs/03-设计约束/产品功能约束.md +++ b/docs/03-设计约束/产品功能约束.md @@ -17,6 +17,7 @@ | 产品约束-007 | 3 个监控表格(全部持仓 / 手动做T / 网格超市)展示**盘中现价**:现价列显示 lastPrice,红涨绿跌着色(对比昨收),价格变化轻微高亮;本期仅现价一项实时数据 | 2026-08-31 | 生效 | - | R-005 定稿(2026-08-31):老师确认现价列 + 涨跌色 + 变化高亮 | | 产品约束-006 | QMT 连接会话头部快捷切换:会话窗口顶栏(PTC 模式标签旁)常驻下拉控件(chip 显示 `QMT: <激活配置名>`),点开列出全部配置(激活项勾选),点选即激活并轻提示反馈;头部控件仅做切换,配置管理(增删改/测试连接/默认标记)仍在设置页子 tab | 2026-08-29 | 生效 | - | R-004 定稿(2026-08-29):Q9/Q10 确认(入口=会话头部 PTC 旁,菜单仅切换激活) | +| 产品约束-008 | 交易记录支持**按策略过滤**:交易记录 tab 提供策略过滤下拉(全部 / 各策略 / 未关联),对今日(QMT 实时)与历史(本地 SQLite)均生效;策略归属 = 委托时间 join 持仓生命周期窗口推导(一码多策略取份额最大,未命中=未关联);历史范围展示本地积累数据(不再「接口开发中」占位) | 2026-09-01 | 生效 | - | 新增(2026-09-01 R-009 定稿 + 迭代 07 实施):策略过滤 + 历史本地展示 | \ No newline at end of file diff --git a/docs/03-设计约束/技术方案约束.md b/docs/03-设计约束/技术方案约束.md index f3e6626..ca972c1 100644 --- a/docs/03-设计约束/技术方案约束.md +++ b/docs/03-设计约束/技术方案约束.md @@ -17,11 +17,12 @@ | 技术约束-006 | 客户端 slots.register 的 component 必须是第二参数(register({...}, Component));settings schema 必须用 schemastery z.object() 函数式定义 | 2026-08-28 | 生效 | - | 迭代 01 复盘沉淀:component 位置错误致 React #130;普通对象 schema 报 schema is not a function | | 技术约束-007 | 插件安装用 dsh plugin add(自动 reconcile bundles),不直接用 pnpm add;bundle patch 顶层必须是 insert 操作 | 2026-08-28 | 生效 | - | 迭代 01 复盘沉淀:pnpm add 不会更新 dsh.profile.bundles | | 技术约束-008 | QMT 连接配置存储复用 one-divine-lot settings namespace(新增 qmtConnections 字段:list[{id,name,baseUrl,order}] + activeId + defaultId),与策略配置同机制持久化;激活切换 = 更新数据源实例的 baseUrl(数据源按请求读取地址,已核实),立即生效无需重启 DSH;插件启动时激活默认配置(列表为空时回退 cordis 注入的 qmtBaseUrl 兜底,不做自动迁移);测试连接由服务端代理请求 {baseUrl}/health(避免浏览器跨域) | 2026-08-29 | 生效 | - | R-004 定稿(2026-08-29):Q1/Q2/Q4/Q5/Q8 确认(启动自动激活默认、立即切换、不迁移、超时不配置化、复用 settings) | -| 技术约束-012 | 插件数据存储遵循 **docs/03-设计约束/数据存储设计.md**:存储引擎为 **SQLite(node:sqlite)**——strategy_holdings + market_quotes_cache 两表(持仓生命周期表 + 行情极简两列缓存表);存储层单票生命周期操作;策略定义仍存 DSH settings;旧 JSON(store.json / store.market.json / allocations.json)经一次性迁移脚本 + 启动自动迁移(幂等、迁移前自动备份)后废弃;仅存储引擎替换,对外行为不变 | 2026-09-01 | 生效 | - | 变更(2026-09-01 R-008 定稿 + 迭代 06 实施):JSON data store → SQLite(node:sqlite,零依赖分发;strategy_holdings 持仓生命周期表+market_quotes_cache 两表;存储层单票生命周期;自动迁移;JSON 废弃);R-006 原 JSON 设计为上一版本基线 | +| 技术约束-012 | 插件数据存储遵循 **docs/03-设计约束/数据存储设计.md**:存储引擎为 **SQLite(node:sqlite)**——strategy_holdings + market_quotes_cache + **trade_orders + trade_fills(交易记录两表,R-009)**;存储层单票生命周期操作;交易记录两表零冗余 strategy_id/holding_id(外键链推导策略归属);策略定义仍存 DSH settings;旧 JSON(store.json / store.market.json / allocations.json)经一次性迁移脚本 + 启动自动迁移(幂等、迁移前自动备份)后废弃;仅存储引擎替换,对外行为不变 | 2026-09-01 | 生效 | - | 变更(2026-09-01 R-008 定稿 + 迭代 06 实施):JSON data store → SQLite;变更(2026-09-01 R-009 定稿 + 迭代 07 实施):+trade_orders/trade_fills 交易记录两表(零冗余,外键链推导策略归属) | | 技术约束-011 | 测试/回归脚本**禁止在真实数据上执行写操作**:份额写操作(add/remove/move/clear)必须使用独立数据目录(AllocationStorage 支持 ODL_TEST_DATA_DIR 环境变量或 dataDir 参数指向临时目录),只读端点(positions/summary/strategies/market-snapshot)可直连生产 API | 2026-08-31 | 生效 | - | 2026-08-31 数据误删事故沉淀:回归测试误删大连热电/万顺新材份额分配,老师定「测试用独立数据目录」 | | 技术约束-010 | 行情实时数据由**服务端中转 + 缓存**提供(R-005 演进,2026-08-31 老师改):DSH 服务端做「WS 订阅 + REST 轮询 + 行情缓存」,前端统一轮询 /odl/api/market-snapshot(不做前端直连,无跨域);行情持久化到 store.market.json(重启不丢价,首屏快速展现);QMT Bridge WS 推送是会话级/有状态行为(归属 QMT Bridge 工作空间) | 2026-08-31 | 生效 | - | 变更(2026-08-31):老师由「前端直连 WS」改为「服务端中转 + 缓存」——解决跨域与 WS 语义不稳定问题;2026-09-01 加行情持久化与启动 prime | | 技术约束-009 | 会话头部快捷切换控件挂载 DSH 开放 slot `conversation.session.header.actions`(多实例挂载点,按 order 排序多插件共存):客户端插件以独立 id 并排注册(DSH 内置 PTC 标签 order=-10,本控件 order=-9),不改动 DSH 宿主;控件经 ConnectionProvider 包装复用现有 RPC 通道与 /odl/api/* 端点 | 2026-08-29 | 生效 | - | R-004 定稿(2026-08-29):Q9 确认;宿主代码审查核实 slot 机制与内置插件注册方式 | +| 技术约束-013 | 交易记录本地存储(R-009):QMT 当日交易数据(委托/成交)由服务端 TradeSync 定时同步落 SQLite(启动预热 + 60s 定时 + UPSERT 幂等,只同步当日);trade_orders(委托主行,order_id 主键 + insert_ts 派生时间列 + **strategy_id/holding_id 手动归属列**)+ trade_fills(成交明细,trade_id 主键、order_id 外键)两表;**委托归属由用户在交易记录 tab 手动设置**(候选 = 该 code 当前持仓策略 + 未关联,全手动选、可随时改、以最终为准);**UPSERT 不覆盖归属列**(手动指定为插件逻辑);本地历史查询走 trades/history 端点(策略过滤 = 用户设置的归属);今日实时仍走 QMT Bridge;QMT 委托/成交 code 无后缀、持仓带后缀 —— 数据源映射层统一 normalizeInstrumentCode 归一化;委托交易日 = insertDate(tradeDate 兜底) | 2026-09-01 | 生效 | - | 新增(2026-09-01 R-009 定稿 + 迭代 07 实施):两表 + 定时同步 + 本地历史查询;变更 1(2026-09-01):+code 归一化 + tradeDate 兜底;变更 2(2026-09-01 老师二次定稿):归属改**手动设置**(trade_orders 冗余 strategy_id+holding_id,UPSERT 不覆盖归属列),弃算法推导 | +--> \ No newline at end of file diff --git a/docs/03-设计约束/数据存储设计.md b/docs/03-设计约束/数据存储设计.md index d3ed3f3..a629f72 100644 --- a/docs/03-设计约束/数据存储设计.md +++ b/docs/03-设计约束/数据存储设计.md @@ -261,8 +261,98 @@ CREATE TABLE IF NOT EXISTS market_quotes_cache ( | MarketFeed | 行情获取(WS + REST 定时刷新 + 启动 prime) | src/component/MarketFeed.js | | migrate 脚本 | 一次性迁移脚本 | scripts/migrate-json-to-sqlite.mjs | -## 10. 约束条目(引用) +## 10. 交易记录存储设计(R-009 落实,2026-09-01 迭代 07 实施) -- 技术约束-012:数据存储遵循本文件(SQLite 存储设计); +> 依据:R-009(Q1-Q8 定稿,2026-09-01)。在 SQLite 新增交易记录两表(trade_orders + trade_fills),QMT 当日交易数据本地持久化(跨日积累成历史库),支持按策略过滤 / 复盘交易。 + +### 10.1 表结构 + +```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 合成毫秒时间戳 + cancel_info TEXT NOT NULL DEFAULT '', + error_msg TEXT NOT NULL DEFAULT '', + strategy_id TEXT, -- 用户手动设置的策略归属(冗余,Q1 确认) + holding_id INTEGER, -- 用户手动设置的持仓归属(冗余,Q1 确认) + 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); +``` + +### 10.2 关键设计决策(R-009 Q1-Q8 定稿) + +| 决策 | 结论 | 理由 | +|---|---|---| +| 表范围 | trade_orders + trade_fills 两表 | 委托 1:N 成交,忠实 R-007 单表合并模型;保留无成交委托(待报/已撤/废单);避免 JSON 压扁 | +| 冗余 | **trade_orders 冗余 strategy_id + holding_id**(用户手动设置) | 老师二次定稿(Q3 修正):手动设置归属,便于过滤/展示/复盘;trade_fills 仍零冗余(跟随委托) | +| 委托时间派生列 | insert_ts(insert_date+insert_time 合成毫秒) | join 持仓生命周期窗口用(created_at ≤ t < closed_at) | +| 策略/持仓归属 | **用户手动设置**(交易记录 tab 下拉:候选 = 该 code 当前持仓策略 + 未关联);持久化到 trade_orders.strategy_id + holding_id;UPSERT 不覆盖归属列;可随时改以最终为准 | 一票多策略无法算法区分(300057.SZ 分属 grid/manual),人为确认符合「人机合一」(2026-09-01 老师二次定稿) | +| 同步 | 服务端 TradeSync:启动预热 + 60s 定时 UPSERT(幂等);前端今日轮询写穿 | 不依赖前端开 tab 也持续积累 | +| 代码归一化 | QMT 委托/成交 code 无后缀(001330),持仓带后缀(001330.SZ)——数据源映射层统一 `normalizeInstrumentCode`(补交易所后缀);委托交易日 = insertDate(无独立 tradeDate 字段),tradeDate 兜底 insertDate | 保证 FK 链 join 匹配 + 历史按时间段过滤正确(迭代 07 真实数据发现) | +| 同步范围 | 只同步当日(QMT 无历史接口) | 数据逐日积累 = 本地历史库 | +| 历史查询 | trades/history 端点(时间段/code/策略/方向过滤) | 复盘 + 按策略过滤 | +| 数据清理 | 不做自动清理(复盘需要历史) | 导出/清理后续迭代 | + +### 10.3 数据流 + +``` +启动时:TradeSync 启动预热一次(拉今日 orders+trades → UPSERT 落库,不覆盖归属列) +实盘中:TradeSync 60s 定时同步(UPSERT 幂等,状态覆盖更新,归属列保留) + 前端今日轮询命中 orders/trades 端点 → 写穿本地(机会式) +用户: 交易记录 tab 手动设置每笔委托归属(strategy_id + holding_id 落库,可随时改) +查询时:今日 → QMT 实时(+ 本地归属);历史范围 → trades/history 查本地 SQLite(策略过滤 = 用户设置的归属) +``` + +### 10.4 对应实现(更新) + +| 模块 | 职责 | 文件 | +|---|---|---| +| SqliteStore | +trade_orders/trade_fills 建表 + 交易 UPSERT/历史查询 + 策略归属推导 | src/component/SqliteStore.js | +| DataStore | +交易记录方法(upsertTradeOrders/upsertTradeFills/queryTradeHistory) | src/component/DataStore.js | +| TradeSync | 服务端定时同步(启动预热 + 60s + UPSERT 幂等) | src/component/TradeSync.js | +| api/trades.js | +trades/history 端点 | src/api/trades.js | + +## 11. 约束条目(引用) + +- 技术约束-012:数据存储遵循本文件(SQLite 存储设计,含交易记录表 §10); - 技术约束-011:测试/回归脚本禁止在真实数据上执行写操作(独立数据目录); -- 本文件为新领域设计约束,后续数据存储变更以本文件为最终依据。 +- 本文件为新领域设计约束,后续数据存储变更以本文件为最终依据。 \ No newline at end of file diff --git a/docs/04-迭代记录/07-交易记录本地存储SQLite与策略关联/技术实现方案.md b/docs/04-迭代记录/07-交易记录本地存储SQLite与策略关联/技术实现方案.md new file mode 100644 index 0000000..77ed403 --- /dev/null +++ b/docs/04-迭代记录/07-交易记录本地存储SQLite与策略关联/技术实现方案.md @@ -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) | diff --git a/docs/04-迭代记录/07-交易记录本地存储SQLite与策略关联/迭代复盘.md b/docs/04-迭代记录/07-交易记录本地存储SQLite与策略关联/迭代复盘.md new file mode 100644 index 0000000..5bae880 --- /dev/null +++ b/docs/04-迭代记录/07-交易记录本地存储SQLite与策略关联/迭代复盘.md @@ -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)。 \ No newline at end of file diff --git a/docs/04-迭代记录/07-交易记录本地存储SQLite与策略关联/迭代目标.md b/docs/04-迭代记录/07-交易记录本地存储SQLite与策略关联/迭代目标.md new file mode 100644 index 0000000..87ba4c0 --- /dev/null +++ b/docs/04-迭代记录/07-交易记录本地存储SQLite与策略关联/迭代目标.md @@ -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 模式)。 diff --git a/docs/04-迭代记录/07-交易记录本地存储SQLite与策略关联/验收标准.md b/docs/04-迭代记录/07-交易记录本地存储SQLite与策略关联/验收标准.md new file mode 100644 index 0000000..e389e89 --- /dev/null +++ b/docs/04-迭代记录/07-交易记录本地存储SQLite与策略关联/验收标准.md @@ -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 + 技术方案约束新增条目。 diff --git a/docs/04-迭代记录/08-策略持仓行展开关联交易记录/技术实现方案.md b/docs/04-迭代记录/08-策略持仓行展开关联交易记录/技术实现方案.md new file mode 100644 index 0000000..f1d3f63 --- /dev/null +++ b/docs/04-迭代记录/08-策略持仓行展开关联交易记录/技术实现方案.md @@ -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) | diff --git a/docs/04-迭代记录/08-策略持仓行展开关联交易记录/迭代复盘.md b/docs/04-迭代记录/08-策略持仓行展开关联交易记录/迭代复盘.md new file mode 100644 index 0000000..5cf9ceb --- /dev/null +++ b/docs/04-迭代记录/08-策略持仓行展开关联交易记录/迭代复盘.md @@ -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 验证(开盘后确认)。 diff --git a/docs/04-迭代记录/08-策略持仓行展开关联交易记录/迭代目标.md b/docs/04-迭代记录/08-策略持仓行展开关联交易记录/迭代目标.md new file mode 100644 index 0000000..2072ed5 --- /dev/null +++ b/docs/04-迭代记录/08-策略持仓行展开关联交易记录/迭代目标.md @@ -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)验证展开效果。 diff --git a/docs/04-迭代记录/08-策略持仓行展开关联交易记录/验收标准.md b/docs/04-迭代记录/08-策略持仓行展开关联交易记录/验收标准.md new file mode 100644 index 0000000..7029468 --- /dev/null +++ b/docs/04-迭代记录/08-策略持仓行展开关联交易记录/验收标准.md @@ -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)。 diff --git a/docs/05-需求池/已完成/R-009.md b/docs/05-需求池/已完成/R-009.md new file mode 100644 index 0000000..60dbc27 --- /dev/null +++ b/docs/05-需求池/已完成/R-009.md @@ -0,0 +1,146 @@ +# 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 关联方式修正(外键链 + 两表零冗余) + +--- + +# R-009 交易记录本地存储(SQLite)+ 策略关联 + +> 状态:**已定稿**(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);算法推导降级为候选列表来源。 diff --git a/docs/05-需求池/已完成/R-010.md b/docs/05-需求池/已完成/R-010.md new file mode 100644 index 0000000..29f40a4 --- /dev/null +++ b/docs/05-需求池/已完成/R-010.md @@ -0,0 +1,63 @@ +# R-010 策略持仓行展开关联交易记录(Holding → 交易汇总)· 已完成 + +> 归档日期:2026-09-02 | 需求状态:**已完成** +> 原索引:docs/05-需求池/需求池索引.md(主索引保留 R-010 条目,指向本归档) +> 实现迭代:08-策略持仓行展开关联交易记录(老师确认,迭代复盘见 docs/04-迭代记录/08-策略持仓行展开关联交易记录/迭代复盘.md) +> 关联计划:PLAN-009(docs/02-计划/计划-策略持仓行展开关联交易记录.md) + +--- + +# R-010 策略持仓行展开关联交易记录(Holding → 交易汇总) + +> 状态:**已定稿**(2026-09-02,老师确认 Q1-Q3)| 登记日期:2026-09-02 +> 来源:老师指令(2026-09-02) +> 优先级:P1 +> 关联:R-009(交易记录本地存储 + holding_id 关联)、迭代 07 + +## 需求描述 + +策略持仓 tab(如「网格超市」「手动做T」)中,**每个持仓记录(Holding)行可展开**,展开后显示**与该 holding 关联的交易记录**(仅委托汇总,不分笔成交)。 + +- 展开效果类似交易记录 tab 的展开效果; +- 展开内容只显示**委托汇总**(一笔委托一行,聚合成交信息),**不显示分笔成交明细**; +- 关联依据 = trade_orders.holding_id(R-009 已建立:用户手动设置归属时写入 holding_id)。 + +## 现状(代码审查 2026-09-02) + +| 项 | 现状 | +|---|---| +| 策略持仓 tab | StrategyTab.jsx:表格展示策略持仓(代码/名称/现价/策略份额/总持仓/操作),行不可展开 | +| 持仓数据 | strategy-positions 端点返回 QMT 持仓 + shares,**不含 holding_id**(需附加) | +| holding 关联 | trade_orders.holding_id(R-009 手动归属写入),按 holding_id 可精确查询交易 | +| 交易汇总 | trade_orders 单行即委托汇总(order_volume/traded_volume/amount/费用等),天然是汇总 | + +## 待讨论点(Q1-Q3,AI 建议见各条) + +### Q1 持仓行如何带出 holding_id(AI 建议:strategy-positions 附加 holding_id) +- 方案:PositionManager.getStrategyPositions 返回时,查 strategy_holdings 附加 holding_id(同策略同 code 的当前持仓); +- 一票多策略:当前策略下的该 code 只有一个当前持仓(idx_active_holding 约束),可精确匹配; +- 需确认:是否接受服务端附加 holding_id 到 strategy-positions 响应? + +### Q2 查询端点(AI 建议:新增 trades/by-holding 端点) +- 新增端点:按 holding_id 查 trade_orders(返回委托汇总列表,含状态/方向/量/价/金额/时间); +- 复用 SqliteStore 查询(WHERE holding_id=? ORDER BY insert_ts DESC); +- 需确认:端点命名与返回结构? + +### Q3 前端展开 UI(AI 建议:持仓行展开 + 内嵌汇总表) +- 展开效果:类似交易记录 tab(行点击展开/折叠,箭头指示); +- 展开内容:内嵌小表格显示该 holding 的委托汇总(时间/方向/状态/委托量/成交量/均价/金额/费用); +- 懒加载:展开时才查询 trades/by-holding(不展开不请求); +- 需确认:展示列是否与交易记录 tab 的委托汇总一致(可精简)? + +### Q4 成本价 + 最后一笔成交价(2026-09-02 老师补充,已确认) + +- **成本价**:持仓数据源已有 avgPrice(QMT 持仓字段,如博纳影业 6.8393),直接展示; +- **最后一笔成交价**: + - 该 holding 关联交易记录(trades/by-holding)按时间取**最新一笔有实际成交**(tradedVolume > 0)的委托的 tradedPrice(成交价); + - 无关联交易 / 关联委托均未成交 → 默认等于成本价(avgPrice); +- 展示位置:策略持仓 tab 持仓行加「成本价」「最后一笔成交价」两列。 + +## 定稿记录 + +- 2026-09-02 老师确认 Q1-Q3 全部定稿:strategy-positions 附加 holding_id(Q1)+ trades/by-holding 端点(Q2)+ 持仓行展开 UI(懒加载,仅委托汇总)(Q3);**R-010 转已定稿,进入迭代 08 实施**。 +- 2026-09-02 老师补充 Q4(成本价 + 最后一笔成交价)并确认规则(取最新有成交的 tradedPrice,无成交默认成本价)。 \ No newline at end of file diff --git a/docs/05-需求池/需求池索引.md b/docs/05-需求池/需求池索引.md index 3e3937a..2213a77 100644 --- a/docs/05-需求池/需求池索引.md +++ b/docs/05-需求池/需求池索引.md @@ -20,6 +20,8 @@ | R-004 | QMT 连接配置(多配置管理 + 会话头部快捷切换) | 设置页「QMT 连接配置」子 tab:多配置 CRUD(卡片形式)、单选激活(数据源热切换立即生效)、默认标记(启动自动激活)、测试连接(/health 代理)、删除边界处理;会话头部(PTC 模式标签旁)快捷切换 chip。**2026-08-29 定稿(Q1-Q10)并完成实现与验收(7/7 通过),已归档至 已完成/R-004.md**。决策沉淀:产品约束-005/006、技术约束-003(变更)/008/009、UI约束-001/002 | 老师指令(2026-08-28) | P1 | 已定稿 | 2026-08-29 | 03-QMT连接配置 | **已实现(已归档)** | | R-007 | 交易记录接入(订单与成交数据 + 时间段查询) | 交易记录 tab 接入 QMT Bridge 当日委托(/trade/orders)与当日成交(/trade/trades):**单表合并**(委托主行 + 展开成交明细)+ **时间段查询**(今日/本周/本月 + 手动起止)+ 费用计算(佣金费率+最低5元/印花税/过户费,委托级合并计费)。**2026-09-01 定稿并完成(迭代 05 验收通过),已归档至 已完成/R-007.md** | 老师指令(2026-09-01) | P1 | 已定稿 | 2026-09-01 | 05-交易记录接入 | **已实现(已归档)** | | R-008 | 数据存储管理(JSON → SQLite) | 数据存储从 JSON data store 升级为 SQLite(node:sqlite):仅存储引擎替换 + strategies/allocation/market_quotes 三表 + 一次性迁移脚本(启动自动迁移)+ JSON 废弃;策略仍存 DSH settings;不建 trades 表(R-007 时再建)。**2026-09-01 定稿(T-008 转正,D1-D8 确认),2026-09-01 完成(迭代 06 验收通过),已归档至 已完成/R-008.md** | 老师指令(2026-09-01,T-008 转正) | P1 | 已定稿 | 2026-09-01 | 06-数据存储SQLite | **已实现(已归档)** | +| R-009 | 交易记录本地存储(SQLite)+ 策略关联 | 在 SQLite 新增交易记录表(trade_orders + trade_fills,委托/成交两表,**零冗余 strategy_id/holding_id**),QMT 当日交易数据本地持久化(跨日积累成历史库);策略/持仓归属 = 委托时间 join strategy_holdings 生命周期窗口推导(外键链:成交→委托→策略→holding);本地历史查询端点 + 交易记录 tab 策略过滤与历史范围本地展示。**2026-09-01 定稿(Q1-Q8 确认),2026-09-01 完成(迭代 07 验收通过),已归档至 已完成/R-009.md** | 老师指令(2026-09-01) | P1 | 已定稿 | 2026-09-01 | 07-交易记录本地存储SQLite与策略关联 | **已实现(已归档)** | +| R-010 | 策略持仓行展开关联交易记录(Holding → 交易汇总) | 策略持仓 tab 每个持仓行(Holding)可展开,展开显示与该 holding 关联的交易记录(仅委托汇总,不分笔成交);strategy-positions 附加 holding_id + 新增 trades/by-holding 端点 + 持仓行展开 UI + 成本价/最后一笔成交价两列 + 神之一手 tab 隐藏输入框。**2026-09-02 定稿(Q1-Q4 确认),2026-09-02 完成(老师确认),已归档至 已完成/R-010.md** | 老师指令(2026-09-02) | P1 | 已定稿 | 2026-09-02 | 08-策略持仓行展开关联交易记录 | **已实现(已归档)** | ## 渐进明细规划素材 @@ -34,4 +36,4 @@ | T-005 | 通过 ws 长连接做市场数据的实时反映 | WebSocket 长连接实现市场数据(持仓/行情)实时推送与展示;源自迭代 01 复盘。**2026-08-31 转正为 R-005(迭代 04 实施中)**,草稿文件:`草稿/通过ws长连接做市场数据的实时反映.md` | 已转需求 R-005 | | T-006 | 数据池中间层 + 策略表格动态字段配置 | 服务端数据池(数据集中间层:按 code 聚合宽表、多源字段映射、填充器架构)+ 策略持仓表格动态字段配置(列配置×池行渲染)。源自 R003 讨论(2026-08-28)拆分独立,草稿文件:`草稿/数据池中间层与策略表格动态字段配置.md` | 起草 | | T-007 | RSS 订阅管理 | 管理 RSS 订阅源(增删改查)+ 抓取聚合订阅内容,作为资讯/消息来源;支撑 目标-003 市场监控、目标-004 消息管理,与 T-004 同属消息链路。**2026-08-28 老师确认先放草稿**,草稿文件:`草稿/RSS订阅管理.md` | 起草 | -| T-008 | 数据存储管理(JSON → SQLite) | 数据存储从 JSON data store 升级为 SQLite(node:sqlite)数据库 + 数据存储管理能力;**变更既有设计约束**(数据存储设计.md「无需数据库」决策)。**2026-09-01 定稿转正为 R-008 并完成(迭代 06)** | 已转需求 R-008 | +| T-008 | 数据存储管理(JSON → SQLite) | 数据存储从 JSON data store 升级为 SQLite(node:sqlite)数据库 + 数据存储管理能力;**变更既有设计约束**(数据存储设计.md「无需数据库」决策)。**2026-09-01 定稿转正为 R-008 并完成(迭代 06)** | 已转需求 R-008 | \ No newline at end of file