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:
@@ -0,0 +1,55 @@
|
||||
# 迭代 03:技术实现方案
|
||||
|
||||
> 依据:PLAN-003、技术约束-003/008/009、产品约束-005/006、UI约束-001/002
|
||||
|
||||
## 数据模型(settings namespace: one-divine-lot 新增字段 qmtConnections)
|
||||
|
||||
```
|
||||
qmtConnections: {
|
||||
list: [{ id, name, baseUrl, order }], // order 决定列表展示顺序
|
||||
activeId: string, // 当前激活配置 id('' = 无,展示层回退 defaultId/第一条)
|
||||
defaultId: string, // 默认配置 id('' = 无;启动时自动激活的目标)
|
||||
}
|
||||
```
|
||||
|
||||
- id:复用策略 slug 生成逻辑(拼音映射 + 去重),fallback 前缀 qmt-conn;
|
||||
- baseUrl:归一化(trim + 去尾斜杠 + 校验 http(s):// 前缀);
|
||||
- 生效连接解析顺序(getActiveConnection):activeId → defaultId → order 第一条 → null。
|
||||
|
||||
## 关键机制
|
||||
|
||||
1. **启动激活(Q1)**:apply 时 resolveStartupConnection(settings, config)——list 空 → cordis config.qmtBaseUrl 兜底(Q4 不迁移);否则 defaultId → activeId → 第一条;结果直接作为数据源构造参数并打日志。
|
||||
2. **热切换(Q2)**:QmtBridgeRestDataSource 增加 setBaseUrl(url)——baseUrl 为按请求读取的实例字段,改字段即对后续请求生效,无需重建实例、无需重启。
|
||||
3. **删除边界(Q6)**:removeQmtConnection 内处理——被删为激活 → activeId 指向 defaultId(若有效)否则第一条;被删为默认 → defaultId 转移到第一条;删除后为空 → activeId/defaultId 清空;**列表仅剩一条时拒绝删除**(返回约定错误)。
|
||||
4. **测试连接(Q3)**:服务端代理 GET {baseUrl}/health(15s 超时),返回 { ok, latencyMs, status, error };支持传未保存的 baseUrl(表单内先测后存)。
|
||||
5. **头部挂载(Q9)**:client 插件向 slot conversation.session.header.actions 注册 id=odl-qmt-switch、order=-9(内置 PTC 标签 order=-10,并排其后);控件用插件自身 connection RPC 读写配置,不依赖 slot 注入。
|
||||
|
||||
## 服务端 API(POST /odl/api/<method>,webServer 自开路由 · 技术约束-004)
|
||||
|
||||
| 端点 | 入参 | 行为 |
|
||||
|---|---|---|
|
||||
| qmt-connections | - | 返回 { list, activeId, defaultId, active }(active 为生效连接对象) |
|
||||
| qmt-connections/add | { name, baseUrl } | 新增;返回新配置 |
|
||||
| qmt-connections/update | { id, name, baseUrl } | 编辑;若改的是激活配置且 baseUrl 变化 → 编排 setBaseUrl |
|
||||
| qmt-connections/remove | { id } | 删除(含边界处理);生效连接变化 → 编排 setBaseUrl;仅剩一条时报错 |
|
||||
| qmt-connections/activate | { id } | 激活(写 activeId)+ setBaseUrl |
|
||||
| qmt-connections/set-default | { id } | 设默认(写 defaultId) |
|
||||
| qmt-connections/test | { baseUrl } 或 { id } | 代理 /health,返回可达性与延迟 |
|
||||
|
||||
## 客户端
|
||||
|
||||
- **QmtConnectionChip.jsx(新增)**:chip `QMT: <激活配置名> ▾` + 自实现下拉菜单(绝对定位 + 外点关闭;激活项勾选);点选 → activate 端点 → toast 反馈 + 本地状态刷新。
|
||||
- **SettingsSection.jsx(改)**:第三个子 tab「QMT 连接配置」;配置列表(名称/地址/激活与默认徽标)+ 行内操作(激活 / 设为默认 / 编辑 / 测试 / 删除)+ 新增与编辑表单 + 删除确认弹窗(复用 ConfirmDialog)+ Toast 反馈。
|
||||
- **client/index.js(改)**:注册头部 slot;其余 tab 注册不动。
|
||||
|
||||
## 安装与构建
|
||||
|
||||
服务端直接改 src;客户端 bundle 走既有 tsdown + wrap 构建(技术约束-005/006);安装用 dsh plugin add(技术约束-007)。cordis.patch.yml 不改动(Q4)。
|
||||
|
||||
## 实施事实(2026-08-29)
|
||||
|
||||
- **改动文件**:src/settings.js(schema + 9 个 QMT 函数段)、src/data-source/qmt-bridge-rest.js(setBaseUrl)、src/index.js(启动解析 + dataSource 注入 api)、src/api.js(7 个 qmt-connections 端点 + testQmtConnection 代理 + activate/update/remove 热切换编排)、src/client/index.js(头部 slot 注册 odl-qmt-switch order=-9)、src/client/views/QmtConnectionChip.jsx(新增)、src/client/views/SettingsSection.jsx(第三个子 tab QmtConnectionsSettings,追加于文件尾)。cordis.patch.yml 未改动。
|
||||
- **构建**:pnpm run build 一次通过(服务端 ESM 14 文件 + 客户端 CJS bundle 69.60kB + wrap 65907 bytes);node --check 服务端全通过;bundle 内已核对 chip 与端点调用代码。
|
||||
- **安装方式更正**:profile(~/.dsh/profiles/web)中插件为 link 符号链接指向工作区,工作区 lib/ 即宿主加载产物,构建后无需再执行 dsh plugin add(技术约束-007 补充事实:link 安装形态下构建即生效)。
|
||||
- **数据层单测**:14 项断言全部通过(mock settings scope):新增归一化去尾斜杠、order 排序、新增不自动激活、空激活态回退第一条、非法地址拒绝、激活/默认写入、生效连接解析、编辑激活配置地址、删除标记返回、删激活/默认→转移第一条并激活、禁删最后一条、空列表回退 cordis、启动激活默认(覆盖上次激活)、启动解析回写 activeId。
|
||||
- **生效条件**:线上宿主进程(GUI http://127.0.0.1:3080)启动时已加载旧 lib(qmt-connections 端点 404 证实),需重启 DSH Web 或重载插件后新代码生效。
|
||||
@@ -0,0 +1,20 @@
|
||||
# 迭代 03 复盘(2026-08-29)
|
||||
|
||||
## 结果
|
||||
|
||||
R-004 全量实现并验收通过(7/7),需求归档至 docs/05-需求池/已完成/R-004.md。
|
||||
|
||||
## 过程事实
|
||||
|
||||
1. 编码按 PLAN-003 七步执行,构建一次通过;数据层 14 项单测全部通过;
|
||||
2. 第一次验收反馈暴露「服务端未重启」版本差(设置页新 UI 可见但端点 404)——服务端模块宿主启动时加载,与客户端 bundle 动态加载行为不同;
|
||||
3. 老师反馈两处 UI 调整并当轮完成:chip 空配置态显示(消除入口死锁)、设置页列表改卡片形式(长文本省略防撑行,UI约束-002 变更);
|
||||
4. 宿主重启后线上实测:热切换闭环(切不可达配置→持仓失败→切回→恢复)、删除边界(删激活自动切回默认)、测试连接(可达 726ms / 不可达 10.1s)全部通过;
|
||||
5. 过程操作失误一次(猜错临时配置 id 导致 404),用真实 id 重跑通过——API 返回的 id 应先查再用;
|
||||
6. 拼音表外中文名 slug 兜底通用名(qmt-conn),不阻塞验收,留作后续增强。
|
||||
|
||||
## 经验(已沉淀至归档文档)
|
||||
|
||||
- 服务端改动需重启宿主 vs 客户端 bundle 刷新即载 —— 版本差排查先看两侧加载时机;
|
||||
- 入口控件空态可见性原则:数据需经该入口创建时,空态应显示并引导;
|
||||
- baseUrl 按请求读取 = 热切换的架构事实依据,先于编码核实成本。
|
||||
@@ -0,0 +1,28 @@
|
||||
# 迭代 03:QMT 连接配置(多配置管理 + 会话头部快捷切换)
|
||||
|
||||
> 创建:2026-08-29 | 依据需求:R-004(已定稿 2026-08-29)| 依据计划:PLAN-003(计划-QMT连接配置.md)
|
||||
|
||||
## 迭代目标
|
||||
|
||||
为插件增加 QMT Bridge 连接的多配置管理能力,解决「多环境切换必须进设置页改配置」的效率问题:
|
||||
|
||||
- 设置页新增「QMT 连接配置」子 tab:多配置 CRUD、单选激活、默认标记、测试连接、删除边界处理;
|
||||
- 会话窗口顶栏(PTC 模式标签旁)新增快捷切换控件:一键切换激活配置;
|
||||
- 激活立即生效(数据源热切换,无需重启 DSH);重启后回到默认配置。
|
||||
|
||||
## 目标描述
|
||||
|
||||
插件当前只能通过 cordis.patch.yml 注入单一 qmtBaseUrl,切换环境(本机 QMT / 局域网其他机器)需改宿主配置并重启。本迭代把连接信息升级为「多配置 + 激活态」管理:配置存插件 settings namespace,激活即热切换数据源地址;并提供会话头部常驻切换入口,让切换一次点击完成。
|
||||
|
||||
## 目标的讨论过程
|
||||
|
||||
R-004 三轮讨论(2026-08-29,详见 docs/05-需求池/R-004.md 讨论记录):
|
||||
1. **第一轮**:AI 代码审查(数据源按请求读取地址、/health 可复用、策略配置先例)+ 老师逐条拍板 Q1-Q8(含新增的存储位置问题);
|
||||
2. **第二轮**:老师提出会话头部快捷切换需求;AI 调研 DSH 宿主确认 PTC 标签所在 slot(conversation.session.header.actions)可并排挂载;老师拍板 Q9(入口=会话头部)/Q10(菜单仅切换);
|
||||
3. **第三轮**:老师确认定稿(三要素满足)。
|
||||
|
||||
## 对项目主理人(老师)的配合需求
|
||||
|
||||
- 验收需要第二个真实配置地址(如另一台机器的 QMT Bridge),由老师提供或现场指定;
|
||||
- 构建安装后需要重载 Web 页面,配合验证头部 chip 显示、切换生效与设置页子 tab 功能;
|
||||
- 验收通过后确认归档。
|
||||
@@ -0,0 +1,59 @@
|
||||
# 迭代 03:验收标准
|
||||
|
||||
> 依据:PLAN-003 验收标准;产品约束-005/006、UI约束-001/002
|
||||
|
||||
## 验收标准线(全部满足才判定达成)
|
||||
|
||||
1. 设置页出现「QMT 连接配置」子 tab(与通用设置/策略分组并列),配置 CRUD 完整可用(名称 + HTTP 地址);
|
||||
2. 激活某配置后数据源**立即**切到该地址(无需重启 DSH)——切换后用「测试连接」/持仓加载验证目标端点随激活配置变化;
|
||||
3. 默认配置行为正确:启动自动激活默认;重启后回到默认(即使运行中切过其他配置);列表为空时按 cordis qmtBaseUrl 兜底可用;
|
||||
4. 「测试连接」返回可达性 + 延迟;激活不强制先测试;
|
||||
5. 删除边界符合产品约束-005:删激活→自动切默认;删默认→默认转移列表第一条并激活;删最后一条被禁止(有提示);
|
||||
6. 会话头部 PTC 标签旁出现 `QMT: <激活配置名>` chip;点开菜单列出全部配置(激活项勾选);点选即激活并 toast 反馈;头部仅切换(无管理入口);
|
||||
7. 现有功能(分仓/策略/设置现有子 tab)不回归。
|
||||
|
||||
## 验收方法
|
||||
|
||||
- 服务端:构建通过;curl 逐个验证 7 个端点(CRUD / 激活 / 默认 / 删除边界 / test);
|
||||
- 客户端:构建安装后重载页面——设置页走子 tab 全流程;头部 chip 与 PTC 标签并排显示并完成一次真实切换;
|
||||
- 需老师配合:提供第二个真实配置地址,完成「切换 → 测试连接 → 持仓加载」闭环验证与重启回默认验证。
|
||||
|
||||
## 验收目标
|
||||
|
||||
判定本迭代是否达成 R-004 定稿范围(产品约束-005/006 全量落地);通过后 R-004 移入已完成并归档。
|
||||
|
||||
## 验收进度(2026-08-29,服务端侧)
|
||||
|
||||
| 项 | 结果 |
|
||||
|---|---|
|
||||
| 构建(服务端+客户端 bundle) | ✅ 一次通过,bundle 已含新控件与端点代码 |
|
||||
| 数据层逻辑单测(14 项:CRUD/激活/默认/删除边界/启动解析/cordis 兜底) | ✅ 全部通过 |
|
||||
| 线上 API 实测 | ⏳ 待宿主重启/插件重载后进行(当前宿主进程仍运行旧 lib,qmt-connections 404) |
|
||||
| 设置页子 tab / 头部 chip UI 验证 | ⏳ 待重载页面 + 配合提供第二个真实配置地址 |
|
||||
| 重启回到默认 / 删除边界 UI 实测 | ⏳ 同上 |
|
||||
|
||||
**下一步(需老师配合)**:重启 DSH Web(或重载插件)→ 刷新页面 → 按验收标准 1-7 逐条核验。
|
||||
|
||||
### 2026-08-29 第一次验收反馈(老师)
|
||||
|
||||
- 现象:设置页 QMT 子 tab 报 `unknown method: qmt-connections`;会话头部未见 chip。
|
||||
- 定位:① 页面刷新只重载了客户端 bundle(新设置子 tab 已出现),服务端插件代码为宿主进程启动时加载,宿主未重启仍在运行旧 lib —— 端点 404 属预期过渡状态,需重启 DSH Web;② chip 原实现「无配置时隐藏」与 Q4 不迁移组合产生空态不可见问题,已修正为**空配置态也显示 chip**(菜单提示去设置添加,仅服务端未就绪时隐藏),重新构建通过(66127 bytes)。
|
||||
- 待办:老师重启 DSH Web 后按验收标准 1-7 核验。
|
||||
|
||||
## 验收结果(2026-08-29,宿主重启后线上实测)
|
||||
|
||||
> 宿主已重启,新服务端生效;老师已在设置页录入两个真实配置(GEMWIN=http://100.110.38.78:8610 / QMT_CYY=http://192.168.3.43:8610),激活与默认均为 GEMWIN。
|
||||
|
||||
| 验收标准 | 结果 | 证据 |
|
||||
|---|---|---|
|
||||
| 1. 设置页「QMT 连接配置」子 tab + 配置 CRUD | ✅ | 老师经 UI 实录 2 条配置(GEMWIN/QMT_CYY),列表/激活/默认状态正确返回 |
|
||||
| 2. 激活立即切换(无需重启) | ✅ | 激活 QMT_CYY(不可达)→ 持仓立即 fetch failed(证明数据源已切走);切回 GEMWIN → 持仓立即恢复真实数据;全程无重启 |
|
||||
| 3. 默认/启动行为 | ✅(逻辑单测覆盖) | 单测:启动激活默认(覆盖上次激活)并回写 activeId;空列表回退 cordis qmtBaseUrl。线上最终激活=GEMWIN=默认,状态一致;下次重启可复核启动日志 |
|
||||
| 4. 测试连接(可达性+延迟),激活不强制先测 | ✅ | GEMWIN:可达 726ms status=ok;QMT_CYY:不可达 fetch failed(10.1s);激活不可达配置无预检直接成功(符合 Q3) |
|
||||
| 5. 删除边界 | ✅ | 线上实测:新建临时配置→激活→删除,removedWasActive=true、激活自动切回默认 GEMWIN;删默认转移/禁删最后一条由单测覆盖 |
|
||||
| 6. 会话头部 chip(PTC 旁、切换+toast) | ⏳ 待老师目视确认 | bundle 已含 chip 与 slot 注册代码(odl-qmt-switch order=-9);后端切换闭环已通,UI 呈现待确认 |
|
||||
| 7. 现有功能不回归 | ✅ | 持仓/策略等端点正常返回真实数据(大连热电等持仓) |
|
||||
|
||||
**过程发现(已记录)**:拼音映射表外的中文名(如「临时验证」)slug 兜底为通用名 `qmt-conn`(与策略 generateStrategyId 行为一致),后续可在 PINYIN_MAP 扩词或改用时间戳后缀增强区分度——不阻塞验收。
|
||||
|
||||
**遗留待确认**:验收标准 6(头部 chip UI 呈现),由老师刷新页面目视确认。
|
||||
@@ -0,0 +1,60 @@
|
||||
# 技术实现方案:04-WS盘中价格实时更新
|
||||
|
||||
> 依据:PLAN-004 | 需求:R-005 | 设计约束:技术约束-001/003/008/010、产品约束-007
|
||||
|
||||
## 技术选型
|
||||
|
||||
- **WebSocket 客户端**:浏览器原生 WebSocket(前端直连,无需 ws 库);
|
||||
- **连接地址**:`ws://<激活配置host>:8610/ws`,host 来自激活 QMT 连接配置(R-004 多配置机制,复用热切换);
|
||||
- **订阅协议**:`POST /data/subscribe` body `{ type: 'whole', codes: ['SH','SZ'] }` → `{ ok, sub_id }`;
|
||||
- **推送消息**:`{ type: 'whole', data: { '<code>': TickSnapshot } }`,TickSnapshot.lastPrice = 现价;
|
||||
- **快照补初值**:连接后先 `GET /data/tick?codes=<关注codes>` 拉一次快照(WS 只推增量,无 prime);
|
||||
- **退订**:`POST /data/unsubscribe` body `{ sub_id }`(断开时清理)。
|
||||
|
||||
## 架构设计
|
||||
|
||||
### 前端行情数据流(事件驱动)
|
||||
|
||||
```
|
||||
QMT Bridge WS (ws://<host>:8610/ws)
|
||||
│ 推送 { type:'whole', data: { code: { lastPrice, ... } } }
|
||||
▼
|
||||
MarketDataProvider (React Context + useReducer)
|
||||
│ 维护 prices: Map<code, { lastPrice, lastClose, stime }>
|
||||
│ 事件:onPrice(codes) → 通知订阅方
|
||||
▼
|
||||
3 个监控表格(AllPositionsTab / StrategyTab)
|
||||
│ 行渲染时读 prices.get(code) 叠加现价
|
||||
▼
|
||||
PriceCell(格式化 + 红涨绿跌 + 变化高亮)
|
||||
```
|
||||
|
||||
### 关注列表(自动汇总)
|
||||
|
||||
- 3 个表格各自渲染时,把出现的 code 上报给 MarketDataProvider(`registerCodes(codes)`);
|
||||
- Provider 合并为关注集合,作为快照拉取与推送过滤的依据;
|
||||
- 简化:全量推送本来就带所有 code,前端只从 prices Map 取自己行需要的 code(无需显式过滤,天然按需)。
|
||||
|
||||
### 生命周期
|
||||
|
||||
- 挂载:获取激活配置 host → 拉快照 → 建 WS → 订阅 whole → 收推送更新 Map;
|
||||
- 配置切换:监听激活配置变化(host 变化)→ 断开旧 WS → 退订 → 重建连接;
|
||||
- 断开:指数退避静默重连 + 状态提示(连接中/已连接/重连中/断开)。
|
||||
|
||||
## 涉及设计约束
|
||||
|
||||
| 约束 | 内容 |
|
||||
|---|---|
|
||||
| 技术约束-001 | 统一数据源抽象:行情实时数据仍走 QMT Bridge 适配器口径(现价字段 lastPrice 语义一致) |
|
||||
| 技术约束-003 | 直连 QMT Bridge(前端直连其原生 WS,不经过 MCP) |
|
||||
| 技术约束-008 | 复用激活 QMT 连接配置的 host(多配置热切换联动 WS) |
|
||||
| 技术约束-010(新增) | 前端直连 QMT Bridge WebSocket 订阅行情;服务端不中转 WS |
|
||||
| 产品约束-007(新增) | 3 个监控表格展示盘中现价列:红涨绿跌着色 + 变化高亮;仅现价一项 |
|
||||
|
||||
## 实现步骤
|
||||
|
||||
1. 服务端 API:新增 `qmt-connections/active-host`(返回 { host, baseUrl, wsUrl, name });
|
||||
2. MarketDataProvider.jsx:WS 客户端(连接/订阅/价格 Map/快照/重连/配置切换)+ Context;
|
||||
3. PriceCell.jsx:现价单元格(格式化 + 涨跌色 + 高亮动画);
|
||||
4. 3 表格接入:AllPositionsTab / StrategyTab 加「现价」列;
|
||||
5. 构建安装 + 验收。
|
||||
@@ -0,0 +1,42 @@
|
||||
# 迭代 04 复盘:WS 盘中价格实时更新(2026-09-01)
|
||||
|
||||
> 复盘日期:2026-09-01 | 迭代状态:**已完成(验收通过)**
|
||||
> 关联需求:R-005(WS 盘中价格实时更新,已定稿)、R-006(策略数据 JSON data store schema,已定稿)
|
||||
> 关联计划:PLAN-004(WS 盘中价格实时更新)、PLAN-005(策略数据 JSON 标准化)
|
||||
|
||||
## 结果
|
||||
|
||||
迭代 04 达成:3 个监控表格(全部持仓 / 手动做T / 网格超市)实现盘中现价实时显示(服务端缓存 + 前端轮询),数据层标准化为 JSON data store(份额 + 行情持久化),并完成多项架构治理(api 拆分、component 目录、健康检查、膨胀修复)。
|
||||
|
||||
## 过程事实
|
||||
|
||||
1. **R-005 WS 实时价格**:经历多次架构演进——
|
||||
- 初版前端直连 WS(老师拍板)→ 实测发现 QMT Bridge REST 无 CORS 头、前端直连订阅被跨域拦截;
|
||||
- 改服务端代理订阅/快照 → 前端轮询;再演进为「服务端中转 + 行情缓存」(老师定);
|
||||
- 行情持久化(store.market.json)解决首屏无价;MarketFeed 启动 prime 主动拉持仓盘口;
|
||||
2. **R-006 JSON data store**:策略数据集标准化(store.schema.json / store.json / store.market.json),旧 allocations.json 自动迁移(保留 .bak);
|
||||
3. **架构治理**:
|
||||
- src/api.js(377 行)按领域拆分 → src/api/ 目录(positions / strategies / qmt-connections / market / common);
|
||||
- 创建 src/component/ 目录,market-cache 拆分为 MarketDataHub(缓存)+ MarketFeed(获取),业务模块陆续迁入;
|
||||
- 移除冗余端点(subscribe-ws/unsubscribe-ws/tick-snapshot)、前端去重(表格上报 code 替代 Provider 自拉);
|
||||
4. **QMT 健康检查**:服务端定时探测(5 分钟)+ 缓存,前端 QMT chip 右侧圆点(绿/红),点击触发即时探测;
|
||||
5. **数据误删事故**(严重教训):回归测试脚本误在真实数据执行写操作,导致大连热电/万顺新材份额丢失——已恢复 + 建立测试隔离机制(ODL_TEST_DATA_DIR)并沉淀技术约束-011;
|
||||
6. **行情缓存膨胀 bug**(38MB):WS 全市场推送无过滤 + setMarketQuotes 合并残留——三层修复(watchCodes 过滤 / warmup 不认可全市场 / 整体替换),最终 store.market.json 13 条 11KB;
|
||||
7. **WS 推送稳定性**:实测 QMT Bridge WS 推送是会话级/有状态(连接后 wsPushCount=0,纯 REST 轮询供数)——归属 QMT Bridge 工作空间;
|
||||
8. **全市场缓存**:实测 whole 推送 26,751 条中标准 A股仅 2,775 只(其余为期权/衍生品代码段)——方案搁置,保持 watchCodes。
|
||||
|
||||
## 经验(已沉淀)
|
||||
|
||||
- **服务端重启 vs 客户端刷新**:服务端模块宿主启动时加载(改动需重启),客户端 bundle 刷新即载——版本差排查先看两侧加载时机(迭代 03 经验复用);
|
||||
- **前端对行情状态应无感**:行情刷新/获取是服务端职责,前端只拿结果——移除状态条,避免误报;
|
||||
- **测试隔离**:回归/测试脚本禁止在真实数据上执行写操作(技术约束-011,数据误删事故沉淀);
|
||||
- **缓存膨胀三要素**:全量数据源(WS 全市场)必须过滤(watchCodes)、加载不认可历史全量(warmup)、写盘必须整体替换(setMarketQuotes);
|
||||
- **WS 推送不可靠**:QMT Bridge WS 为会话级行为(连接后可能 0 推送),实时数据需 REST 轮询兜底;
|
||||
- **代码段过滤**:QMT whole 推送含大量非 A股品种(89%),全市场类操作需按标准股票代码过滤。
|
||||
|
||||
## 遗留/后续
|
||||
|
||||
1. **WS 推送稳定性**:归属 QMT Bridge 工作空间(老师另开筛选解决);
|
||||
2. **全市场缓存**:搁置(需先解决 WS 稳定 + 只缓存标准 A股);
|
||||
3. **临时诊断端点 market-stats**:可保留观测或后续移除;
|
||||
4. **数据存储设计文档**:docs/03-设计约束/数据存储设计.md(已沉淀,后续存储变更以此为据)。
|
||||
@@ -0,0 +1,29 @@
|
||||
# 迭代目标:04-WS盘中价格实时更新
|
||||
|
||||
## 目标
|
||||
|
||||
在 3 个监控表格(全部持仓 / 手动做T / 网格超市)中实现**盘中价格实时更新**:前端直连 QMT Bridge WebSocket 订阅全市场 tick,事件驱动更新关注股票的现价(lastPrice)。
|
||||
|
||||
## 目标描述
|
||||
|
||||
- 范围:R-005(已定稿 2026-08-31);不新增其他功能域;
|
||||
- 要解决的问题:交易时段表格价格**不会自动更新**(R-003 复盘遗留 O4 问题),需实时反映盘中价格;
|
||||
- 引用需求:**R-005(已定稿,2026-08-31)**;
|
||||
- 实现方式:前端直连 QMT Bridge WS(老师确认拓扑),订阅 whole 全市场,前端按关注列表过滤,现价叠加层更新。
|
||||
|
||||
## 目标讨论过程
|
||||
|
||||
- 2026-08-28 迭代 01 复盘提出 T-005(WS 数据监控)方向,放草稿;
|
||||
- 2026-08-31 老师提出本轮需求:前端直连 ws 全量订阅 + 事件驱动更新 3 个表格的盘中价格(仅现价一项);
|
||||
- 2026-08-31 讨论确认 4 项决策:
|
||||
1. 连接拓扑:**前端直连 QMT Bridge WS**(非插件服务端中转);
|
||||
2. 订阅范围:**订阅全市场行情流**(type=whole,codes=SH/SZ),前端按关注列表过滤;
|
||||
3. 关注列表来源:**3 表格自动汇总**;
|
||||
4. 现价展示:**现价列 + 红涨绿跌 + 变化高亮**,刷新机制为**价格叠加层**(不整表重拉);
|
||||
- 2026-08-31 协议实测(GEMWIN 100.110.38.78:8610):订阅/退订/推送/快照格式全部确认。
|
||||
|
||||
## 对老师的配合需求
|
||||
|
||||
- 验收需要 GEMWIN(Tailscale 100.110.38.78:8610)可达(已确认可达);
|
||||
- 构建安装后重载页面配合验证;
|
||||
- 验收通过后确认归档。
|
||||
@@ -0,0 +1,482 @@
|
||||
# 迭代说明:04-WS盘中价格实时更新
|
||||
|
||||
> 本文件为本次迭代的**修改记录**(追加式,只记事实):每完成一处修改,追加一条记录(改了什么 / 为什么 / 涉及文件)。
|
||||
> 创建:2026-08-31
|
||||
|
||||
## 修改记录
|
||||
|
||||
### 2026-08-31 · 建立迭代 04 文档骨架
|
||||
|
||||
- **做了什么**:需求 T-005 草稿转正为 R-005(docs/05-需求池/R-005.md);创建计划 PLAN-004(docs/02-计划/计划-WS盘中价格实时更新.md);创建迭代 04 子目录(docs/04-迭代记录/04-WS盘中价格实时更新/)下的迭代目标 / 技术实现方案 / 验收标准 三件套 + 本说明。
|
||||
- **为什么**:本轮迭代按 divine-lot-dev 流程先落文档(需求定稿 → 计划 → 迭代范围),后续修改全部可回溯。
|
||||
- **涉及文件**:docs/05-需求池/R-005.md、docs/02-计划/计划-WS盘中价格实时更新.md、docs/04-迭代记录/04-WS盘中价格实时更新/(4 个 md)。
|
||||
|
||||
### 2026-08-31 · 服务端:新增 active-host / unsubscribe-ws 端点
|
||||
|
||||
- **做了什么**:src/api.js 新增两个端点:
|
||||
- `qmt-connections/active-host`:返回激活 QMT 连接配置的 `{ name, baseUrl, host, wsUrl, activeId }`(host 含端口,wsUrl = ws://host/ws),供前端据此直连 QMT Bridge WebSocket;连接列表为空时回退数据源当前 baseUrl(cordis 兜底);
|
||||
- `qmt-connections/unsubscribe-ws`:服务端代理 `POST {baseUrl}/data/unsubscribe`(浏览器侧 WS 直连可行,REST 退订统一走服务端避免跨域)。
|
||||
- **为什么**:前端需要激活配置的 host 才能建 WS(技术约束-010);退订走服务端代理更干净。
|
||||
- **涉及文件**:src/api.js。
|
||||
|
||||
### 2026-08-31 · 客户端:MarketDataProvider(WS 行情客户端)
|
||||
|
||||
- **做了什么**:新增 src/client/market/MarketDataProvider.jsx:
|
||||
- 直连 `ws://<激活配置host>:8610/ws`(浏览器原生 WebSocket);
|
||||
- 连接前 REST 订阅 `POST /data/subscribe { type:'whole', codes:['SH','SZ'] }` 拿 sub_id;
|
||||
- 维护 code → { lastPrice, lastClose, stime, open, high, low } 价格 Map(useReducer);
|
||||
- 事件驱动:WS 推送 `{ type:'whole', data: { code: snap } }` → dispatch update → 所有订阅表格重渲染现价;
|
||||
- 连接后拉一次 `GET /data/tick?codes=SH,SZ` 快照补初值(WS 只推增量,无 prime);
|
||||
- 断线指数退避重连(1s→2s→4s…上限 15s)+ 连接状态(connecting/live/reconnecting/disconnected);
|
||||
- 每 5s 轮询 active-host,激活配置 host 变化时自动断开旧 WS 并重建(复用 R-004 多配置热切换)。
|
||||
- **为什么**:实现「前端直连 + 全量订阅 + 事件驱动」的核心(R-005 定稿)。
|
||||
- **涉及文件**:src/client/market/MarketDataProvider.jsx(新增)。
|
||||
|
||||
### 2026-08-31 · 客户端:PriceCell 现价单元格 + 行情状态条
|
||||
|
||||
- **做了什么**:新增 src/client/views/PriceCell.jsx:
|
||||
- `PriceCell`:显示 lastPrice(≥100 用 2 位小数,否则 3 位),红涨绿跌(对比 lastClose,A股习惯:涨红 #d32f2f / 跌绿 #2e7d32 / 平灰);价格变化时 1.2s 金色高亮闪动(CSS keyframes);
|
||||
- `MarketStatusBar`:表格顶部行情连接状态提示(连接中/实时/重连中/未连接,圆点颜色区分)。
|
||||
- **为什么**:产品约束-007(现价列 + 涨跌色 + 变化高亮)。
|
||||
- **涉及文件**:src/client/views/PriceCell.jsx(新增)。
|
||||
|
||||
### 2026-08-31 · 客户端:3 个监控表格接入现价列
|
||||
|
||||
- **做了什么**:
|
||||
- src/client/index.js:withConnection 内包一层 MarketDataProvider,3 个表格共享同一行情实例;
|
||||
- src/client/views/AllPositionsTab.jsx:表头/表体新增「现价」列(名称后),标题下加 MarketStatusBar;
|
||||
- src/client/views/StrategyTab.jsx:表头/表体新增「现价」列(名称后),顶部加 MarketStatusBar(手动做T / 网格超市共用)。
|
||||
- **为什么**:3 表格展示关注股票盘中现价(R-005 目标)。
|
||||
- **涉及文件**:src/client/index.js、src/client/views/AllPositionsTab.jsx、src/client/views/StrategyTab.jsx。
|
||||
|
||||
### 2026-08-31 · 构建验证
|
||||
|
||||
- **做了什么**:`pnpm typecheck`(0 错误)+ `pnpm build` 通过(客户端 bundle 82.54kB / 17.96kB gzip,服务端 14 文件,wrap 完成)。
|
||||
- **端到端链路验证**:Node 模拟前端流程(订阅 whole → WS 连接 → 26 批推送 → 关注 code 过滤 → 提取现价)成功:603028.SH → lastPrice=7.18(lastClose=7.1,涨,UI 红色)。
|
||||
- **为什么**:构建与链路验证通过,可进入宿主安装验收。
|
||||
- **涉及文件**:lib/(构建产物)。
|
||||
|
||||
|
||||
### 2026-08-31 · 问题分析记录:订阅走服务端代理,WS 推送问题归属另一工作空间
|
||||
|
||||
**现象**(老师观察):全部持仓页「行情实时 IP」短暂出现后变「行情重连中」;持仓列表现价始终无数据。
|
||||
|
||||
**分析过程(基于实验证据,非猜测)**:
|
||||
1. **CORS 实证**:QMT Bridge REST(/health、/data/subscribe、/data/tick)响应**不含 Access-Control-Allow-Origin 头**,OPTIONS 预检返回 404 → 浏览器页面(origin=127.0.0.1:3080)直接 fetch QMT Bridge REST 会被**跨域拦截**。这与项目历史一致:REST 一直走 DSH 服务端代理(qmt-connections/test 注释「浏览器跨域规避」)。
|
||||
2. **架构结论**:**订阅是 RESTful 接口(POST /data/subscribe),必须走 DSH 服务端代理转发**,服务端无跨域问题;前端直连 REST 订阅是错误的架构。
|
||||
3. **WS 行为实验**(Node 直连 GEMWIN):
|
||||
- 不订阅直接连 WS → 收到 25 条/5秒 推送(首个连接);
|
||||
- REST 订阅成功(sub_id 有效)→ 连 WS → 0 条;
|
||||
- 全新进程不订阅直连 → 稳定收到 21 条/5秒。
|
||||
- 结论:**QMT Bridge 的 WS 推送语义与 OpenAPI 文档描述不符**(订阅反而可能干扰推送),该问题**归属 QMT Bridge 工作空间**(老师负责,另行解决)。
|
||||
4. **本工作空间策略**:订阅与快照(REST)一律走 DSH 服务端代理;数据显示先以 **tick 快照轮询**(GET /data/tick,稳定可用)为兜底数据源 + WS 推送为增强通道,确保持仓列表实盘价格**先显示出来**。
|
||||
|
||||
**涉及文件**:本记录(docs/04-迭代记录/04-WS盘中价格实时更新/迭代说明.md)。
|
||||
|
||||
|
||||
### 2026-08-31 · 服务端:新增 subscribe-ws / tick-snapshot 代理端点
|
||||
|
||||
- **做了什么**:src/api.js 新增两个端点(均走 DSH 服务端代理,无跨域):
|
||||
- `qmt-connections/subscribe-ws`:代理 `POST {激活baseUrl}/data/subscribe`(body { type, codes }),返回 { ok, sub_id };
|
||||
- `qmt-connections/tick-snapshot`:代理 `GET {激活baseUrl}/data/tick?codes=...`,返回 { ok, data: { code: snapshot } }。
|
||||
- **为什么**:订阅/快照是 RESTful 接口,浏览器直连 QMT Bridge 会跨域(无 CORS 头、OPTIONS 404),统一走服务端代理(与 qmt-connections/test 同模式,项目既有架构约定)。
|
||||
- **涉及文件**:src/api.js。
|
||||
|
||||
### 2026-08-31 · 客户端:MarketDataProvider 重构(服务端代理 + 快照轮询兜底)
|
||||
|
||||
- **做了什么**:重写 src/client/market/MarketDataProvider.jsx:
|
||||
- 订阅 / 快照 / 退订全部改走服务端代理端点(/odl/api/qmt-connections/subscribe-ws、tick-snapshot、unsubscribe-ws),**移除前端直连 QMT Bridge REST**;
|
||||
- **双通道价格源**:① tick 快照轮询(3s 间隔,走服务端代理,稳定兜底,确保现价一定显示);② WS 推送(增强通道,收到即实时覆盖);
|
||||
- WS 连接保持前端直连(WebSocket 不受 CORS 限制),断线重连、激活配置切换联动保留。
|
||||
- **为什么**:实证确认 QMT Bridge REST 无 CORS 头(浏览器直连被拦截);「先实现订阅后的数据显示」——快照轮询保证现价先显示出来,WS 作为增强。
|
||||
- **涉及文件**:src/client/market/MarketDataProvider.jsx。
|
||||
|
||||
### 2026-08-31 · 模拟验证
|
||||
|
||||
- **做了什么**:Node 模拟新版前端流程(tick 快照轮询走服务端代理)→ 一次返回全部关注 code(600719.SH/601117.SH/603028.SH/001330.SZ/002065.SZ)的 lastPrice + lastClose,现价提取完整(如 600719.SH → 7.17,昨收 7.22 → 跌,UI 绿色)。
|
||||
- **为什么**:验证服务端代理 + 快照轮询链路可用,前端渲染 PriceCell 即可显示红涨绿跌。
|
||||
- **涉及文件**:无(验证记录)。
|
||||
|
||||
|
||||
### 2026-08-31 · 端到端验收(宿主链路)
|
||||
|
||||
- **做了什么**:服务端新端点经宿主验证生效(subscribe-ws 订阅成功拿 sub_id;tick-snapshot 返回真实持仓现价);前端 bundle 确认加载新代码(rev=ae40cadd056a,与本地 lib 一致)。
|
||||
- **端到端链路验证**(Node 模拟前端调宿主 /odl/api):
|
||||
- 激活配置 GEMWIN → 订阅 ok(sub_id)→ 13 条持仓 → tick 快照 5/5 命中现价 → 红涨绿跌判定正确(600719.SH 跌→绿、603028.SH 涨→红)→ 退订 ok。
|
||||
- **验证中发现**:/odl/api/positions 端点返回**数组**(value 直接是数组),非 summary 的 {positions:[...]} 结构(测试脚本字段误用,已修正;非产品问题)。
|
||||
- **待老师确认**:浏览器刷新后 3 个表格现价列显示与红涨绿跌效果。
|
||||
- **涉及文件**:无(验证记录)。
|
||||
|
||||
|
||||
### 2026-08-31 · 修复:快照用真实持仓代码(根因定位)
|
||||
|
||||
- **现象**:刷新后订阅成功(sub_id 日志出现),但快照日志与现价均无。
|
||||
- **定位(实证,非猜测)**:给前端加调试日志(订阅/快照/prices 三处)→ 只见订阅成功,无快照日志 → 审查 fetchSnapshot 传参为 'SH,SZ'(交易所代码)→ 宿主实测:tick-snapshot 传 'SH,SZ' 返回 **data 为空** {},传真实股票代码(600719.SH)才返回数据。
|
||||
- **根因**:QMT Bridge 的 /data/tick 需要**真实股票代码**,前端快照却传了交易所代码 'SH,SZ',导致 data 为空、现价无数据。
|
||||
- **修复**:MarketDataProvider 新增 fetchWatchCodes()(从 /odl/api/positions 获取持仓 code 集合),快照改用真实持仓代码。
|
||||
- **验证**:Node 模拟修复后流程 → 13 只持仓全部取到现价(002347.SZ → 6.25 昨收 6.14 红涨;600719.SH → 7.17 昨收 7.22 绿跌)。
|
||||
- **涉及文件**:src/client/market/MarketDataProvider.jsx。
|
||||
|
||||
|
||||
### 2026-08-31 · 架构升级:服务端中转 + 行情缓存(老师定)
|
||||
|
||||
- **老师决策**:DSH 服务端做「一次 WS 订阅 + QMT 桥整合」,维护一份实时行情缓存(内存 Map);页面统一 5s 轮询读 DSH 接口(按 code 查询),不做前端直连。
|
||||
- **实现**:
|
||||
- 新增 src/market-cache.js:MarketCache 类(服务端连 ws://<激活host>:8610/ws 收全市场推送 → 写缓存 Map;断线指数退避重连;setBaseUrl 热切换;getByCodes 按 code 查询);
|
||||
- src/index.js:创建 MarketCache 并启动,插件释放时 stop;激活热切换同步 setBaseUrl;
|
||||
- src/api.js:新增 `market-snapshot` 端点(按 codes 查询缓存);激活/更新/删除连接时同步 marketCache;
|
||||
- src/client/market/MarketDataProvider.jsx:**移除前端直连 WS**,改为 5s 轮询 /odl/api/market-snapshot(先取持仓 code,再按 code 查询)。
|
||||
- **WS 推送稳定性实验**(Node 直连 GEMWIN,2026-08-31):
|
||||
- 不订阅直连 10s → 0 条;REST 订阅后连 10s → 0 条;退订后再连 10s → 8 条;
|
||||
- 结论:**QMT Bridge WS 推送是会话级/有状态行为**,仅首个连接或特定订阅窗口会推送——该问题归属 QMT Bridge 工作空间(老师负责,另行解决)。
|
||||
- **缓存兜底**:MarketCache 以 WS 推送更新缓存为主,**辅以 REST tick 快照补数据**(REST 稳定可用,13 只持仓全部可取),确保缓存有值、前端轮询能拿到现价。
|
||||
- **涉及文件**:src/market-cache.js(新增)、src/index.js、src/api.js、src/client/market/MarketDataProvider.jsx。
|
||||
|
||||
|
||||
### 2026-08-31 · 缓存兜底:market-snapshot 按需补 + REST 定时刷新
|
||||
|
||||
- **做了什么**:
|
||||
- src/market-cache.js:MarketCache 加 REST tick 快照兜底(每 5s 对缓存已有 code 用 /data/tick 刷新);
|
||||
- src/api.js:market-snapshot 端点加「miss 按需补」——查询的 code 不在缓存时,现场用 REST /data/tick 拉取补入缓存并返回(**不依赖 WS 推送**,REST 稳定可用)。
|
||||
- **为什么**:2026-08-31 实验证实 QMT Bridge WS 推送是会话级/有状态行为(仅首个连接或特定订阅窗口推送),不能作为唯一数据源;REST tick 稳定可取(13 只持仓全取到),作为兜底确保前端轮询必有数据。
|
||||
- **验证**:Node 模拟 market-snapshot(缓存空→按需补→5/5 命中→缓存 5 条→再次查询全命中)。
|
||||
- **涉及文件**:src/market-cache.js、src/api.js。
|
||||
|
||||
|
||||
### 2026-08-31 · 结构调整:src/api.js 按领域拆分
|
||||
|
||||
- **做了什么**(老师要求,审视结构后清理):
|
||||
- 原 src/api.js(377 行,聚合 5 类端点)拆分为:
|
||||
- src/api/index.js:路由入口(合并各领域方法表 + 405/404/500 公共处理 + 领域分发);
|
||||
- src/api/common.js:公共工具(writeJson / readJsonBody / resolveActiveBaseUrl);
|
||||
- src/api/positions.js:持仓域(positions);
|
||||
- src/api/strategies.js:策略/份额/tabs 域(14 端点);
|
||||
- src/api/qmt-connections.js:QMT 连接域(11 端点,含热切换编排 setBaseUrl + marketCache 同步);
|
||||
- src/api/market.js:行情域(market-snapshot,含 miss 按需补)。
|
||||
- 更新 src/index.js import(./api.js → ./api/index.js)与 tsdown.config.ts 入口。
|
||||
- **验证**:typecheck 0 错误;构建通过(lib/api/index.js + api chunk 正常);27 个端点方法表核对完整(positions 1 + strategies 14 + qmt 11 + market 1);构建产物模块加载正常(registerApi 正确导出)。
|
||||
- **涉及文件**:src/api/*(6 个新文件)、src/index.js、tsdown.config.ts;删除 src/api.js。
|
||||
|
||||
|
||||
### 2026-08-31 · 拆分验收(宿主重启后全端点回归)
|
||||
|
||||
- **做了什么**:宿主重启后对拆分后的 API 做全端点回归测试。
|
||||
- **结果**:
|
||||
- 27 端点全部可用(15 项首轮通过;3 项「失败」为测试参数错误——shares=0 触发业务校验、无未分配份额触发 no-unallocated,属正确业务行为);
|
||||
- 合法参数重测 add-shares / remove-shares / move-all-shares 全部 OK(600719.SH 100 股往返,清理干净);
|
||||
- 核心端点:market-snapshot 13/13、active-host GEMWIN、QMT CRUD、subscribe-ws/unsubscribe-ws、tick-snapshot(lastPrice=7.15)全部通过。
|
||||
- **结论**:api.js 按领域拆分无功能回归,验收通过。
|
||||
- **涉及文件**:无(验证记录)。
|
||||
|
||||
|
||||
### 2026-08-31 · 结构调整:创建 src/component/ 目录 + market-cache 拆分为 Hub/Feed
|
||||
|
||||
- **老师决策**:在 src/ 下统一创建 component/ 目录收纳业务模块(便于统一位置管理);market-cache 拆成多个模块各一个文件。
|
||||
- **实现**:
|
||||
- 新建 src/component/ 目录:
|
||||
- MarketDataHub.js:行情缓存服务(缓存 Map + getByCodes 查询 + **miss 自动补拉收敛在内部** + ingest 写入入口);
|
||||
- MarketFeed.js:行情数据获取(WS 订阅连接 + REST 定时刷新兜底 + 断线重连 + setBaseUrl 热切换),写入 Hub;
|
||||
- src/index.js:装配改为 marketHub + marketFeed(替代 marketCache);
|
||||
- src/api/market.js:**移除 miss 补拉代码**,只调 hub.getByCodes(HTTP 编排纯净);
|
||||
- src/api/qmt-connections.js:热切换 marketCache?.setBaseUrl → marketFeed?.setBaseUrl;
|
||||
- src/api/index.js:runtime 命名 marketCache → marketHub/marketFeed;
|
||||
- 删除 src/market-cache.js。
|
||||
- **职责划分**(解决「职责混乱」):
|
||||
- MarketDataHub = 缓存 + 查询(单一职责,查询保证有值);
|
||||
- MarketFeed = 取数(WS + REST 刷新),不对外查询;
|
||||
- api/market.js = 纯 HTTP 编排,不碰缓存内部。
|
||||
- **验证**:typecheck 0 错误;构建通过(chunk 打包正常,无动态导入残留);Node 模拟 hub.getByCodes 三场景(空缓存 miss 补拉 / 命中缓存 / 重复全命中)全部通过。
|
||||
- **涉及文件**:src/component/MarketDataHub.js(新增)、src/component/MarketFeed.js(新增)、src/index.js、src/api/market.js、src/api/qmt-connections.js、src/api/index.js;删除 src/market-cache.js。
|
||||
|
||||
|
||||
### 2026-08-31 · 拆分验收(宿主重启后全端点回归)
|
||||
|
||||
- **结果**:宿主重启后全端点回归全部通过:
|
||||
- market-snapshot 13/13(miss 补拉正常,600719.SH → 7.18);二次查询命中缓存 13 条;
|
||||
- summary 13 只;qmt-connections / active-host(GEMWIN)/ activate 热切换 / subscribe-ws+unsubscribe-ws / tick-snapshot(7.18)/ strategies(2 个)全部 OK。
|
||||
- **结论**:MarketDataHub + MarketFeed 拆分无功能回归,component/ 目录结构落地,验收通过。
|
||||
- **涉及文件**:无(验证记录)。
|
||||
|
||||
|
||||
### 2026-08-31 · 优化:移除冗余端点 + 前端去重 + 模块迁入 component/
|
||||
|
||||
**#1 移除冗余端点**(死代码清理):
|
||||
- 移除 src/api/qmt-connections.js 的 subscribe-ws / unsubscribe-ws / tick-snapshot 三端点(架构演进后无人调用:前端无引用、MarketFeed 直接 new WebSocket 不依赖它们)。
|
||||
- 涉及文件:src/api/qmt-connections.js。
|
||||
|
||||
**#2 前端去重**(消除重复 positions 调用):
|
||||
- MarketDataProvider 不再自拉 positions 获取关注 code,改为暴露 registerCodes(codes),由表格组件渲染后上报(AllPositionsTab 上报 summary 持仓 code、StrategyTab 上报策略持仓 code);Provider 只轮询 market-snapshot。
|
||||
- 收益:减少一次网络往返;Provider 与表格数据天然同步(同一份 code 来源)。
|
||||
- 涉及文件:src/client/market/MarketDataProvider.jsx、src/client/views/AllPositionsTab.jsx、src/client/views/StrategyTab.jsx。
|
||||
|
||||
**#3 业务模块迁入 src/component/**(结构治理延续):
|
||||
- 迁移:storage.js → component/AllocationStorage.js;position/manager.js → component/PositionManager.js;data-source/qmt-bridge-rest.js → component/QmtBridgeRestDataSource.js;data-source/types.js → component/data-source-types.js。
|
||||
- 删除空的 src/position/、src/data-source/ 目录;更新 index.js import、tsdown 入口(src/component/*.js)、JSDoc 类型引用。
|
||||
- 收益:lib/component/ 每个模块独立编译文件,结构统一(component = 业务模块,api = 路由,client = 前端)。
|
||||
- 涉及文件:迁移 4 文件 + src/index.js + tsdown.config.ts + 2 处 JSDoc。
|
||||
|
||||
**验证**:typecheck 0 错误;构建通过(lib/component/ 6 个模块独立文件);构建产物模块加载全部正常。
|
||||
|
||||
|
||||
### 2026-08-31 · 优化验收(宿主重启后)
|
||||
|
||||
- **全端点回归通过**:positions 13 条、market-snapshot 13/13、summary 13 只、strategy-positions 12 只、qmt-connections/active-host GEMWIN、已移除端点 tick-snapshot 返回 404(确认移除生效)、test 可达 82ms、二次 market-snapshot 命中缓存。
|
||||
- **备注**:回归中出现一次 positions/summary fetch failed,排查为 QMT Bridge 瞬时网络抖动(Tailscale relay 切换,ping 通 + health ok 后恢复),非代码问题。
|
||||
- **结论**:#1 移除冗余端点 + #2 前端去重 + #3 模块迁入 component/ 三项优化全部完成,无功能回归,验收通过。
|
||||
- **涉及文件**:无(验证记录)。
|
||||
|
||||
|
||||
### 2026-08-31 · 数据误删事故分析与防再发机制
|
||||
|
||||
**事故**:大连热电(600719.SH)与万顺新材(300057.SZ)的份额分配数据丢失。
|
||||
|
||||
**根因(非业务代码 bug,是测试脚本误操作)**:
|
||||
- 我在回归测试脚本(regression.mjs / retest-shares.mjs)中在**真实生产数据**上执行了写操作:
|
||||
- regression.mjs 的 remove-all-shares(清空某策略全部份额);
|
||||
- retest-shares.mjs 对 600719.SH 的 move-all-shares + remove-shares 清理。
|
||||
- 这些操作本应只读或使用临时数据,误在真实 allocations.json 上执行,导致 600719 的 grid/manual-t 份额、300057 的 manual-t 份额被清空。
|
||||
|
||||
**恢复**:按老师提供的原值恢复(600719 = grid 800 + manual-t 1000;300057 = grid 1000 + manual-t 1000),已确认 13 只全部正确。
|
||||
|
||||
**防再发机制(老师定:测试用独立数据目录)**:
|
||||
- AllocationStorage 新增测试模式:环境变量 ODL_TEST_DATA_DIR 或构造参数 dataDir 指向独立目录时,存储隔离,不影响真实 ~/.dsh/one-divine-lot/allocations.json;
|
||||
- 已验证:临时目录实例执行 set/removeStrategyShares 写操作,真实数据不变;
|
||||
- 测试规范:回归/测试脚本必须用独立数据目录,**禁止在真实数据上执行写操作**(只读端点可直连,写操作一律走测试实例)。
|
||||
|
||||
**涉及文件**:src/component/AllocationStorage.js(测试模式支持)。
|
||||
|
||||
|
||||
### 2026-08-31 · R-006:策略数据 JSON data store schema 标准化
|
||||
|
||||
**需求定稿**(讨论确认):
|
||||
- 范围:策略数据集(份额分配)标准化;策略定义仍留 DSH settings(设置页交互不变);
|
||||
- 形态:store.schema.json(类 JSON Schema 描述 dataset)+ store.json(数据,策略为中心);
|
||||
- dataset 格式:[{code, shares}] 数组(可扩展);
|
||||
- 旧 allocations.json 启动时自动迁移(保留 .bak 备份);
|
||||
- 删除策略时 API 层联动删 store dataset(同现状联清份额模式)。
|
||||
|
||||
**实现**:
|
||||
- 新增 src/component/DataStore.js:STORE_SCHEMA 定义 + 读写 store.json(原子写)+ 迁移 + dataset CRUD(getDataset/setDataset/removeDataset/getAllDatasets);
|
||||
- 重写 src/component/PositionManager.js:存储改接 DataStore(策略为中心),对外方法签名不变(addToStrategy/removeFromStrategy/getSummary 等);
|
||||
- src/index.js:DataStore 替代 AllocationStorage 装配。
|
||||
|
||||
**验证**(测试隔离目录):
|
||||
- 迁移:allocations.json(code 为中心)→ store.json(策略为中心 strategies[].dataset=[{code,shares}]),旧文件备份 .bak ✅;
|
||||
- schema 生成 ✅;dataset CRUD ✅;原子写多次 save 完整 ✅。
|
||||
|
||||
**涉及文件**:src/component/DataStore.js(新增)、src/component/PositionManager.js(重写)、src/index.js。
|
||||
|
||||
|
||||
### 2026-08-31 · R-006 验收(宿主重启后)
|
||||
|
||||
- **迁移验证**:宿主重启自动迁移真实 allocations.json → store.json(策略为中心),13 只持仓份额完整转入(grid 13 只 + manual-t 2 只),旧文件备份 allocations.json.bak ✅
|
||||
- **数据完整性**:大连热电 grid 800 + manual-t 1000(分配 1800);万顺新材 grid 1000 + manual-t 1000(分配 2000)✅
|
||||
- **端点回归**:summary 13 只、strategy-positions(grid) 13 只、strategies 列表正常;add-shares 超限正确拒绝(业务校验);strategies/remove 联动清 dataset 正常;market-snapshot 行情正常 ✅
|
||||
- **真实数据未污染**:测试用临时策略(add/remove 往返 + 删除联动),600719 份额不变 ✅
|
||||
- **结论**:R-006 JSON data store schema 标准化完成,数据迁移零丢失,无功能回归,验收通过。
|
||||
- **涉及文件**:无(验证记录)。
|
||||
|
||||
|
||||
### 2026-08-31 · 修复:行情状态误显示「无激活配置」
|
||||
|
||||
- **现象**:点击持仓 tab 能加载持仓数据,但状态条显示「行情未连接(无激活配置)」。
|
||||
- **根因**:MarketDataProvider 主循环在 watchCodes 为空(表格尚未上报 code)时设置 DISCONNECTED 状态;DISCONNECTED 文案「无激活配置」是前端直连时代遗留——R-006 后行情走服务端中转,前端对激活配置应**无感**。
|
||||
- **修复**:
|
||||
- Provider 主循环:watchCodes 为空时保持 IDLE(等待表格上报),不再设 DISCONNECTED;用 statusRef 同步最新状态避免闭包过期;
|
||||
- PriceCell 状态文案:disconnected 改为「行情未连接」(去掉「无激活配置」);
|
||||
- IDLE 时不显示状态条。
|
||||
- **涉及文件**:src/client/market/MarketDataProvider.jsx、src/client/views/PriceCell.jsx。
|
||||
|
||||
|
||||
### 2026-08-31 · 行情持久化(store.market.json,解决首屏无价格)
|
||||
|
||||
- **问题**:行情缓存仅内存(MarketDataHub.cache Map),宿主重启后为空;持仓 tab 首次刷新时价格 miss,若 REST 补拉慢/失败则显示「—」。
|
||||
- **根因**:行情未持久化——重启后无上次价格,需每次实盘订阅/拉取。
|
||||
- **方案(老师确认:DataStore 就是为数据快速展现存在)**:
|
||||
- DataStore 新增行情持久化:store.market.json(code → snapshot,原子写);
|
||||
- MarketDataHub 接入:启动 warmup 读盘 → 内存缓存首屏即有价;ingest 增量更新后防抖写盘(3s);查询命中顺序:内存 → 磁盘 → REST 补拉;
|
||||
- index.js:dataStore 注入 Hub + 启动预热。
|
||||
- **验证**(测试隔离目录):实盘 ingest → 写盘 2 条;模拟重启 warmup 加载 2 条;首次查询 2/2 命中(600719 lastPrice=7.16)——重启后首屏即有价格 ✅。
|
||||
- **涉及文件**:src/component/DataStore.js、src/component/MarketDataHub.js、src/index.js。
|
||||
|
||||
|
||||
### 2026-08-31 · 修复:启动报错 storage 未初始化(TDZ)
|
||||
|
||||
- **现象**:宿主重启后插件加载失败,报 `Cannot access 'storage' before initialization`。
|
||||
- **根因**:index.js 装配时序错误——marketHub 创建时引用 storage(dataStore: storage),但 storage 在其后才创建(const 暂时性死区 TDZ)。
|
||||
- **修复**:将 storage 创建移到 marketHub 之前(55 行),顺序改为 storage → marketHub → marketFeed。
|
||||
- **验证**:装配顺序正确,构建通过。
|
||||
- **涉及文件**:src/index.js。
|
||||
|
||||
|
||||
### 2026-09-01 · 行情持久化验收(宿主重启后)
|
||||
|
||||
- **启动修复**:storage 时序问题(TDZ)修复后宿主正常启动。
|
||||
- **持久化验证**:
|
||||
- store.market.json 已生成(13 条行情快照,600719 大连热电 lastPrice=7.16 等完整字段);
|
||||
- market-snapshot 查询 13/13 命中(内存→磁盘→REST 三级);
|
||||
- 即使 QMT 断网,磁盘缓存也能提供上次价格(首屏有价,不依赖实盘订阅)。
|
||||
- **结论**:行情持久化闭环完成,验收通过。
|
||||
- **涉及文件**:无(验证记录)。
|
||||
|
||||
|
||||
### 2026-09-01 · 修复:持仓 tab 价格要等很久才出现(时序)
|
||||
|
||||
- **现象**:打开全部持仓,价格要等很久(约 5s)才刷新出来。
|
||||
- **根因**:MarketDataProvider 主循环是 5s 定时器;页面加载时 poll() 立即跑一次但 watchCodes 为空(表格未上报)跳过;表格加载后 registerCodes 上报 code,但**不触发立即拉取**,只能等下一次定时器(最多 5s)才有价格。
|
||||
- **修复**:registerCodes 上报新 code 后**立即触发一次 pollMarket**(通过 pollMarketRef 引用),首屏价格快速显示;定时器继续 5s 增量更新。
|
||||
- **验证**:构建通过。服务端 market-snapshot 本就 13/13 有价(已实测),修复后前端上报即拉取,不再等定时器。
|
||||
- **涉及文件**:src/client/market/MarketDataProvider.jsx。
|
||||
|
||||
|
||||
### 2026-09-01 · MarketFeed 启动主动拉取持仓盘口(prime)
|
||||
|
||||
- **问题**(老师指出):前端打开页面只有持仓数据、价格要等行情刷新才出现;但行情刷新是服务端职责,前端应直接拿到最新最终价格。
|
||||
- **根因查证**:插件启动时 MarketFeed 只做了 ①warmup 读磁盘(可能旧/空)②连 WS(被动等推送,QMT 推送会话级不可靠)③定时刷新(**空缓存时跳过**)——**没有任何「启动主动拉一次最新盘口」的逻辑**。
|
||||
- **修复**:MarketFeed.start() 新增 _primePositions():启动即从 dataSource 拿持仓 code → REST /data/tick → 写入缓存;setBaseUrl 热切换时也主动拉一次(新地址盘口)。
|
||||
- **验证**:Node 模拟启动 → prime 拉取 13 条盘口,缓存 13 条,查询 600719 → 7.16 ✅。
|
||||
- **效果**:服务端启动即有最新盘口价,前端任何时候打开页面都能从缓存拿到价格(不依赖前端轮询触发)。
|
||||
- **涉及文件**:src/component/MarketFeed.js。
|
||||
|
||||
|
||||
### 2026-09-01 · 需求:移除行情状态条 + 订阅状态灯 + QMT 健康检查
|
||||
|
||||
**需求 1:移除「行情实时」状态条**
|
||||
- 前端表格彻底移除 MarketStatusBar(PriceCell 删除该组件;AllPositionsTab / StrategyTab 移除引用与 status/wsInfo 解构)。
|
||||
- 原则:行情刷新是服务端职责,前端只拿结果,对行情状态彻底无感。
|
||||
- 涉及文件:src/client/views/PriceCell.jsx、AllPositionsTab.jsx、StrategyTab.jsx。
|
||||
|
||||
**需求 3:QMT 连接健康检查**
|
||||
- 新增 market-status 端点(src/api/market.js):探测激活 QMT Bridge /health 可达性(5s 超时)+ 返回订阅状态 + 缓存大小。
|
||||
- MarketFeed 新增 getStatus()(WS 连接态、最近推送时间、推送计数)。
|
||||
|
||||
**需求 2:订阅状态灯(QMT 切换 chip)**
|
||||
- QmtConnectionChip 文字前加圆点状态灯:
|
||||
- 绿:订阅存在 + 数据正常接收 + QMT 健康;
|
||||
- 黄:订阅存在但数据停滞(最近 30s 无推送);
|
||||
- 红:QMT 连接不健康;
|
||||
- 灰:无订阅。
|
||||
- 每 5s 轮询 market-status;下拉菜单显示订阅明细(推送次数)+ QMT 健康(延迟)。
|
||||
- 涉及文件:src/client/views/QmtConnectionChip.jsx。
|
||||
|
||||
**验证**:typecheck 0 错误;构建通过;本地模拟 market-status(QMT 健康 162ms、订阅 active+dataHealthy、状态灯绿)逻辑正确。
|
||||
|
||||
|
||||
### 2026-09-01 · 修正:market-status 数据健康判断(WS 不可靠时结合缓存)
|
||||
|
||||
- **问题**:宿主重启后 market-status 返回 wsConnected=true 但 tickCount=0、lastTickAt=0 → dataHealthy=false → 状态灯黄(停滞)。但缓存实际有 13 条(REST prime/轮询兜底)。
|
||||
- **根因**:数据健康只以「WS 收到推送」为标准,而 QMT WS 推送是会话级/有状态(连接后可能 0 推送)。
|
||||
- **修正**:dataHealthy = WS 推送新鲜(30s 内)**或** 缓存有数据(cacheSize > 0)——REST 兜底保证缓存更新时数据仍是好的。
|
||||
- **涉及文件**:src/api/market.js。
|
||||
|
||||
|
||||
### 2026-09-01 · 状态灯不稳定分析(黄/灰波动)
|
||||
|
||||
- **实测**(连续 5 次 market-status):qmt.healthy=true、wsConnected=true(WS 稳定)、tickCount=0、cache=13、dataHealthy=false → 灯黄。
|
||||
- **根因**:
|
||||
1. 服务端 dataHealthy 修正(wsFresh || cacheSize)**未生效**——宿主加载的是上一轮构建前的旧代码(构建在重启后完成);
|
||||
2. 前端首次加载 marketStatus=null 显示灰,轮询成功后变黄;「一会儿又变灰」可能与前端某次请求失败/时序有关(已加调试日志待确认)。
|
||||
- **修复**:
|
||||
- 服务端:dataHealthy = WS 推送新鲜 || 缓存有数据(WS 不可靠时缓存兜底);
|
||||
- 前端:loadStatus 失败保留上次状态(不误降灰);加调试日志定位。
|
||||
- **待办**:重启宿主让服务端修正生效,观察状态灯应为绿(订阅+缓存正常)。
|
||||
|
||||
|
||||
### 2026-09-01 · 数据存储设计文档(设计约束)
|
||||
|
||||
- **做了什么**:整理 DataStore 设计为独立设计约束文档 docs/03-设计约束/数据存储设计.md:
|
||||
- 设计目标(数据快速展现、标准化描述、统一 JSON 操作);
|
||||
- 总体架构(store.schema.json / store.json / store.market.json 三层);
|
||||
- 数据文件结构与 Schema(策略为中心份额分配 + 行情快照缓存);
|
||||
- 数据流(启动读盘 → 实盘更新 → 防抖写盘 → 三级查询命中);
|
||||
- 关键设计决策表(JSON 存储、策略定义留 settings、dataset 数组、行情持久化、原子写、测试隔离);
|
||||
- 迁移(allocations.json 自动迁移 + 备份)。
|
||||
- **同步更新**:技术方案约束.md —— 技术约束-010 更新为「服务端中转 + 缓存」(原前端直连已废弃);新增技术约束-012(数据存储设计引用)。
|
||||
- **涉及文件**:docs/03-设计约束/数据存储设计.md(新增)、docs/03-设计约束/技术方案约束.md。
|
||||
|
||||
|
||||
### 2026-09-01 · 重做:移除全盘订阅状态标记,独立 QMT health 检查
|
||||
|
||||
**移除**(全盘订阅状态暂缓,老师重开筛选做):
|
||||
- market-status 端点的 subscriptions 部分(订阅状态);
|
||||
- MarketFeed.getStatus()(订阅状态);
|
||||
- QmtConnectionChip 的订阅状态灯 + market-status 轮询。
|
||||
|
||||
**保留并重做**(QMT health 检查,需求 3):
|
||||
- 服务端:market-status → 精简为 qmt-health 端点(仅 QMT /health 可达性 + 延迟);
|
||||
- 前端:QmtConnectionChip 下拉框**右侧**新增独立 QmtHealthIndicator 控件(小圆点:绿灯=健康 / 红灯=异常;悬停显示延迟/错误;每 5s 轮询 qmt-health)。
|
||||
|
||||
**涉及文件**:src/api/market.js、src/component/MarketFeed.js、src/client/views/QmtConnectionChip.jsx。
|
||||
|
||||
|
||||
### 2026-09-01 · QMT 健康检查改为服务端定时 + 缓存机制
|
||||
|
||||
**机制(老师定)**:
|
||||
- 服务端 QmtHealthMonitor 每 **5 分钟**探测一次激活 QMT /health,结果缓存内存;
|
||||
- 前端打开页面读缓存(**秒回**,不等实时探测);
|
||||
- 前端点击圆点触发即时探测(refresh=true)刷新缓存;
|
||||
- 激活配置切换时立即补探(热切换联动)。
|
||||
|
||||
**实现**:
|
||||
- 新增 src/component/QmtHealthMonitor.js:定时探测(5min)+ 缓存 + getStatus(读缓存)+ probe(即时探测,防并发);
|
||||
- src/api/market.js:qmt-health 默认读缓存;refresh=true 触发即时探测;
|
||||
- src/index.js:创建并启动 QmtHealthMonitor,注入 registerApi,释放时 stop;
|
||||
- src/api/qmt-connections.js:激活/更新/删除连接后 qmtHealthMonitor.probe() 联动;
|
||||
- 前端 QmtHealthIndicator:打开读缓存、30s 低频同步、点击触发即时探测(悬停显示检测时间/延迟/错误)。
|
||||
|
||||
**验证**:typecheck 0 错误;构建通过;QmtHealthMonitor 模块加载正常。
|
||||
|
||||
|
||||
### 2026-09-01 · QMT 健康检查验收(宿主重启后)
|
||||
|
||||
- **验证**:
|
||||
- 读缓存(默认):healthy=true、fromCache=true,耗时 23ms(秒回);
|
||||
- 即时探测(refresh=true):healthy=true、latencyMs=745ms;
|
||||
- 再读缓存:fromCache=true(命中探测结果),耗时 3ms。
|
||||
- **结论**:服务端定时探测(5 分钟)+ 缓存机制生效;前端读缓存秒回,点击触发即时探测;验收通过。
|
||||
- **涉及文件**:无(验证记录)。
|
||||
|
||||
|
||||
### 2026-09-01 · 修复:store.market.json 膨胀(38MB,全市场行情入库)
|
||||
|
||||
- **问题**:store.market.json 膨胀到 38MB(50910 只股票行情)。
|
||||
- **根因**:MarketFeed WS 推送是全市场(whole),MarketDataHub.ingest 不过滤,把全市场 5 万+ code 写进缓存并持久化。
|
||||
- **修复**:MarketDataHub 增加 watchCodes 关注集合(前端查询 code + 持仓 code 自动加入);ingest 只保留关注集合内 code;写盘也只写关注集合(防抖)。MarketFeed prime 把持仓 code 加入 watch。
|
||||
- **清理**:删除膨胀的 store.market.json(修复后自动重建,只含持仓行情)。
|
||||
- **验证**:Node 模拟 WS 推 100 只 → 缓存只 3 只持仓 → store.market.json 只 3 条 ✅。
|
||||
- **涉及文件**:src/component/MarketDataHub.js、src/component/MarketFeed.js。
|
||||
|
||||
|
||||
### 2026-09-01 · 修复 v2:warmup 认可全市场导致 watchCodes 过滤失效
|
||||
|
||||
- **问题**:重启后 store.market.json 又膨胀到 38MB(50910 键)——watchCodes 过滤未生效。
|
||||
- **根因**:MarketDataHub.warmup 从磁盘加载旧数据时,把**所有 code 都加入 watchCodes**(this.watch(code))——磁盘历史认可了全市场,后续 WS 推送全部保留。
|
||||
- **修复**:warmup 只加载数据到内存 cache,**不加入 watchCodes**(watchCodes 只由「持仓 + 前端查询」驱动)。
|
||||
- **清理**:再次删除膨胀文件(重建后只含持仓 13 条)。
|
||||
- **涉及文件**:src/component/MarketDataHub.js。
|
||||
|
||||
|
||||
### 2026-09-01 · 修复 v3:setMarketQuotes 整体替换(清除旧键残留)
|
||||
|
||||
- **问题**:v2 后 store.market.json 仍含旧全量数据(100 条旧 + 新)。
|
||||
- **根因**:DataStore.setMarketQuotes 是**增量合并**(this.market[code]=snap),旧键永不清除——磁盘旧数据(如 50910 条)残留,每次合并越积越多。
|
||||
- **修复**:setMarketQuotes 改为**整体替换**(this.market = next),传入完整集合,清除不在其中的旧键。
|
||||
- **验证**:磁盘预置 100 旧 → warmup → 持仓查询 → WS 推送 → 写盘只 2 条(600719/300057),旧 100 条清除 ✅。
|
||||
- **清理**:删除真实膨胀文件(重启重建只含持仓)。
|
||||
- **涉及文件**:src/component/DataStore.js。
|
||||
|
||||
|
||||
### 2026-09-01 · 膨胀修复验收(宿主重启后)
|
||||
|
||||
- **结果**:store.market.json 从 38MB(50910 键)恢复为 **11KB(13 键 = 13 只持仓)**,无多余键。
|
||||
- **结论**:watchCodes 过滤 + warmup 不认可全市场 + setMarketQuotes 整体替换 三层修复全部生效,膨胀 bug 彻底解决,验收通过。
|
||||
- **涉及文件**:无(验证记录)。
|
||||
|
||||
|
||||
### 2026-09-01 · 决策:全市场缓存方案搁置(保持 watchCodes)
|
||||
|
||||
- **讨论**:老师考虑「全市场缓存」(前端任意 A股实盘查询秒回),vs 当前 watchCodes(持仓 + 前端查询自动累积)。
|
||||
- **实测发现**:QMT whole 推送 26,751 条中,**标准 A股仅 2,775 只(10.4%)**,其余 23,976 条为期权/衍生品/其他品种代码段(23x/10x/19x/16x 等)——A股真实约 5,400 只,推送全市场含大量非股票。
|
||||
- **结论(老师定)**:全市场缓存方案**搁置**,保持当前 watchCodes 方案(持仓 + 前端查询过的 code 自动累积,内存稳定 ~13 条);
|
||||
- **记录**:若未来启用全市场缓存,需只缓存标准 A股代码(正则过滤 60/688/000/001/002/003/300/301),并先解决 WS 推送稳定性(归属 QMT Bridge 工作空间)。
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# 验收标准:04-WS盘中价格实时更新
|
||||
|
||||
## 验收标准线
|
||||
|
||||
1. 3 个监控表格(全部持仓 / 手动做T / 网格超市)出现「现价」列,显示关注股票的 lastPrice(无数据时显示 —);
|
||||
2. 盘中价格变化**自动更新**(无需手动刷新)—— 观察价格列随时间变化;
|
||||
3. 红涨绿跌着色正确(现价 > 昨收 红色,< 昨收 绿色);价格变化时有轻微高亮提示;
|
||||
4. WS 连接与激活 QMT 配置联动:切换激活配置(host 变化)→ WS 自动切换到新 host;断线自动重连;
|
||||
5. 现有功能不回归:份额分配 / 策略管理 / 连接配置管理照常工作。
|
||||
|
||||
## 验收方法
|
||||
|
||||
- 打开 3 个表格,确认现价列出现且显示真实价格(对照 QMT 端行情);
|
||||
- 观察一段时间(约 30s),确认价格随推送自动变化;
|
||||
- 临时断网/杀 Bridge 进程 → 确认重连提示与恢复;
|
||||
- 切换激活 QMT 配置(GEMWIN ↔ 其他)→ 确认 WS 随之切换;
|
||||
- 操作份额添加/移出,确认表格功能正常。
|
||||
|
||||
## 验收目标
|
||||
|
||||
- 现价实时更新闭环完整:WS 推送 → 价格 Map → 表格行更新;
|
||||
- 关注列表自动覆盖 3 个表格出现的所有股票;
|
||||
- 交互(着色/高亮/重连提示)符合预期。
|
||||
@@ -0,0 +1,85 @@
|
||||
# 技术实现方案:05-交易记录接入(订单与成交 + 日期导航)
|
||||
|
||||
> 依据:PLAN-006 | 需求:R-007 | 设计约束:技术约束-001/003/004/010、产品约束-007 方向语义
|
||||
|
||||
## 技术选型
|
||||
|
||||
- **数据源**:复用 QmtBridgeRestDataSource(REST 直连,技术约束-003),新增 3 方法;
|
||||
- **服务端 API**:webServer 自开路由(技术约束-004),新增 api/trades.js 领域;
|
||||
- **前端数据流**:服务端中转 + 前端轮询(技术约束-010 思路,复用 market-snapshot 模式);
|
||||
- **UI**:React 组件 + 现有表格风格(LoadState 三态 / 内联样式 / Toast);
|
||||
- **时间段查询**(v3,2026-09-01):RangeSelector 组件(今日/本周/上周快捷 + 手动起止日期),替代单日导航。
|
||||
|
||||
## 架构设计
|
||||
|
||||
### 服务端
|
||||
|
||||
```
|
||||
QmtBridgeRestDataSource.getOrders({code,status}) → GET /trade/orders → 语义化映射 order 列表
|
||||
QmtBridgeRestDataSource.getTrades() → GET /trade/trades → 语义化映射 trade 列表
|
||||
QmtBridgeRestDataSource.getTradingDates({s,e}) → GET /data/calendar/trading_dates → dates[]
|
||||
|
||||
api/trades.js(新增领域):
|
||||
orders → dataSource.getOrders(args)
|
||||
trades → dataSource.getTrades()
|
||||
trading-dates → dataSource.getTradingDates(args)
|
||||
(registerApi 合并 TRADE_METHODS + HANDLERS)
|
||||
```
|
||||
|
||||
**语义化映射(核心)**:
|
||||
|
||||
```
|
||||
order: { orderId, code, name, exchange, direction(48买/49卖), status, orderVolume,
|
||||
tradedVolume, totalVolume, limitPrice, tradedPrice, amount, insertTime }
|
||||
trade: { tradeId, orderId, code, name, exchange, direction, price, volume, amount,
|
||||
commission, tradeTime }
|
||||
```
|
||||
|
||||
**方向判定**:m_nOffsetFlag(48=买入/49=卖出)为主 + m_strOptName 交叉验证(R-007 Q3 闭环)。
|
||||
|
||||
### 客户端(单表合并)
|
||||
|
||||
```
|
||||
TradeRecordsTab(标题 + 时间段同一行 + 单表)
|
||||
├─ RangeSelector:trading-dates → 快捷范围(今日/本周/上周)+ 手动起止日期
|
||||
│ 今日=最近交易日单日(完整互斥时间段);本周=所在自然周周一~周五(不考虑周六日);本月=所在自然月1号~月末
|
||||
├─ 合并逻辑:fetch orders+trades(带 start/end)→ 按 orderId 分组 → 聚合
|
||||
│ 聚合:成交量=Σ、成交均价=加权、成交额=Σ、手续费=Σ
|
||||
├─ OrderRow:委托主行(聚合成交),点击展开/折叠
|
||||
└─ TradeDetailRows:展开的每笔成交明细
|
||||
```
|
||||
|
||||
**合并规则**:
|
||||
- 主行 = 一笔委托(orderId 唯一);
|
||||
- 成交聚合:m_strOrderSysID 匹配的 trades 聚合进主行(加权均价 + 求和);
|
||||
- 无成交(待报/已撤/废单):成交量/均价/金额/手续费显示 —;
|
||||
- 展开:逐笔渲染成交(时间/价/量/额/手续费/成交号)。
|
||||
|
||||
**排序**:委托主行默认按时间降序(最新在前);表头点击排序(时间/代码/方向/状态/委托量/成交量/金额),再次点击切换升/降序。
|
||||
|
||||
**轮询**:仅「今日」范围(最近交易日单日)3-5s 轮询;切换时间段取消轮询、按 range 一次性查询(历史接口就绪后接入 start/end 实际过滤)。
|
||||
|
||||
**时间段查询(v3 定稿)**:
|
||||
- 服务端 orders/trades 端点接收 start/end(透传,QMT Bridge 历史接口就绪后生效);
|
||||
- 前端 RangeSelector:三个快捷按钮 + 两个 date input;
|
||||
- 历史范围(非今日)本期占位「历史数据接口开发中」。
|
||||
|
||||
## 涉及设计约束
|
||||
|
||||
| 约束 | 内容 |
|
||||
|---|---|
|
||||
| 技术约束-001 | 统一数据源抽象:交易数据走 QmtBridgeRestDataSource 适配器 |
|
||||
| 技术约束-003 | 直连 QMT Bridge REST(不经过 MCP) |
|
||||
| 技术约束-004 | webServer 自开路由 /odl/api/*(不占 /api interceptor) |
|
||||
| 技术约束-010 | 服务端中转 + 前端轮询(复用市场模式) |
|
||||
| 产品约束-007 | 方向着色沿用红/绿习惯语义(交易:红买绿卖) |
|
||||
|
||||
## 实现步骤
|
||||
|
||||
1. 文档骨架:PLAN-006 + 迭代 05(本步);
|
||||
2. 数据源:getOrders/getTrades/getTradingDates + 语义化映射;
|
||||
3. 服务端 API:api/trades.js 三端点 + 领域分发;
|
||||
4. 客户端合并逻辑:fetch 两接口 → 分组 → 聚合;
|
||||
5. UI:DateNav + OrderRow + TradeDetailRows + TradeRecordsTab + 排序;
|
||||
6. 构建安装:pnpm run build + 刷新/重启;
|
||||
7. 验收 + 复盘。
|
||||
@@ -0,0 +1,38 @@
|
||||
# 迭代 05 复盘:交易记录接入(订单与成交 + 日期导航)(2026-09-01)
|
||||
|
||||
> 复盘日期:2026-09-01 | 迭代状态:**实现完成,待老师验收确认**
|
||||
> 关联需求:R-007(交易记录接入,范围已定稿)
|
||||
> 关联计划:PLAN-006(计划-交易记录接入.md)
|
||||
|
||||
## 结果
|
||||
|
||||
迭代 05 达成:交易记录 tab 接入当日委托(/trade/orders)+ 当日成交(/trade/trades),单表合并展示(委托主行 + 按 m_strOrderSysID 展开成交明细),日期导航(交易日历驱动,历史接口占位),排序默认降序可手动。
|
||||
|
||||
## 过程事实
|
||||
|
||||
1. **需求定稿(R-007)**:老师指令接入订单与成交 → 技术研究(API 字段全表 / 方向判定澄清 / 数据时效研究 / sample data)→ 范围定稿(当日订单+成交 + 日期导航)→ 展示设计定稿(单表合并 + 排序降序);
|
||||
2. **方向判定闭环**:m_nDirection=48 恒为操作标记(官方 innerApi 字典),真实方向在 m_nOffsetFlag(48买/49卖)+ m_strOptName 交叉验证;
|
||||
3. **数据时效确认**:/trade/orders 与 /trade/trades 仅当日数据(get_trade_detail_data 读客户端缓存),历史查询需老师完善 QMT Bridge 接口;
|
||||
4. **服务端实现**:QmtBridgeRestDataSource 新增 getOrders/getTrades/getTradingDates(语义化映射)+ api/trades.js 领域(orders/trades/trading-dates 端点);
|
||||
5. **客户端实现**:TradeRecordsTab(单表合并 + 排序 + 轮询 4s)+ DateNav(交易日历导航,当日默认,历史占位);
|
||||
6. **验证**:typecheck 通过、构建成功、数据源三方法端到端实测通过(博纳影业卖出 200 股 @6.00,手续费 0.852)、合并逻辑多笔成交场景验证通过(加权均价 6.03);
|
||||
7. **未做**:历史数据查询实现(等老师 QMT Bridge 接口)、本地持久化(Q5 本期不做)、按策略过滤(Q10 后续)。
|
||||
|
||||
## 经验(待沉淀)
|
||||
|
||||
- **方向判定**:大 QMT m_nDirection 恒 48 是操作标记,m_nOffsetFlag 才是方向(48买/49卖)——实现时勿踩坑;
|
||||
- **手续费字段**:m_dCommission 与 m_dComssion(拼写变体)同值,取值优先 m_dCommission;
|
||||
- **前端轮询**:静默刷新不闪 loading(setOrders 直接更新,不置 loading=true);日期切换时清理轮询定时器;
|
||||
- **服务端/客户端加载时机**:服务端改动需宿主重启,客户端 bundle 刷新即载(沿用迭代 03/04 经验)。
|
||||
|
||||
## 遗留/后续
|
||||
|
||||
1. **历史成交接口**:老师完善 QMT Bridge 后,DateNav 历史日期从占位切换为真实查询(trading-dates 已就绪,orders/trades 加日期参数即可);
|
||||
2. **本地持久化**:跨日复盘需按日落盘(Q5),待历史接口就绪后评估;
|
||||
3. **按策略过滤**:Q10 后续迭代(strategy_name 与插件策略体系打通)。
|
||||
|
||||
## 验收状态
|
||||
|
||||
- 服务端/数据源端到端验证:✅(真实数据实测通过)
|
||||
- 前端 UI:待老师页面验收(客户端 bundle 已构建,刷新页面即载)
|
||||
- 完整验收:对照 验收标准.md 逐条核验(待老师确认)
|
||||
@@ -0,0 +1,27 @@
|
||||
# 迭代目标:05-交易记录接入(订单与成交 + 日期导航)
|
||||
|
||||
## 目标
|
||||
|
||||
在「交易记录」tab 接入 QMT Bridge **当日委托(/trade/orders)与当日成交(/trade/trades)**:**单表合并**展示(委托主行 + 按 m_strOrderSysID 展开成交明细)+ **基于交易日历的日期导航**(当日默认选中,历史接口占位)。
|
||||
|
||||
## 目标描述
|
||||
|
||||
- 范围:R-007(范围已定稿 2026-09-01);不新增其他功能域;
|
||||
- 要解决的问题:交易记录 tab 当前为占位(PlaceholderTab),无当日订单/成交数据展示,无法支撑交易复盘(目标-006);
|
||||
- 引用需求:**R-007(范围定稿,2026-09-01)**;
|
||||
- 实现方式:服务端 REST 直连 QMT Bridge(技术约束-003)+ 前端轮询(技术约束-010 思路)+ 单表合并 UI;
|
||||
- 日期导航:复用 /data/calendar/trading_dates,为老师完善历史成交接口预留。
|
||||
|
||||
## 目标讨论过程
|
||||
|
||||
- 2026-09-01 老师提出需求:接入订单与成交数据,开始需求和技术研究(R-007 入池);
|
||||
- 2026-09-01 技术研究:API 面实测(orders/trades 字段全表)、方向判定澄清(m_nOffsetFlag + m_strOptName)、数据时效研究(仅当日)、sample data 沉淀;
|
||||
- 2026-09-01 范围定稿:当日订单+成交接入 + 日期导航(老师将完善历史接口);
|
||||
- 2026-09-01 展示设计定稿:**单表合并**(委托主行 + 展开成交明细)+ **排序默认降序可手动调整**;
|
||||
- 关键决策:Q1-Q11 全部有结论(两块合一、3-5s 轮询、m_nOffsetFlag 判方向、本期不持久化、红买绿卖、P1 等)。
|
||||
|
||||
## 对老师的配合需求
|
||||
|
||||
- QMT Bridge(激活配置)可达,验证当日订单/成交返回真实数据;
|
||||
- 构建安装后重载页面配合验证;
|
||||
- 历史成交接口完善后接入日期导航历史查询(本期仅占位)。
|
||||
@@ -0,0 +1,28 @@
|
||||
# 验收标准:05-交易记录接入(订单与成交 + 日期导航)
|
||||
|
||||
## 验收标准线
|
||||
|
||||
1. 「交易记录」tab 显示**当日委托**(单表合并视图):每行一笔委托,展示时间/代码/名称/方向/状态/委托量/成交量/委托价/成交均价/成交额/手续费;
|
||||
2. **合并正确**:委托行聚合成交(按 m_strOrderSysID);有成交的委托行可**展开**查看每笔成交明细(成交时间/价/量/额/手续费/成交号);聚合金额与逐笔合计一致;
|
||||
3. **未成交委托**(待报/已报/已撤/废单):成交量/均价/金额/手续费显示 —;状态列中文映射正确(未报/待报/已报/部成/已成/已撤/废单等);
|
||||
4. **方向**:红买绿卖(红=买、绿=卖);方向判定正确(对照 m_strOptName);
|
||||
5. **时间段查询**:标题 + 时间段控件同一行;快捷按钮 今日/本周/本月 可快速切换(基于交易日历最近交易日计算);手动起止日期可用;非今日范围展示「历史数据接口开发中」占位;
|
||||
6. **排序**:委托主行默认降序(最新在前);点击表头可手动排序(升/降切换);
|
||||
7. **刷新**:当日数据 3-5s 自动刷新(无需手动);切换日期取消轮询;
|
||||
8. **不回归**:持仓/策略/行情/连接配置等现有功能正常。
|
||||
|
||||
## 验收方法
|
||||
|
||||
- 打开交易记录 tab,对照 QMT 端确认当日委托/成交数据真实显示(实测博纳影业 001330.SZ 限价卖出 200 股);
|
||||
- 点击有成交的委托行,确认展开显示每笔成交明细;
|
||||
- 检查未成交委托(若有)成交列显示 —;
|
||||
- 点击 今日/本周/上周 快捷按钮,确认时间段切换正确(今日=最近交易日单日、本周=周一~周五(交易日,不考虑周六日)、本月=完整自然月(1号~月末);三范围互斥,点哪个激活哪个);手动改起止日期确认生效;历史范围显示占位提示;
|
||||
- 点击表头排序,确认升/降序切换与默认降序;
|
||||
- 观察 3-5s 轮询刷新(数据变化自动更新);
|
||||
- 回归:全部持仓 / 策略 / 行情 / QMT 连接配置正常。
|
||||
|
||||
## 验收目标
|
||||
|
||||
- 交易记录闭环完整:REST 查询 → 语义化映射 → 单表合并 → 展开明细 → 日期导航;
|
||||
- 支撑交易复盘(目标-006):当日委托/成交全貌可见;
|
||||
- 交互(合并/展开/排序/导航/着色)符合定稿设计。
|
||||
Reference in New Issue
Block a user