feat: 迭代03-05 交易记录接入、行情实时、QMT连接配置、data store 标准化

- R-004 QMT连接配置:多配置管理 + 会话头部快捷切换(03-QMT连接配置)
- R-005 WS盘中价格实时更新:服务端中转+缓存+前端轮询(04-WS盘中价格实时更新)
- R-006 策略数据 JSON data store 标准化(store.json/market.json/schema.json)
- R-007 交易记录接入:当日订单+成交单表合并(委托主行+展开成交明细)、时间段查询(今日/本周/本月+手动起止)、费用计算(佣金费率万m_dCommission+最低5元、印花税卖出万5、过户费双向万0.1)
- 架构治理:api 按领域拆分、component 目录、QmtHealthMonitor
This commit is contained in:
2026-09-01 13:21:49 +08:00
parent 5a723d514e
commit bb3f8bd28d
57 changed files with 5244 additions and 397 deletions
+115
View File
@@ -0,0 +1,115 @@
# 计划:QMT 连接配置(多配置管理 + 会话头部快捷切换)(阶段航点)
> 编号:PLAN-003 | 粒度:阶段航点(大粒度) | 创建:2026-08-29 状态:**已完成(2026-08-29 验收通过,R-004 已归档)**
> 派生自终极目标:目标-001(DSH 插件形态)、目标-008(真实交易系统接入·统一数据源抽象)
> 依据需求:**R-004(已定稿,2026-08-29)** —— 符合入范围门槛(已定稿方可进入计划)
> 设计约束:产品约束-005/006、技术约束-003/008/009、UI约束-001/002、技术约束-004/005/006/007(沿用)
## 目标
为插件增加 QMT Bridge 连接的多配置管理能力:设置页「QMT 连接配置」子 tab 管理
多个连接配置(CRUD / 激活 / 默认 / 测试连接),会话头部(PTC 模式标签旁)常驻
快捷切换控件;激活配置**立即切换**数据源地址(无需重启 DSH),重启后回到默认配置。
## 范围
**做**(对应 R-004 定稿范围,决策编号 Q1-Q10):
1. **设置页「QMT 连接配置」子 tab**(产品约束-005、UI约束-002):
- 配置 CRUD:新增 / 编辑 / 重命名 / 删除(字段:名称 + HTTP 地址);
- 单选激活(一次只能激活一个),激活立即生效;
- 默认标记:启动时自动激活默认配置,重启后回到默认;
- 测试连接:手动触发,请求 `/health` 返回可达性与延迟;
- 删除边界:删激活配置→自动切默认;删默认→默认标记转列表第一条并激活之;禁删最后一条;
2. **会话头部快捷切换**(产品约束-006、UI约束-001、技术约束-009):
- slot `conversation.session.header.actions` 注册紧凑下拉 chip`QMT: <激活配置名> ▾`
- 菜单列出全部配置(激活项勾选),点选即激活 + toast 反馈;
- 头部仅切换,管理仍在设置页;
3. **生效机制**(技术约束-008):
- 激活/修改后立即切换(更新数据源实例 baseUrl,按请求读取已核实);
- 启动时自动激活默认配置;列表为空时回退 cordis 注入的 qmtBaseUrl 兜底;
4. **存储**(技术约束-008):复用 one-divine-lot settings namespace,新增 qmtConnectionslist + activeId + defaultId)。
**不做**(本期):
- 认证/多账户支持(QMT Bridge 当前无鉴权);
- 连接状态监控与断线重连(后续);
- 超时(timeoutMs)配置化 —— 保持代码默认 15000ms(Q5);
- 宿主配置(cordis.patch.yml qmtBaseUrl)自动迁移 —— 首次使用手动录入(Q4);
- 激活前强制连通性校验 —— 测试连接为手动操作(Q3);
- 头部菜单管理能力(增删改/测试连接/设默认)—— 留在设置页(Q10)。
## 程序结构(改造后)
```
src/
├── index.js # 服务端入口(改)
│ ├── 数据源创建后注册为可寻址组件;启动时按「默认配置」初始化激活地址(列表空→cordis qmtBaseUrl 兜底)
│ └── inject + ['settings', 'webServer'](不变)
├── settings.js # 设置管理(改)
│ ├── schema 增加 qmtConnections: { list: [{id,name,baseUrl,order}], activeId, defaultId }
│ ├── 新增 QMT 连接配置 CRUD(与策略 CRUD 同模式:列表读写 + 整表 update)
│ │ ├── getQmtConnections(scope) # 读列表(按 order 排序)
│ │ ├── addQmtConnection(scope, {name, baseUrl})
│ │ ├── renameQmtConnection(scope, id, {name, baseUrl})
│ │ ├── removeQmtConnection(scope, id) # 删除边界处理(产品约束-005)
│ │ ├── activateQmtConnection(scope, id) # 激活(写 activeId
│ │ ├── setDefaultQmtConnection(scope, id) # 设默认(写 defaultId
│ │ └── resolveStartupConnection(scope, config) # 启动激活地址解析(默认→激活→cordis 兜底)
│ └── generateQmtConnectionId(name, existingIds) # slug id(复用拼音映射与去重逻辑)
├── data-source/qmt-bridge-rest.js # 数据源(改:小改)
│ └── 新增 setBaseUrl(url) # 运行期更新实例地址(技术约束-008:按请求读取,改字段即生效)
timeoutMs 不暴露 setter —— Q5
├── api.js # 服务端 HTTP API(改)
│ ├── METHODS 增加:qmt-connections / qmt-connections/add
│ │ / qmt-connections/update / qmt-connections/remove
│ │ / qmt-connections/activate / qmt-connections/set-default
│ │ / qmt-connections/test(服务端代理 GET {baseUrl}/health,返回 {ok, latencyMs, status}
│ └── 编排:activate/update/remove 成功后调用 dataSource.setBaseUrl(激活地址)
├── client/
│ ├── index.js # 客户端入口(改)
│ │ └── slots.register({ name: 'conversation.session.header.actions', id: 'odl-qmt-switch', order: -9 }, QmtConnectionChip)
│ └── views/
│ ├── QmtConnectionChip.jsx # 新增:头部快捷切换控件(下拉 chip + 菜单 + toast 反馈)
│ └── SettingsSection.jsx # 改:新增第三个子 tab「QMT 连接配置」
│ └── 内部新组件 QmtConnectionsSettings:配置列表 + 行内操作 + 新增/编辑表单 + 删除确认弹窗
└── cordis.patch.yml # 不改:qmtBaseUrl 保留为兜底种子(Q4 不迁移)
```
## 改动说明(按文件)
| 文件 | 改动 | 依据 |
|---|---|---|
| src/settings.js | schema 增 qmtConnections;新增 7 个 CRUD/激活/默认/启动解析函数 | 技术约束-008、产品约束-005 |
| src/data-source/qmt-bridge-rest.js | 新增 setBaseUrl(url);其余不动 | 技术约束-008(按请求读取地址) |
| src/api.js | 新增 7 个 qmt-connections 端点;activate/update/remove 编排 setBaseUrl | 技术约束-004webServer 自开路由)、-008 |
| src/index.js | 启动时解析并设置激活地址;数据源实例传递给 api 注册 | 技术约束-008 |
| src/client/index.js | 注册会话头部 slotodl-qmt-switchorder=-9 | 技术约束-009 |
| src/client/views/QmtConnectionChip.jsx | 新增头部切换控件(复用 ConnectionProvider/useRpc + Toast | 产品约束-006、UI约束-001 |
| src/client/views/SettingsSection.jsx | 新增「QMT 连接配置」子 tab 与配置管理界面 | 产品约束-005、UI约束-002 |
| cordis.patch.yml | 不改动 | Q4(不迁移) |
## 实现步骤(建议顺序)
1. **服务端数据层**settings.js 增 schema 与 CRUD/激活/默认/启动解析函数(纯函数,先行可测);
2. **数据源热切换**qmt-bridge-rest.js 增 setBaseUrlindex.js 启动时 resolveStartupConnection 并设置初始激活地址;把 dataSource 传给 registerApi
3. **服务端 API**api.js 增 7 个端点 + 激活编排 setBaseUrl;本地 curl 验证 CRUD/激活/测试连接;
4. **设置页 UI**SettingsSection.jsx 增「QMT 连接配置」子 tab(列表/表单/确认弹窗,复用 ConfirmDialog/Toast);走通设置页全流程;
5. **头部快捷切换**client/index.js 注册头部 slot;新增 QmtConnectionChip.jsx(读列表/激活/失败反馈);验证与 PTC 标签并排显示与切换生效;
6. **构建安装**:服务端+客户端构建,dsh plugin add 安装,重载页面;
7. **验收**:对照下节验收标准逐条核验,记录迭代 03。
## 验收标准
1. 设置页出现「QMT 连接配置」子 tab,配置 CRUD 完整可用(名称 + HTTP 地址);
2. 激活某配置后,数据源立即切到该地址(无需重启 DSH)——通过切换后刷新持仓/测试连接验证目标端点随激活配置变化;
3. 默认配置行为正确:启动自动激活默认;重启后回到默认(即使运行中切过其他配置);列表为空时按 cordis qmtBaseUrl 兜底且页面/数据源可用;
4. 测试连接返回可达性 + 延迟;激活不强制先测试;
5. 删除边界符合产品约束-005:删激活→自动切默认;删默认→默认转移列表第一条并激活;删最后一条被禁止;
6. 会话头部 PTC 标签旁出现 `QMT: <激活配置名>` chip,点选菜单项即激活并 toast 反馈;头部仅切换(无管理入口);
7. 现有功能(分仓/策略/设置现有子 tab)不回归。
## 验收方法
- 建第二个配置指向任一可达地址,激活后通过「测试连接」与持仓加载观察目标地址变化;
- 重启 DSH(或重载插件)验证回到默认配置;
- 删除激活/默认配置验证自动切换与默认转移;删至最后一条验证禁止提示;
- 头部 chip 切换后观察 toast 与数据源目标变化(配合测试连接)。
@@ -0,0 +1,51 @@
# 计划:WS 盘中价格实时更新(前端直连 QMT Bridge WebSocket)(阶段航点)
> 编号:PLAN-004 | 粒度:阶段航点(大粒度) | 创建:2026-08-31 状态:**已完成(2026-09-01 迭代 04 验收通过,R-005 已实现)**
> 派生自终极目标:目标-001(DSH 插件形态)、目标-008(真实交易系统接入·统一数据源抽象)
> 依据需求:**R-005(已定稿,2026-08-31)** —— 符合入范围门槛(已定稿方可进入计划)
> 设计约束:技术约束-001/003/008(沿用)、技术约束-010(新增:前端直连 WS)、产品约束-007(新增:现价列展示)
## 目标
在 3 个监控表格(全部持仓 / 手动做T / 网格超市)中实现**盘中价格实时更新**:前端直连 QMT Bridge WebSocket 订阅全市场 tick,事件驱动更新关注股票的现价(lastPrice),红涨绿跌着色 + 变化高亮。
## 范围
**做**
1. **WS 行情客户端**(前端):连接 `ws://<激活配置host>:8610/ws`,订阅 `whole` 全市场 tick;维护 code→lastPrice 实时价格 Map
2. **订阅生命周期**:订阅前 REST 拉一次 `/data/tick` 快照补初值(WS 只推增量);连接断开静默重连 + 状态提示;切换激活 QMT 配置时 WS 随之切换;
3. **关注列表**:3 表格自动汇总出现的 code(全部持仓 + 手动做T + 网格超市),作为过滤集合;
4. **现价展示**:3 表格新增「现价」列(lastPrice),红涨绿跌着色(对比昨收 lastClose),价格变化轻微高亮;
5. **事件驱动**:收到 WS 推送 → 更新价格 Map → 通知订阅方(React state/事件)→ 表格行重渲染现价。
**不做**(本期):
- 市值/盈亏/五档实时展示;
- 插件服务端中转 WS
- 关注列表手动配置;
- 断线重连 UI 引导(仅静默重连 + 状态提示)。
## 程序结构(改造后)
```
src/
├── index.js # 服务端入口(小改):新增 API 暴露激活连接 host(供前端建 WS)
├── api.js # 服务端 API(改):新增 qmt-connections/active-host 端点(返回 { host, baseUrl, wsUrl }
├── client/
│ ├── index.js # 客户端入口(改):注册行情 Provider,包装 3 个表格
│ ├── market/
│ │ └── MarketDataProvider.jsx # 新增:WS 行情客户端 + 价格 Map + 订阅/重连/切换 + Context 下发
│ └── views/
│ ├── AllPositionsTab.jsx # 改:新增「现价」列 + 涨跌色 + 高亮
│ ├── StrategyTab.jsx # 改:新增「现价」列 + 涨跌色 + 高亮
│ └── PriceCell.jsx # 新增:现价单元格组件(格式化 + 涨跌色 + 高亮)
└── cordis.patch.yml # 不改(QMT 配置已在 settings
```
## 实现步骤(建议顺序)
1. **文档骨架**R-005 转正 + PLAN-004 + 迭代 04 子目录 + 迭代说明(本步);
2. **服务端 API**:新增 `qmt-connections/active-host` 返回激活配置 host(前端据此建 WS);
3. **WS 行情客户端**MarketDataProvider(连接/订阅/价格 Map/重连/配置切换);
4. **现价列 UI**PriceCell + 3 表格接入;
5. **构建安装**dsh plugin add,重载页面;
6. **验收**:对照验收标准逐条核验,记录迭代 04。
@@ -0,0 +1,63 @@
# 计划:交易记录接入(订单与成交数据 + 日期导航)(阶段航点)
> 编号:PLAN-006 | 粒度:阶段航点(大粒度) | 创建:2026-09-01 状态:**进行中**
> 派生自终极目标:目标-006(交易复盘)、目标-003(按策略监控市场)
> 依据需求:**R-007(范围已定稿,2026-09-01)** —— 符合入范围门槛
> 设计约束:技术约束-001/003/004/010(沿用)、产品约束-007 方向(着色习惯,红买绿卖为交易方向新语义)
## 目标
在「交易记录」tab 接入 QMT Bridge 当日订单(/trade/orders)与成交(/trade/trades)数据:**单表合并展示**(委托主行 + 按 m_strOrderSysID 展开成交明细)+ **基于交易日历的日期导航**(当日默认选中,历史接口占位,为老师完善 QMT Bridge 历史成交接口预留)。
## 范围
**做**
1. **数据源接入**QmtBridgeRestDataSource 新增 getOrders() / getTrades() / getTradingDates()(语义化映射,核心字段见 R-007 2.1/2.2);
2. **服务端 API**:新增 api/trades.jsorders / trades / trading-dates 端点),纳入领域分发;
3. **交易记录 tab 落地**:占位 tab 替换为 TradeRecordsTab——**单表合并**:委托主行(聚合成交)+ 展开/折叠成交明细;
4. **日期导航**DateNav 基于 /data/calendar/trading_dates,前后切换交易日,当日默认选中,历史日期展示「接口开发中」占位;
5. **排序**:委托主行默认降序(最新在前),支持表头点击手动排序;
6. **数据刷新**:服务端中转 + 前端轮询(3-5s,复用 market-snapshot 模式)。
**不做**
- 历史(非当日)交易数据查询实现(老师完善 QMT Bridge 接口后接入);
- 交易数据本地持久化(Q5 本期不做);
- 按策略过滤交易(Q10 后续迭代);
- 下单/撤单等交易操作(目标-007 控制权另议)。
## 程序结构(改造后)
```
src/
├── component/
│ └── QmtBridgeRestDataSource.js # 改:+getOrders/getTrades/getTradingDates
├── api/
│ ├── trades.js # 新增:orders/trades/trading-dates 端点
│ └── index.js # 改:合并 TRADE_METHODS
├── client/
│ ├── views/
│ │ ├── TradeRecordsTab.jsx # 新增:交易记录 tab(日期导航 + 单表合并)
│ │ ├── OrderRow.jsx # 新增:委托主行(聚合成交 + 展开)
│ │ ├── TradeDetailRows.jsx # 新增:展开成交明细行
│ │ └── DateNav.jsx # 新增:交易日历日期导航
│ └── index.js # 改:tradeRecords tab 渲染 TradeRecordsTab
```
## 实现步骤(建议顺序)
1. **文档骨架**PLAN-006 + 迭代 05 子目录(本步);
2. **数据源**getOrders/getTrades/getTradingDates(语义化映射 + 方向判定);
3. **服务端 API**api/trades.js 三端点 + 领域分发注册;
4. **客户端合并逻辑**fetch 两接口 → 按 m_strOrderSysID 分组 → 聚合;
5. **UI**DateNav + OrderRow + TradeDetailRows + TradeRecordsTab;排序(默认降序 + 表头点击);
6. **构建安装**pnpm run build + 页面刷新(客户端)或宿主重启(服务端);
7. **验收**:对照迭代 05 验收标准逐条核验,记录迭代复盘。
## 验收要点
- 交易记录 tab 显示当日委托(单表合并,展开可见每笔成交);
- 委托主行:时间/代码/名称/方向/状态/委托量/成交量/委托价/成交均价/成交额/手续费;
- 方向红买绿卖;状态中文映射;未成交列显示 —;
- 日期导航基于交易日历,前后切换,当日默认,历史占位;
- 排序默认降序 + 表头手动排序;
- 现有功能(持仓/策略/行情/连接配置)不回归。
@@ -0,0 +1,40 @@
# 计划:策略数据 JSON data store schema 标准化(阶段航点)
> 编号:PLAN-005 | 粒度:阶段航点(大粒度) | 创建:2026-08-31 状态:**已完成(2026-09-01 迭代 04 验收通过,R-006 已实现)**
> 派生自终极目标:目标-001(DSH 插件形态)
> 依据需求:**R-006(已定稿,2026-08-31** —— 符合入范围门槛
> 设计约束:技术约束-011(测试数据隔离)、技术约束-012(新增:JSON data store schema
## 目标
将策略份额分配数据(现 allocations.jsoncode 为中心)标准化为 **JSON data store schema**
- store.schema.json:描述数据集(dataset)的标准字段(类 JSON Schema);
- store.json:实际数据文件,策略为中心(strategies[].dataset = [{code, shares}]);
- 策略定义仍留 DSH settings(设置页交互不变);
- 旧 allocations.json 启动时自动迁移(保留备份)。
## 范围
**做**
1. 新增 DataStore 模块(src/component/DataStore.js):
- 定义 store.schema.json 与 store.json 结构;
- 读写 store.jsonschema 校验 + 原子写);
- 迁移:启动时检测旧 allocations.json → 转换为 store.json(code 为中心 → 策略为中心),保留 allocations.json.bak
- 数据集 CRUDgetDataset(strategyId) / setDataset(strategyId, items) / removeDataset(strategyId)。
2. PositionManager / api 改造:
- 份额读写改走 DataStore(替代 AllocationStorage);
- strategies/remove 联动删 store dataset(同现状联清份额模式);
- 现有端点(add-shares/remove-shares/strategy-positions/summary 等)行为不变。
3. 删除/归档 AllocationStorage(迁移完成后)。
**不做**
- 策略定义迁移(保留 settings);
- 不引入数据库(数据量少,JSON 足够)。
## 实现步骤
1. DataStore 模块(schema + 读写 + 迁移 + dataset CRUD);
2. PositionManager 改接 DataStore
3. api 编排(strategies/remove 联动删 dataset);
4. 构建 + 测试隔离模式验证迁移;
5. 验收 + 记录迭代。