This commit is contained in:
2026-08-29 16:20:33 +08:00
commit cba31428dc
52 changed files with 6553 additions and 0 deletions
@@ -0,0 +1,171 @@
# 迭代 01 实施步骤规划(分步执行)
> 2026-08-27 | 按步骤分批推进,每步有独立产出与验证点
> 关联:技术实现方案.md、程序结构设计.md
## 总览
| 步骤 | 内容 | 产出 | 验证点 | 依赖 |
|---|---|---|---|---|
| S1 | 插件骨架 | package.json + 目录 + 空 apply | 包可被识别 | - |
| S2 | 数据源适配 | qmt-bridge-rest.jsREST 直连) | fetch QMT 返回真实数据 | S1 |
| S3 | 设置管理 | settings.js(策略注册) | 设置中出现策略配置 | S1 |
| S4 | 本地存储 | storage.jsallocations.json | 读写持久化 | S1 |
| S5 | 分仓逻辑 | manager.js(份额分配/聚合) | 单元验证分仓计算 | S2+S4 |
| S6 | 服务端 RPC API | api.jsconnection.rpc | RPC 端点可调用 | S2+S3+S5 |
| S7 | 客户端 tab | client/conversation.view 注册) | tab 出现在对话/轨迹/上下文后 | S3+S6 |
| S8 | 宿主挂载+联调 | cordis.patch.yml + 安装 | 全链路可用 + 验收 | S1-S7 |
## 分步细节
### S1:插件骨架
- package.jsonname/version/main/files + `dsh.client` 声明(web 平台)
- 目录:src/、lib/、src/data-source/、src/position/、src/client/
- src/index.jsapply(ctx) 空实现 + name/inject/apply 导出
- **验证**:node 语法检查 + 包结构完整
### S2:数据源适配(qmt-bridge-rest.js
- src/data-source/types.js:统一数据模型(Position/AssetSummary/DataSource 接口)
- src/data-source/qmt-bridge-rest.js
- baseUrl 配置(默认 http://192.168.3.43:8610
- getPositions() / getAsset() / health()
- 字段映射(语义字段优先,m_ 回退)
- **验证**:独立脚本调用,返回真实持仓/资金数据(对齐快照)
### S3:设置管理(settings.js
- ctx.settings.register('one-divine-lot', schema)
- 策略 schemastrategies: [{id, name, visible, order}]
- 预置:网格超市、手动做T
- **验证**:settings 可读取默认策略;更新后值变化
### S4:本地存储(storage.js
- 路径:~/.dsh/one-divine-lot/allocations.json
- load()/save()/get()/set()
- 原子写(临时文件+rename
- **验证**:写入后文件存在,重启读回一致
### S5:分仓逻辑(position/manager.js
- getAllPositions():全量持仓
- getStrategyPositions(strategyId):按分配份额过滤
- getSummary():各策略份额/市值/盈亏 + 未分配
- allocate():份额分配(校验和 ≤ 总持仓)
- **验证**:单元测试分仓计算(如 1000 股 → 网格 600 + 做T 400
### S6:服务端 RPC APIapi.js
- ctx.connection.rpc.intercept('/api', matcher, handler)
- 端点:strategies/positions/allocations/summary/strategy/:id/positions
- **验证**:通过 DSH RPC 调用端点返回正确数据
### S7:客户端 tabclient/
- client/index.js:读策略设置 → 注册 conversation.view entries
- client/views/StrategyTab.jsx:渲染策略持仓(调 RPC API)
- **验证**:tab 出现在对话/轨迹/上下文后,点击加载数据
### S8:宿主挂载 + 联调验收
- cordis.patch.yml insert 插件行
- 插件安装到 profilepnpm link 等)
- 全链路验收(对照验收标准.md
## 建议执行批次
- **批次 1(S1-S2)**:骨架 + 数据源 —— 打通「插件→QMT REST」数据读取(纯服务端,可独立验证)
- **批次 2S3-S5)**:设置 + 存储 + 分仓逻辑 —— 核心业务逻辑
- **批次 3S6**:RPC API —— 打通「服务端能力 → DSH 通道」
- **批次 4S7-S8**:客户端 tab + 宿主挂载 —— 全链路 UI 呈现
## 前置技术确认(编码前)
1. conversation.view 注册签名(读 dsh-client-ui-conversation slots.d.ts
2. 插件安装到 profile 的方式(pnpm link / 本地路径)
3. settings 注册在 host plane 的注入方式(ctx.settings 是否对插件可用)
## 执行进度(2026-08-27
### ✅ 批次 1-2S1-S4)已完成
| 步骤 | 状态 | 验证结果 |
|---|---|---|
| S1 插件骨架 | ✅ 完成 | package.jsondsh.client 声明)+ src/index.js 空入口,语法通过 |
| S2 数据源适配 | ✅ 完成 | qmt-bridge-rest.js 真实调通 QMT REST:可用性✅、资金✅(总资产129,412.25)、持仓✅(12只,市值85,948 |
| S3 设置管理 | ✅ 完成 | settings.js:默认策略(网格超市/手动做T)、可见过滤、排序,逻辑验证通过 |
| S4 本地存储 | ✅ 完成 | storage.js:读写/持久化/覆盖/删除验证通过(1000股→网格600+做T400 校验通过) |
### 产出文件
- src/index.js(接入 S2-S4
- src/settings.js
- src/storage.js
- src/data-source/types.js
- src/data-source/qmt-bridge-rest.js
### 下一步(批次 2 剩余 / 批次 3)
- S5 分仓逻辑(position/manager.js
- S6 服务端 RPC APIconnection.rpc
- S7-S8 客户端 + 宿主挂载
### ✅ S5 已完成(2026-08-27
| 项 | 结果 |
|---|---|
| S5 分仓逻辑 | ✅ 完成 position/manager.js:全量持仓/策略持仓/未分配/添加/移出/摘要 |
| 验证 | ✅ 真实 QMT 数据:添加(600+400)、超限拦截(>1800拒绝)、移出(400→200)、移出超限拦截、摘要(份额汇总+未分配=1000)全部通过 |
| 设计依据 | S5 讨论定稿(Q1股数/Q2允许未分配/Q3人为分组/Q4无统计/Q5策略tab内编辑) |
### 待办(批次 3-4
- S6 服务端 RPC APIconnection.rpc
- S7 客户端 tab(全部持仓/网格策略持仓/做T持仓)
- S8 宿主挂载 + 联调验收
### ✅ S6 已完成(2026-08-27
| 项 | 结果 |
|---|---|
| S6 服务端 RPC API | ✅ 完成 src/api.jsconnection.rpc.intercept('/api') 暴露 8 个端点 |
| 端点 | positions / strategies / strategy-positions / unallocated / summary / add-shares / remove-shares+ 未知端点错误) |
| 验证 | ✅ 全部端点回归通过;错误码精确(share-limit / share-exceed / unknown-endpoint |
| 模式 | 参照 dsh-api-gatewayintercept('/api', matcher, handler, {authority:'trusted-host'})RpcResult 格式 {ok, value}/{ok:false, error} |
### 待办(批次 4
- S7 客户端 tab(全部持仓/网格策略持仓/做T持仓)
- S8 宿主挂载 + 联调验收
### 🔄 S7 客户端 tab(部分完成,2026-08-27
**已完成**
- src/client/index.js:客户端插件入口(inject slots/connection,注册 3 个 conversation.view tab:全部持仓/网格策略持仓/做T持仓)
- src/client/views/AllPositionsTab.jsx:全部持仓组件(全量+份额标注)
- src/client/views/StrategyTab.jsx:策略持仓组件(添加/移出交互)
- src/client/views/connection.jsRPC 调用 hook
**待完成**
- 构建配置(tsdown/tsconfig + 客户端依赖安装)
- 客户端 bundle 构建 + 语法/类型验证
- 与宿主集成测试(S8 时统一)
**关键约束**S7 现实情况):
- 客户端插件需要 react/slots/runtime 等依赖(peerDependencies),需安装;
- JSX 需要构建工具(tsdown)处理成浏览器 bundle
- 构建环境与宿主挂载(S8)联动,建议 S7+S8 一起验收;
- 客户端代码的完整运行验证依赖 DSH web 宿主重启加载。
### ✅ S7-S8 已完成(2026-08-28
| 项 | 状态 |
|---|---|
| S7 客户端 tab | ✅ 完成:全部持仓/网格策略持仓/做T持仓 三个 conversation.view tab |
| S7 设置菜单 | ✅ 完成:settings.section「神之一手」(策略列表 + 显示开关) |
| S8 宿主挂载 | ✅ 完成:插件 link 到 web profile + bundles 加入 + bundle patch 挂载 |
| 客户端 bundle | ✅ __ModuleLoader__.load 包装(scripts/wrap-client.mjs |
| 服务端 API | ✅ webServer /odl/api/* 路由(8 端点)—— 修正:不用 RPC(与 api-gateway 冲突) |
### 已解决的问题(实现过程中的关键坑)
1. **RPC 冲突**/api 通道只能一个 interceptorapi-gateway 占用)→ 改用 webServer 自开路由
2. **slots.register 签名**:component 必须是第二参数 → React #130 崩溃
3. **客户端 bundle 格式**:必须 __ModuleLoader__.load 包装
4. **settings schema**:必须 schemastery z.object(函数式 schema
5. **endpoint 前缀**:客户端传 one-divine-lot/* 需剥离
### 验证结果(2026-08-28,真实 QMT 数据)
- positions/summary/strategies/add-shares/remove-shares 全部 HTTP 200
- 12 只真实持仓显示正常(老师确认)
- 份额添加/移除/持久化工作正常
- 分仓存储文件 allocations.json 已创建
@@ -0,0 +1,116 @@
# 技术实现方案:01-分仓管理工具(迭代设计 v2)
## 一、技术选型
| 项 | 选型 | 依据 |
|---|---|---|
| 数据源 | QMT Bridge **RESTful 接口**http://192.168.3.43:8610OpenAPI v3 | 技术约束-003(插件直连 REST,非 MCP |
| HTTP 客户端 | Node 内置 fetchNode 22+ | 零依赖 |
| 设置管理 | ctx.settings 注册 namespace | DSH 内置 settings 服务 |
| 本地存储 | JSON 文件(~/.dsh/one-divine-lot/ | 分仓数据本地化 |
| 客户端 UI | dsh.client 平台 + React 组件 | conversation.view 插槽扩展 tab 栏 |
| 宿主集成 | cordis.patch.yml insertweb profile patch 层) | 外部插件挂载 |
## 二、架构分层
```
[DSH 主窗口 conversation 区域]
└─ view tabs:对话 │ 轨迹 │ 上下文 │ ➕ 策略标签(网格超市 / 手动做T ...)
└─ 每个策略 tab = 一个 conversation.view 插槽 entryViewTab: id + label
└─ 渲染组件:加载该策略下的持仓数据
│ API 请求
[DSH 宿主] ◀── cordis.patch.yml insert 挂载插件行
[插件核心(apply ctx]
├─ settings:策略 CRUD + 显示开关(ctx.settings.register
├─ storage:策略配置 + 分仓数据 JSON 持久化
├─ apiconnection.rpc /api 通道端点(持仓、策略、分仓查询)
├─ data-sourceQmtBridgeRestDataSourcefetch REST
└─ position:分仓逻辑(持仓 ↔ 策略份额分配)
```
## 三、模块详细设计
### 模块 1:数据源适配器(QmtBridgeRestDataSource
- 基础地址:http://192.168.3.43:8610(插件配置项,可改)
- 端点封装:
- getAsset() → GET /trade/asset(资金摘要)
- getPositions() → GET /trade/positions(全量持仓,含语义字段 stock_code/volume/available/avg_price/price/market_value/profit/profit_pct
- getOrders() → GET /trade/orders(预留)
- getTrades() → GET /trade/trades(预留)
- health() → GET /health
- 字段映射:优先语义字段(stock_code 等),回退 m_ 原始字段
- 统一返回 Position[] / AssetSummary(技术约束-001
### 模块 2:设置管理(策略=标签)
- ctx.settings.register('one-divine-lot', schema)
- schema 字段:
- strategies: array of { id, name, visible(默认true), order }
- 初始预置:网格超市、手动做T(老师可改)
- 用户经 DSH 设置界面维护:增/删/改策略、显示隐藏开关、排序
### 模块 3:分仓数据存储(本地)
- 文件:~/.dsh/one-divine-lot/allocations.json
- 结构:{ [positionCode]: { [strategyId]: shares } }
- 示例:{ "600719.SH": { "grid-supermarket": 1000, "manual-t": 800 } }
- 约束:各策略份额之和 ≤ 总持仓(允许未分配余量)
- API:读/写分仓分配(服务端存储,QMT 只提供全量持仓)
### 模块 4:客户端 tab 栏(conversation.view 扩展)
- package.json 声明 dsh.clientweb 平台)
- 客户端插件注册 conversation.view entries
- 读取设置中 visible=true 的策略,每个策略注册一个 ViewTab
- 注册在既有 tab(对话/轨迹/上下文)之后
- 每个策略 tab 渲染:
- 该策略下持仓列表(从服务端 API 获取)
- 展示:代码/名称/份额/市值/盈亏
- 未分配持仓展示在「未分配」区域
- 显示/隐藏:设置变更 → 重新注册/注销 view entry(或按 visible 过滤渲染)
### 模块 5:服务端 API(供客户端,RPC 通道)
> 修正:使用 DSH 原生 RPCconnection.rpc),不自开 HTTP 路由。
- `ctx.connection.rpc.intercept('/api', matcher, handler, {authority:'trusted-host'})`
- 端点:
- `one-divine-lot/strategies` → 策略列表(含 visible
- `one-divine-lot/positions` → 全量持仓(QMT
- `one-divine-lot/allocations` → 分仓分配
- `one-divine-lot/allocations/update` → 更新分仓分配(份额设置,二期)
- `one-divine-lot/strategy/:id/positions` → 某策略下的持仓汇总
## 四、宿主挂载
- 修改 ~/.dsh/profiles/web/cordis.patch.ymlinsert 插件行:
```yaml
- insert:
- id: one-divine-lot
name: "one-divine-lot" # 插件包名(本地安装或 link
config: { ... }
```
- 插件包需在 profile 的 node_modules 中可用(pnpm link / 本地安装)
- 遵循 editing-cordis-compositions:不改 shipped preset,走用户 profile patch 层
## 五、实现步骤
1. **服务端骨架**:插件包结构 + package.jsondsh.client 声明)+ apply(ctx) 入口
2. **数据源适配**QmtBridgeRestDataSourceREST 直连,字段映射)
3. **设置管理**ctx.settings.register(策略 schema
4. **存储**allocations.json 读写
5. **服务端 API**connection.rpc.intercept('/api') 端点(策略/持仓/分仓)
6. **客户端插件**conversation.view entries 注册 + 策略持仓渲染组件
7. **宿主挂载**cordis.patch.yml insert + 插件安装
8. **验收**:设置→tab 显示→持仓加载→持久化
## 六、涉及设计约束
- 技术约束-001(统一数据源抽象)/ 003(REST 直连)
- 产品约束-001(全量持仓)/ 002(标签体系)/ 003(标签自定义+显示开关)
## 七、风险与开放项
1. conversation.view 插槽注册的精确 APIregister 签名)需在实现时读取 slots.d.ts 确认;
2. ~~服务端 API 与客户端通信:走 DSH webServer 标准路由~~ → 已确认走 DSH 原生 RPC 通道(connection.rpc.intercept);
3. 策略 tab 的渲染组件挂载到 conversation.view 后,其数据刷新时机(实时/手动/切换时);
4. 分仓份额编辑入口:设置界面 or tab 内直接编辑(先做 tab 内只读展示,编辑入口二期);
5. 插件本地安装方式:pnpm link 到 profile node_modules(需确认 profile 的包管理方式)。
@@ -0,0 +1,42 @@
# 数据快照:QMT 账户资金与持仓(验证用)
> 生成时间:2026-08-27 用途:验证 QMT Bridge MCP 数据连通(迭代 01
> 数据来源:QMT Bridge MCPqmt_asset / qmt_positions
> 注意:此为验证时刻的快照,非实时数据;仅作迭代验收与复盘参照。
## 账户资金摘要
| 字段 | 值 |
|---|---|
| 账户 | 8882874667 |
| 状态 | 登录成功 |
| 交易日 | 20260827 |
| 总资产 | 129,166.25 |
| 可用资金 | 43,460.25 |
| 股票市值 | 85,702.00 |
| 持仓盈亏(浮动) | -10,057.60 |
| 账户余额 | 129,166.25 |
## 持仓明细(12 只)
| 代码 | 名称 | 数量 | 可用 | 均价 | 现价 | 市值 | 盈亏 | 盈亏% |
|---|---|---|---|---|---|---|---|---|
| 600719.SH | 大连热电 | 1800 | 1800 | 7.233 | 7.07 | 12,726 | -54 | -0.41% |
| 601117.SH | 中国化学 | 600 | 600 | 8.293 | 7.54 | 4,524 | -24 | -0.48% |
| 603028.SH | 赛福天 | 800 | 800 | 8.233 | 7.01 | 5,608 | -72 | -1.09% |
| 001330.SZ | 博纳影业 | 1200 | 1200 | 6.696 | 5.07 | 6,084 | -36 | -0.45% |
| 002065.SZ | 东华软件 | 800 | 800 | 7.376 | 6.90 | 5,520 | +8 | +0.14% |
| 002135.SZ | 东南网架 | 1000 | 1000 | 7.491 | 6.72 | 6,720 | +160 | +2.14% |
| 002339.SZ | 积成电子 | 800 | 800 | 8.463 | 7.42 | 5,936 | 0 | 0.00% |
| 002347.SZ | 泰尔股份 | 1000 | 1000 | 7.503 | 6.06 | 6,060 | +20 | +0.27% |
| 002478.SZ | 常宝股份 | 800 | 800 | 7.892 | 6.82 | 5,456 | +8 | +0.13% |
| 300008.SZ | 天海防务 | 1200 | 1200 | 7.481 | 6.19 | 7,428 | +12 | +0.13% |
| 300057.SZ | 万顺新材 | 2000 | 2000 | 6.881 | 6.69 | 13,380 | +240 | +1.74% |
| 300426.SZ | 华智数媒 | 1000 | 1000 | 6.424 | 6.26 | 6,260 | -120 | -1.87% |
## 汇总
- 持仓数:12 只
- 总市值:85,702.00
- 总盈亏(浮动):+142.00(个股浮动盈亏合计)
- 备注:账户持仓盈亏 -10,057.60 与个股合计存在口径差异(账户级含其他因素),验收时以账户级为准
@@ -0,0 +1,191 @@
# 程序结构设计:01-分仓管理工具
> 迭代设计 v2 的程序结构细化 2026-08-27
## 一、整体目录结构
```
one_divine_lot/
├── package.json # 插件元数据 + dsh.client 声明(web 平台)
├── src/ # 源码(服务端 + 客户端)
│ ├── index.js # 插件入口:apply(ctx),组装各模块
│ ├── settings.js # 设置管理:ctx.settings.register(策略 CRUD + 开关)
│ ├── storage.js # 本地存储:分仓分配 JSON 读写
│ ├── api.js # 服务端 RPC APIconnection.rpc.intercept('/api') 端点
│ ├── data-source/
│ │ ├── types.js # 统一数据模型(Position / AssetSummary / DataSource 接口)
│ │ └── qmt-bridge-rest.js # QMT Bridge REST 适配器(fetch 直连)
│ ├── position/
│ │ └── manager.js # 分仓逻辑:持仓↔策略份额分配、聚合视图
│ └── client/
│ ├── index.js # 客户端插件入口:注册 conversation.view entries
│ └── views/
│ ├── StrategyTab.jsx # 策略标签 tab 组件(渲染该策略持仓)
│ └── PositionList.jsx # 持仓列表子组件
├── lib/ # 构建输出(与 src 结构对应)
│ ├── index.js
│ ├── settings.js
│ ├── storage.js
│ ├── api.js
│ ├── data-source/
│ ├── position/
│ └── client/ # 客户端 bundle(浏览器端加载)
└── docs/ # 神之一手框架文档(既有)
```
## 二、模块职责与依赖
### 1. index.js(插件入口)
```js
const name = 'one-divine-lot';
const inject = ['tools', 'settings', 'webServer']; // 依赖 DSH 服务
async function apply(ctx, config) {
const settings = registerSettings(ctx); // 设置管理
const storage = new Storage(config); // 本地存储
const dataSource = new QmtBridgeRestDataSource(config); // REST 数据源
const manager = new PositionManager({ dataSource, storage }); // 分仓逻辑
registerApi(ctx, manager); // 服务端 API
registerClient(ctx); // 客户端插件(dsh.client
ctx.effect(() => () => { /* 清理 */ });
}
export { name, inject, apply };
```
### 2. settings.js(设置管理)
- `ctx.settings.register('one-divine-lot', schema)`
- schemastrategies: array[{ id, name, visible, order }]
- 预置:网格超市、手动做T
- 提供 getStrategies() / watchStrategies()(客户端 API 读取)
### 3. storage.js(本地存储)
- 路径:`~/.dsh/one-divine-lot/allocations.json`
- 结构:`{ [positionCode]: { [strategyId]: shares } }`
- APIload() / save() / get(code) / set(code, strategyShares)
### 4. data-source/qmt-bridge-rest.jsREST 数据源)
- baseUrlhttp://192.168.3.43:8610config 可配)
- getPositions() → GET /trade/positions → Position[]
- getAsset() → GET /trade/asset → AssetSummary
- 字段映射:语义字段优先,m_ 回退(技术约束-001/003
### 5. position/manager.js(分仓逻辑)
> 2026-08-27 S5 讨论定稿:
> - Q1 按股数分配;Q2 允许未分配余额(sum ≤ 总持仓);Q3 策略仅为人为分组(无策略逻辑);
> - Q4 不做统计(无市值/盈亏/占比聚合);Q5 UI = 策略 tab 内编辑(添加默认0起填 + 指定数量移出)。
- getAllPositions():全量持仓(QMT,真实数据)
- getPosition(code):单只持仓
- getStrategyPositions(strategyId):某策略下的持仓(读分配份额,过滤出份额>0的标的)
- getUnallocated():未分配持仓(总持仓 - 各策略份额之和 > 0 的标的)
- addToStrategy(code, strategyId, shares):添加到策略(**份额从0起填**,更新该策略份额,并刷新已分配)
- removeFromStrategy(code, strategyId, shares):从策略移出(**指定数量**,份额减少,回到未分配)
- getSummary():返回 { 全部持仓, 各策略份额, 已分配合计, 未分配 }(仅份额,无统计)
- 校验:单策略份额 ≤ 总持仓;已分配合计 ≤ 总持仓(允许未分配余量)
### 6. api.js(服务端 RPC API —— DSH 原生通道)
> **修正(2026-08-27**:使用 DSH 原生 RPCconnection.rpc.intercept('/api')),不自开 HTTP 路由。
```js
ctx.inject(['connection'], (connectionCtx) => {
connectionCtx.connection.rpc.intercept('/api', matcher, handler, { authority: 'trusted-host' })
})
```
- 端点(endpoint 命名,channel 相对路径):
- `one-divine-lot/strategies` → 策略列表(含 visible
- `one-divine-lot/positions` → 全量持仓(QMT
- `one-divine-lot/allocations` → 分仓分配
- `one-divine-lot/summary` → 分仓聚合视图
- `one-divine-lot/strategy/:id/positions` → 某策略持仓
- 职责:接收 DSH RPC 请求 → 调 manager/data-source(统一接口)→ 返回数据
- **不是请求本身**:真正发 HTTP 到 QMT 的是 data-source/qmt-bridge-rest.js
### 7. client/index.js(客户端插件)
- package.json dsh.client 声明(web 平台)→ DSH clientModules 自动加载
- **tab 结构(2026-08-27 Q5 确认)**
- 全部持仓:显示全量真实持仓,已分配部分标注(网格 X / 做T Y / 未分配 Z)
- 网格策略持仓:初始为空,可从全部持仓添加
- 做T持仓:初始为空,可从全部持仓添加
- 注册 conversation.view entries(在对话/轨迹/上下文之后):
```js
ctx.slots.register('conversation.view', {
id: 'all-positions',
label: '全部持仓',
component: AllPositionsTab,
})
ctx.slots.register('conversation.view', {
id: 'strategy-grid',
label: '网格策略持仓',
component: StrategyTab,
})
ctx.slots.register('conversation.view', {
id: 'strategy-manual-t',
label: '做T持仓',
component: StrategyTab,
})
```
- 策略持仓 tab 内编辑:添加(默认0起填)+ 移出(指定数量)
### 8. client/views/StrategyTab.jsx
- 挂载后从服务端 API 拉取该策略持仓
- 渲染:策略下持仓列表(代码/名称/份额/市值/盈亏)
- 展示未分配持仓区域
- 数据刷新:切换 tab 时拉取(本期)
## 三、职责边界(api.js vs qmt-bridge-rest.js
> 2026-08-27 老师提问澄清,明确两层职责:
| 层 | 文件 | 职责 | 交互对象 |
|---|---|---|---|
| RPC 封装层 | api.js | 接收 DSH 请求,转发给业务层 | 浏览器客户端(DSH RPC 通道) |
| 业务逻辑层 | position/manager.js | 分仓逻辑(份额分配/聚合) | 上层(api.js) |
| 数据源适配层 | data-source/qmt-bridge-rest.js | **真正发 HTTP 请求到 QMT** + 字段映射 + 统一接口 | QMT Bridge REST |
**调用链**api.js → manager.js → qmt-bridge-rest.js → fetch → QMT REST
**换数据源**:只替换 qmt-bridge-rest.js(统一接口不变,上层无感)—— 技术约束-001
## 四、数据流
```
用户点击「网格策略持仓」tab
→ 客户端 StrategyTab 挂载
→ call('/api', 'one-divine-lot/strategy/grid-supermarket/positions') // RPC 通道
→ api.js handler 调 manager.getStrategyPositions(id)
→ storage 读分配份额 + dataSource 读全量持仓
→ 过滤出该策略份额>0的持仓
→ RpcResult 返回 → 客户端渲染持仓列表
用户添加份额(在网格 tab 操作)
→ call('/api', 'one-divine-lot/allocations/add', { code, strategyId, shares })
→ manager.addToStrategy(code, strategyId, shares)
→ storage.set 更新份额(校验 ≤ 总持仓)
→ 返回更新后的分配 → 客户端刷新
```
## 五、关键接口签名(待实现时对照类型确认)
- `ctx.settings.register(ns, schema)` → SettingsScopeget/watch/update
- `webServer.register({kind:'http'|'upgrade', path, handler})` → disposer
- `ctx.slots.register('conversation.view', {id, label, component})`(客户端,具体签名实现时读 slots.d.ts
- `dsh.client` 声明:`{ "dsh": { "client": { "platform": "web", "inject": [...] } } }`
## 六、开发顺序(编码)
1. package.json + 骨架目录
2. data-sourceREST 适配 + 类型)
3. settings(策略注册)
4. storage(分仓 JSON
5. position/manager(分仓逻辑)
6. api(服务端路由)
7. clienttab 注册 + 组件)
8. 宿主挂载 + 联调验收
## 七、非目标(本期不做)
- 不做份额编辑 UI(二期)
- 不做策略参数的完整管理(网格区间等,后续迭代)
- 不做做T 记录
- 不做风控引擎
@@ -0,0 +1,40 @@
# 迭代复盘:01-分仓管理工具
> 复盘日期:2026-08-28 | 迭代状态:已完成(R-002 验收通过)
## 迭代总结
实现了「分仓管理工具」第一个可用版本:DSH 插件(独立挂载)+ 三个策略 tab(全部持仓/网格策略持仓/做T持仓)+ 设置菜单(神之一手)+ 份额分配本地管理 + 服务端 HTTP API。
## 事实记录(做了什么)
1. S1-S8 全部完成:插件骨架、数据源适配(QMT REST 直连)、设置管理、本地存储、分仓逻辑、服务端 API、客户端 tab、宿主挂载;
2. 客户端 UI:三个 conversation.view tab + settings.section 设置菜单;
3. 响应式布局:添加持仓组件支持宽/中/窄三种布局;
4. 构建链路:tsdown 构建 + __ModuleLoader__ 包装脚本。
## 经验教训(复盘沉淀)
### 1. DSH 插件开发的「坑」(重要)
- **RPC 通道冲突**DSH 的 /api RPC 通道**只能一个 interceptor**api-gateway 占用)—— 插件不能直接 intercept('/api'),应改用 **webServer 自开路由**
- **slots.register 签名**component 必须是**第二参数**register({...}, Component)),写在 options 里会导致 React #130 崩溃;
- **客户端 bundle 格式**:必须 **window.__ModuleLoader__.load({id, factory})** 包装(CJS 格式),裸 ESM 无法加载;
- **settings schema**:必须是 **schemastery z.object()**(函数式 schema),普通对象会报 "schema is not a function"
- **ctx 属性赋值**Cordis 不允许直接给 ctx 设属性(ctx.xxx = ...),会报 "cannot set property without provide"。
### 2. 插件安装与宿主集成
-**dsh plugin add**(而非 pnpm add)—— 它会自动 reconcile bundles
- 插件的 dsh.bundle.patchcordis.patch.yml**顶层必须是 insert 操作**,不是裸插件行;
- 客户端插件需要 **exports["./client"]** 指向 bundle,且 bundle 必须 __ModuleLoader__ 包装;
- 修改宿主配置(bundles/cordis.patch.yml)在 session workspace 外,需要沙箱升级 + 用户批准。
### 3. 需求讨论的价值
- 需求从「交易管理插件」→「智能交易辅助系统」→「分仓管理工具」多轮收敛;
- 老师的关键输入:分仓=按策略(网格超市/手动做T)、份额拆分(1000股→网格600+做T400)、策略 tab 内编辑(添加/移出)、步进100+手动输入。
## 下一步建议
1. **WebSocket 数据监控**(老师已提):插件可开启 WebSocketwebServer.registerUpgrade + ws 库),实现持仓/行情实时推送;
2. **策略配置完整管理**:网格参数(区间/格距/档位)等;
3. **做T 记录**:交易过程记录与复盘;
4. **设计约束补充**:本次沉淀的 DSH 插件开发规范(RPC 冲突、ModuleLoader、slots 签名等)应写入技术方案约束,供后续迭代参考。
@@ -0,0 +1,21 @@
# 迭代目标:01-分仓管理工具
## 目标
实现「分仓管理工具」第一个可用版本:DSH 插件设置管理(策略标签 + 显示开关)+ 主窗口 tab 栏展示策略标签 + QMT 全量持仓读取 + 本地存储。
## 目标描述
- 范围:只读(不下单);核心是「策略=标签」的设置管理、tab 栏展示、数据本地化;
- 要解决的问题:让老师在 DSH 中按策略(网格超市/手动做T 等)查看持仓,策略与分仓数据本地管理;
- 引用需求:**R-002(已定稿,2026-08-27**
- 实现方式:独立插件,外部挂载到 DSH 宿主。
## 目标讨论过程
- 2026-08-27 老师指令启动分仓管理工具(先验证 MCP/资金/持仓);
- 2026-08-27 多轮讨论收敛:分仓=按策略;策略=标签;标签=分仓;持仓份额可拆分(1000股→网格600+做T400);
- 2026-08-27 第 5 轮明确完整产品形态:设置管理 + 会话标签旁展示 + 本地存储;
- 2026-08-27 第 6 轮定稿:标签展示在 DSH 主窗口上方 tab 栏(对话/轨迹/上下文后),独立插件外部挂载。
## 对老师的配合需求
- 确认 DSH 宿主 composition 修改许可(加载 client 插件需要改宿主);
- 提供 QMT 账户用途说明(模拟/实盘);
- 确认标签初始配置(网格超市、手动做T 是否预置)。
@@ -0,0 +1,34 @@
# 验收标准:01-分仓管理工具
## 验收标准线
1. **设置管理**:DSH 设置中出现「神之一手」设置管理项,可添加/编辑/删除策略(标签),配置显示/隐藏开关;
2. **tab 栏展示**:DSH 主窗口上方 tab 栏在「对话/轨迹/上下文」后出现策略标签,按设置显示/隐藏;
3. **持仓数据加载**:每个策略标签下加载对应持仓数据(来自 QMT 全量持仓按策略归属过滤);
4. **本地存储**:策略配置与分仓数据持久化,DSH 重启后不丢失;
5. **QMT 连通**:全量持仓读取正常(真实数据,账户 8882874667)。
## 验收方法
1. 实际操作 DSH 设置界面,验证策略 CRUD 与显示开关;
2. 观察主窗口 tab 栏,验证策略标签位置(对话/轨迹/上下文后)与显示/隐藏;
3. 点击各策略标签,核对持仓数据与 QMT 真实持仓一致;
4. 重启 DSH,验证配置与分仓数据保留;
5. 检查本地存储文件内容(JSON)。
## 验收目标
- 老师能在 DSH 设置中管理策略标签(含显示开关);
- 老师能在主窗口 tab 栏按策略查看持仓;
- 分仓数据本地化,QMT 只作为全量持仓数据源。
## 最终验收结果(2026-08-28 · 老师确认完成)
| 验收项 | 结果 |
|---|---|
| 全部持仓显示 | ✅ 12 只真实持仓(QMT REST)显示正常 |
| 三个策略 tab | ✅ 全部持仓/网格策略持仓/做T持仓(对话/轨迹/上下文后) |
| 设置菜单「神之一手」 | ✅ 策略列表 + 显示开关 |
| 份额编辑 | ✅ 添加/移出(步进100 + 手动输入任意值) |
| 添加持仓组件 | ✅ 响应式布局(一行→按钮换行右对齐→每行两端对齐) |
| 本地存储 | ✅ allocations.json 持久化 |
| 服务端 API | ✅ /odl/api/* 8 端点全部工作 |
**验收结论:R-002 完成(老师 2026-08-28 确认)**
@@ -0,0 +1,125 @@
# 迭代 02 实施步骤规划(分步执行)
> 2026-08-28 | 按步骤分批推进,每步有独立产出与验证点
> 关联:技术实现方案.md、程序结构设计.md(02)、R-003(已定稿)
## 总览
| 步骤 | 内容 | 产出 | 验证点 | 依赖 |
|---|---|---|---|---|
| S1 | 服务端:策略 CRUD 逻辑(settings.js | id 生成/增删改排序 + 删除联动 | 单元验证:slug 生成、CRUD、删除清份额 | - |
| S2 | 服务端:存储清份额(storage.js | removeStrategyShares/clearStrategy | 清空后 allocations.json 正确 | S1 |
| S3 | 服务端:分仓快捷逻辑(manager.js) | 全部移入/清空策略份额 | 单元验证:未分配全部移入、清零 | S2 |
| S4 | 服务端:API 新端点(api.js | strategies/add、remove、move、remove-all-shares | 端点调用正确 + 错误码 | S1-S3 |
| S5 | 客户端:基础组件(LoadState/Toast | 三态加载 + 轻提示 | 组件渲染正确 | - |
| S6 | 客户端:SettingsSection CRUD | 新增/重命名/删除(确认)/排序/显隐 UI | UI 操作正确 + 服务端联动 | S4+S5 |
| S7 | 客户端:tab 动态注册(index.js) | 策略配置驱动 tabs + 手动刷新提示 | tab 随设置变化 + 提示出现 | S4+S5 |
| S8 | 客户端:StrategyTab 快捷操作 | 全部移入/一键清零 | 操作正确 + 确认弹窗 + Toast | S4+S5 |
| S9 | 构建 + 安装 + 联调验收 | 全链路 | 对照验收标准.md | S1-S8 |
## 分步细节
### S1:服务端策略 CRUDsettings.js
- `generateStrategyId(name)`:slug 化(去空格/转小写/非英文转拼音或序号兜底)
- `addStrategy / renameStrategy / removeStrategy / moveStrategy`
- removeStrategy 联动:删除策略 + 调 storage.removeStrategyShares
- **验证**Node 直接调用,验证 slug 生成、CRUD 后 settings 值正确、删除后份额清空
### S2:服务端存储清份额(storage.js)
- `removeStrategyShares(strategyId)`:遍历 allocations,删除该 strategyId,原子保存
- `clearStrategy(strategyId)`:同 removeStrategyShares(供一键清零复用)
- **验证**:构造数据 → 清空 → 文件正确
### S3:服务端分仓快捷逻辑(manager.js)
- `moveAllUnallocatedToStrategy(code, strategyId)`:未分配 = volume - 已分配,全部 add
- `clearStrategyShares(strategyId)`:清空该策略全部份额,返回影响的标的列表
- **验证**:真实/模拟持仓数据,全部移入后未分配归零、清零后份额归零
### S4:服务端 API 新端点(api.js
- `strategies/add`{ name } → 新增策略
- `strategies/remove`{ strategyId } → 删除 + 清份额
- `strategies/move`{ strategyId, dir } → 排序
- `remove-all-shares`{ strategyId } → 一键清零
- 全部移入复用 add-shares(服务端算未分配 or 客户端传)
- **验证**HTTP 调用各端点,200 + 正确返回;错误码(strategy-not-found 等)
### S5:客户端基础组件(LoadState.jsx / Toast.jsx
- LoadStateloading / error(原因+重试) / success 三态;自动重试 2 次(间隔 2s)
- Toastsuccess(绿) / error(红)3s 自动消失
- **验证**:组件在 tab 中渲染正确(可临时构造 error 态验证)
### S6:客户端 SettingsSection CRUDO3
- 新增:名称输入 + 按钮 → strategies/add
- 重命名:行内编辑 → strategies/update
- 删除:确认弹窗(提示份额回未分配)→ strategies/remove
- 排序:上移/下移 → strategies/move
- 显隐开关保留
- 操作后 Toast 反馈 + 触发「策略已变更」事件(供 S7)
- **验证**:完整 CRUD 操作路径
### S7:客户端 tab 动态注册(O2)
- index.jsloadStrategies() → 动态注册策略 tabsvisible=true
- 全部持仓 tab 固定
- 提示「刷新页面后生效」(方案 X);优化项:会话切换自动重拉(方案 Y)
- **验证**:增删/隐藏策略后刷新,tab 栏正确变化
### S8:客户端 StrategyTab 快捷操作(O6
- 行内「全部移入」→ add-sharesshares=未分配)
- 顶部「一键清零」→ 确认弹窗 → remove-all-shares
- Toast 反馈 + reload
- **验证**:操作后份额正确、未分配正确变化
### S9:构建 + 安装 + 联调验收
- pnpm buildtsdown + wrap-client.mjs
- dsh plugin add 更新安装
- 对照验收标准.md 全链路验收(含断 QMT 验证 O5、CRUD 验证 O3、tab 验证 O2、快捷操作验证 O6)
## 建议执行批次
- **批次 1(S1-S3)**:服务端核心逻辑(策略 CRUD + 清份额 + 快捷分仓)—— 纯服务端可独立验证
- **批次 2(S4)**:服务端 API 端点 —— 打通 HTTP 通道
- **批次 3(S5-S6)**:客户端基础组件 + 设置 CRUD UI
- **批次 4S7-S8**:客户端 tab 动态化 + 快捷操作 —— 全链路 UI
- **批次 5(S9)**:构建安装 + 联调验收
## 前置技术确认(编码前)
1. ~~slots.unregister 能力~~**已确认**`ctx.slots.register()` 返回 disposer 函数(SlotCore.register 的 return),可注销 entry;动态 tab 注册/注销可行
2. ~~客户端跨组件事件实现方式~~**已确认**:无事件机制。设置页保存后就地提示「刷新页面后生效」;tab 更新 = 刷新页面 → 插件重载 → 重新注册(方案 X;方案 Y 会话切换自动更新为优化项)
3. settings scope 更新后的 watch 机制(是否需要监听策略变更)
## 执行进度(2026-08-28
### ✅ 批次 1-2(S1-S4)服务端已完成并验证
| 步骤 | 状态 | 验证结果 |
|---|---|---|
| S1 策略 CRUDsettings.js | ✅ 完成 | slug 生成(长线持有→long-term-hold、网格超市→grid-supermarket、去重→-2)、增删改排序单元测试通过 |
| S2 存储清份额(storage.js | ✅ 完成 | removeStrategyShares 清空策略份额正确(受影响标的列表、记录清理) |
| S3 分仓快捷逻辑(manager.js) | ✅ 完成 | 全部移入(未分配 700→1200)、无未分配报错(no-unallocated)、一键清零通过 |
| S4 API 新端点(api.js | ✅ 完成 | strategies/add、remove(联动清份额)、move、move-all-shares、remove-all-shares 全部 HTTP 验证通过 |
### ✅ 批次 3-4(S5-S8)客户端已完成,构建通过
| 步骤 | 状态 | 说明 |
|---|---|---|
| S5 基础组件 | ✅ 完成 | LoadState.jsx(三态+自动重试2次+手动重试)、Toast.jsx(成功绿/失败红,3s |
| S6 SettingsSection CRUD | ✅ 完成 | 新增/重命名/删除(确认弹窗)/排序/显隐 + 就地「刷新后生效」提示 |
| S7 tab 动态注册 | ✅ 完成 | 策略配置驱动注册(slots.register 返回 disposer),全部持仓固定,隐藏策略不注册 |
| S8 StrategyTab 快捷操作 | ✅ 完成 | 全部移入(行内)、一键清零(确认弹窗)、LoadState 兜底 |
### 🔄 S9 构建安装 + 联调验收(进行中)
| 项 | 状态 | 验证结果 |
|---|---|---|
| pnpm build | ✅ 完成 | 客户端 bundle 41KB + 服务端 14 模块,lib 产物集成测试通过 |
| 宿主加载 | ✅ 完成 | 客户端模块清单含 one-divine-lotrev a238b5fa84d1),bundle 为新版本(41KB |
| 服务端 API 真实验证 | ✅ 完成 | 新端点全部可用:add/move/remove(联动清份额)/move-all-shares/remove-all-shares |
| 快捷操作真实验证 | ✅ 完成 | 全部移入(603028.SH 未分配800→manual-t)、一键清零(manual-t 清零)验证通过,数据已恢复 |
| UI 视觉验证 | ⏳ 待老师确认 | 需在浏览器实际查看 tab 动态化、设置 CRUD、快捷操作(当前环境无法自动化浏览器) |
### 待老师确认的 UI 验收项
1. 主窗口 tab 栏:只显示「手动做T」(网格超市 visible=false 不显示)—— O2 动态 tab
2. 设置页「神之一手」:策略 CRUD(新增/重命名/删除弹窗/排序/显隐)+「刷新页面后生效」提示
3. 策略 tab:全部移入(行内)、一键清零(弹窗)、Toast 反馈
4. 加载失败:LoadState 重试(可临时断 QMT 验证)
@@ -0,0 +1,90 @@
# 技术实现方案:02-分仓管理工具完善与优化
## 一、技术选型
| 项 | 选型 | 依据 |
|---|---|---|
| 技术栈 | 沿用迭代 01Node/TS 服务端 + React 客户端) | 技术约束-001/003 |
| 服务端 API | webServer 自开路由 /odl/api/***不 intercept /api** | 技术约束-004 |
| 客户端 bundle | CJS + window.__ModuleLoader__.load 包装 | 技术约束-005 |
| slots.register | component 为第二参数;settings 用 schemastery z.object() | 技术约束-006 |
| 插件安装 | dsh plugin add(自动 reconcile bundles | 技术约束-007 |
| 本地存储 | allocations.json~/.dsh/one-divine-lot/ | 沿用迭代 01 |
## 二、改造点设计
### O2 策略 tab 动态化
**现状**src/client/index.js 写死注册 3 个 tab(全部持仓 + 2 策略)。
**改造**
- 客户端新增「拉取策略配置」逻辑:启动/进入时 fetch /odl/api/strategies,取 visible=true 的策略;
- 按策略列表动态注册 conversation.view tabsid = odl-strategy-<slug>label = 策略名);
- 全部持仓 tab 固定保留(数据源 tab,始终显示);
- 隐藏策略的 tab 不注册/移除;
- 策略变更(设置页保存)→ 顶部轻提示「策略已变更,请手动刷新」+ 提供刷新按钮触发重新拉取注册。
### O3 设置界面完善(策略 CRUD)
**现状**SettingsSection.jsx 只有显示/隐藏开关。
**改造**
- 新增策略:输入名称 → 服务端生成英文 id(slug 化,如「长线持有」→ long-term-hold)→ 追加到列表末尾;
- 重命名:改 name(id 不变,分配数据不受影响);
- 删除:弹窗确认(提示「该策略下 N 股将回到未分配」)→ 删除策略 + 其份额回未分配(服务端原子操作);
- 排序:上移/下移按钮调整 order;
- 预置策略(网格超市/手动做T)与其他策略一视同仁,可删可改;
- 保留显示/隐藏开关。
- 服务端:strategies/update 现有端点承载 CRUD;新增删除时份额回未分配的联动逻辑(删除策略 → storage 中该 strategyId 的分配清除,回到未分配)。
### O5 数据加载失败兜底
**现状**:tab 加载失败仅显示「加载失败」文字。
**改造**
- 统一「加载状态组件」:loading / error(含原因)/ success 三态;
- error 态显示:失败原因(HTTP 错误 / 超时 / 网络异常)+ 「重试」按钮;
- 自动重试 2 次(间隔 2s),仍失败进入 error 态(手动重试);
- 超时:沿用 15s 可配置(qmtTimeoutMs),超时报「数据源超时」;
- **不引入全局数据源状态监控**(O5 本质是页面加载兜底,非状态栏)。
### O6 份额编辑体验
**现状**StrategyTab 添加/移出需逐票输入。
**改造**
- 「全部移入」(单票):策略 tab 表格行内按钮,把该票全部未分配份额移入当前策略(一次 add-shares 调用,shares = 未分配数);
- 「一键清零」(策略级):策略 tab 顶部按钮,弹窗确认后把该策略下所有份额移出(循环 remove 或新增批量端点);
- 保留 step=100 步进 + 手动输入任意值;
- 操作反馈:统一轻提示组件(成功绿/失败红,3s 自动消失)。
## 三、服务端 API 变更
| 端点 | 变更 |
|---|---|
| /odl/api/strategies | 不变(列表) |
| /odl/api/strategies/update | 承载 CRUD;删除时联动清空该策略份额(回未分配) |
| /odl/api/add-shares | 不变(O6 全部移入复用,shares=未分配数) |
| /odl/api/remove-shares | 不变 |
| /odl/api/remove-all-shares(新增) | 一键清零:{ strategyId } → 清空该策略全部份额 |
## 四、客户端组件变更
| 组件 | 变更 |
|---|---|
| src/client/index.js | 动态注册策略 tabs(从 /strategies 拉取) |
| src/client/views/SettingsSection.jsx | 补全 CRUD UI(新增/重命名/删除/排序)+ 删除确认弹窗 |
| src/client/views/StrategyTab.jsx | 全部移入按钮、一键清零按钮(确认)、轻提示反馈 |
| src/client/views/AllPositionsTab.jsx | 加载失败兜底(错误提示 + 重试) |
| src/client/views/connection.jsx | 统一 API 调用错误处理(错误码/原因透出) |
## 五、实现步骤
1. 服务端:strategies/update 删除联动(份额回未分配)+ remove-all-shares 端点;
2. 客户端:加载状态组件 + 轻提示组件;
3. 客户端:SettingsSection CRUD 补全 + 删除确认弹窗;
4. 客户端:tab 动态注册(策略配置驱动 + 变更提示刷新);
5. 客户端:StrategyTab 全部移入 / 一键清零;
6. 构建(tsdown + wrap+ 安装(dsh plugin add);
7. 验收测试。
## 六、涉及设计约束
- 产品约束-004(删除策略份额回未分配 + 弹窗确认,R-003 新增)
- 产品约束-002/003、技术约束-001/003~007
## 七、风险与开放项
1. 动态注册 tabs:重复注册/注销策略 tab 的精确 APIslots.unregister 或重挂载)需在实现时确认;
2. 策略变更提示「手动刷新」的触发范围(当前会话内即时提示?跨会话?)—— 按 O2-3 决策:设置保存后提示手动刷新;
3. 一键清零大批量份额的原子性(删除策略 vs 份额操作并发);
4. 删除策略的份额回未分配是否涉及「未分配」展示(未分配 tab 已有,删除后份额自然出现在未分配)。
@@ -0,0 +1,181 @@
# 程序结构设计:02-分仓管理工具完善与优化
> 迭代设计 v2 的程序结构细化 2026-08-28
> 关联:技术实现方案.md(02)、R-003(已定稿)
## 一、整体目录结构(基于迭代 01 演进)
```
one_divine_lot/
├── package.json # 不变(已有 dsh.client 声明)
├── src/ # 源码(服务端 + 客户端)
│ ├── index.js # 插件入口:apply(ctx),组装各模块(少量调整)
│ ├── settings.js # 设置管理:策略 CRUD 逻辑扩展(新增 id 生成、删除联动)
│ ├── storage.js # 本地存储:allocations.json(新增:清空策略份额)
│ ├── api.js # 服务端 HTTP APIwebServer /odl/api/*):新增端点
│ ├── data-source/
│ │ ├── types.js # 不变
│ │ └── qmt-bridge-rest.js # 不变
│ ├── position/
│ │ └── manager.js # 分仓逻辑:新增全部移入/清空策略份额
│ └── client/
│ ├── index.js # 客户端入口:tab 动态注册(O2 核心改造)
│ └── views/
│ ├── AllPositionsTab.jsx # 加载失败兜底(O5
│ ├── StrategyTab.jsx # 全部移入/一键清零(O6)
│ ├── SettingsSection.jsx # 策略 CRUD 补全(O3
│ ├── connection.jsx # API 调用错误透出增强(O5)
│ ├── LoadState.jsx # 【新增】三态加载组件(O5)
│ └── Toast.jsx # 【新增】轻提示组件(O6/O3)
└── docs/ # 神之一手框架文档(既有)
```
## 二、模块职责与变更点
### 1. index.js(插件入口)
- 基本不变;新增:把「策略配置读取」能力暴露给 api(已由 settings 提供)
- 组装链保持:dataSource → settings → storage → manager → api
### 2. settings.js(设置管理)—— O3 核心
- **现有**registerSettings / getStrategies / getVisibleStrategies / updateStrategies
- **新增**
- `generateStrategyId(name)`:中文/任意名 → 英文 slug(如「长线持有」→ long-term-hold;去空格、转小写、连字符)
- `addStrategy(scope, name)`:生成 id + 追加到列表末尾(order = max+1
- `renameStrategy(scope, id, name)`:只改 name(id 不变,分配数据不受影响)
- `removeStrategy(scope, id)`:删除策略(**联动清空该策略份额** —— 调用 storage 清理 + 更新策略列表)
- `moveStrategy(scope, id, dir)`:上移/下移(调整 order
- 策略 schema 不变(id/name/visible/order
### 3. storage.js(本地存储)—— 配合 O3/O6
- **现有**load/save/get/getAll/set/remove(按 code
- **新增**
- `removeStrategyShares(strategyId)`:遍历所有 code,删除该 strategyId 的份额(删除策略联动)
- `clearStrategy(strategyId)`:同 removeStrategySharesO6 一键清零可复用)
### 4. position/manager.js(分仓逻辑)—— O6
- **现有**getAllPositions/getStrategyPositions/getUnallocated/addToStrategy/removeFromStrategy/getSummary
- **新增**
- `moveAllUnallocatedToStrategy(code, strategyId)`:把某票**全部未分配份额**移入指定策略(O6 全部移入)—— 未分配 = volume - 已分配,一次 add
- `clearStrategyShares(strategyId)`:清空某策略全部份额(O6 一键清零)—— 调 storage.clearStrategy + 返回影响标的列表
- 校验保持:各策略份额之和 ≤ 总持仓
### 5. api.js(服务端 API)—— 承载新端点
- **现有**/odl/api/* 8 端点(webServer 自开路由,技术约束-004
- **新增端点**
- `POST /odl/api/strategies/remove`{ strategyId } → 删除策略 + 联动清份额(O3)
- `POST /odl/api/strategies/move`{ strategyId, dir } → 排序(O3
- `POST /odl/api/strategies/add`:{ name } → 新增(O3,也可并入 update,分开更清晰)
- `POST /odl/api/remove-all-shares`{ strategyId } → 一键清零(O6
- 全部移入复用现有 `add-shares`(shares = 未分配数,由客户端先算好或服务端算)
- `strategies/update` 保留(重命名/显隐仍走整表更新)
- 错误透出:统一错误码(not-found / strategy-not-found / share-limit 等)
### 6. client/index.js(客户端入口)—— O2 核心改造
- **现有**:写死注册 3 个 tab
- **改造**
- 新增 `loadStrategies()`fetch /odl/api/strategies,取 visible=true 列表
- **动态注册**:遍历策略列表,逐个 `slots.register('conversation.view', { id: 'odl-strategy-<slug>', label: 策略名 }, StrategyTab)`
- 全部持仓 tab 固定保留(order 10,数据源 tab
- **刷新机制**:暴露「刷新 tab 栏」函数 —— 重新拉策略配置 → 注销旧策略 tabs → 注册新 tabs
- **变更提示(无事件机制,老师确认)**:设置页保存成功后**就地提示**「策略已变更,刷新页面后生效」(Toast);
- **tab 更新时机**:插件启动时按策略配置注册 tabs → 刷新页面 = 插件重载 = 按最新策略重新注册(基线方案 X);
- **可选优化(方案 Y)**:tab 栏利用「会话切换/重新挂载」时机重新拉策略配置自动更新(无需自定义事件,实现时验证框架挂载钩子,失败则保持方案 X)
- **技术确认(已核实)**`ctx.slots.register(...)` 返回 **disposer 函数**(注销该 entry)——动态注销旧 tabs 可行:注册时保存 disposer 列表,刷新时逐一调用 disposer 移除,再注册新 tabs
### 7. client/views/SettingsSection.jsx —— O3 UI
- **现有**:策略列表 + 显示开关
- **改造**
- 新增策略:输入框 + 按钮 → call strategies/add { name }
- 重命名:行内编辑(改 name → strategies/update
- 删除:删除按钮 → **确认弹窗**(提示「该策略下 N 股将回到未分配」)→ call strategies/remove
- 排序:上移/下移按钮 → call strategies/move
- 保留显示开关
- 操作反馈:Toast(成功/失败)
### 8. client/views/StrategyTab.jsx —— O6 UI
- **现有**:添加(选标的+份额)/ 移出(指定数量)
- **改造**
- 「全部移入」按钮(行内):该票未分配全部移入当前策略(call add-sharesshares=未分配)
- 「一键清零」按钮(顶部):弹窗确认 → call remove-all-shares
- 操作反馈:Toast
- 加载失败:LoadState 兜底(O5
### 9. client/views/AllPositionsTab.jsx —— O5
- **现有**:直接显示/加载失败文字
- **改造**:套 LoadState 三态组件(loading/error+重试/success
### 10. 【新增】client/views/LoadState.jsx —— O5
- 三态组件:loading(加载中)/ error(失败原因 + 重试按钮)/ successchildren
- 自动重试 2 次(间隔 2s)逻辑内置(或由父组件控制)
### 11. 【新增】client/views/Toast.jsx —— O6/O3
- 轻提示:success(绿)/ error(红),3s 自动消失
- 简单实现:全局 state + 定时器,或 React portal
## 三、职责边界(延续迭代 01
| 层 | 文件 | 职责 |
|---|---|---|
| API 封装层 | api.js | 接收 HTTP 请求 → 调业务层 |
| 业务逻辑层 | position/manager.js + settings.js | 分仓逻辑 + 策略管理 |
| 数据源适配层 | data-source/qmt-bridge-rest.js | 真正发 HTTP 到 QMT |
| 存储层 | storage.js | allocations.json 本地持久化 |
**调用链**client → HTTP /odl/api/* → api.js → manager/settings → storage / qmt-bridge-rest → QMT
## 四、数据流(关键路径)
```
【O2 动态 tab】
设置页保存策略变更
→ SettingsSection 调 strategies/update(或 add/remove/move
→ 服务端更新 settings + 联动 storage
→ 客户端 Toast「策略已变更,请手动刷新」+ 刷新按钮
→ 用户点刷新 → 重新 loadStrategies() → 注销旧 tabs → 注册新 tabs
【O3 删除策略】
设置页点删除 → 确认弹窗
→ call strategies/remove { strategyId }
→ settings.removeStrategy + storage.removeStrategyShares(strategyId)
→ 份额回未分配(全部持仓 tab 未分配数增加)
【O6 全部移入】
策略 tab 行内「全部移入」
→ 客户端已知该票未分配数 → call add-shares { code, strategyId, shares: 未分配数 }
→ manager.addToStrategy(校验 ≤ 总持仓)
→ Toast 成功 → reload
【O6 一键清零】
策略 tab 顶部「一键清零」→ 确认弹窗
→ call remove-all-shares { strategyId }
→ manager.clearStrategyShares → storage.clearStrategy
→ Toast 成功 → reload
```
## 五、关键接口签名(待实现时对照类型确认)
- slots.register('conversation.view', {id, label}, Component) —— 动态注册/注销(读 slots.d.ts 确认 unregister
- webServer.register({kind:'prefix', path:'/odl/api', handler}) —— 已有
- settings scope.get()/update() —— 已有
- **事件机制:不需要(老师确认,2026-08-28)**——策略变更的提示在设置页内就地显示,tab 更新靠「刷新页面 → 插件重载 → 重新注册」,全程无跨组件事件;
- 优化(方案 Y):若框架支持会话切换挂载钩子,tab 栏自动重新拉策略配置(仍无事件)
## 六、开发顺序(编码)
1. 服务端:settings.jsadd/rename/remove/move + id 生成)
2. 服务端:storage.jsremoveStrategyShares/clearStrategy
3. 服务端:manager.jsmoveAllUnallocated/clearStrategyShares
4. 服务端:api.js(新端点 + 错误透出)
5. 客户端:LoadState.jsx + Toast.jsx(基础组件)
6. 客户端:SettingsSection CRUD + 确认弹窗
7. 客户端:index.js 动态 tab 注册 + 刷新机制
8. 客户端:StrategyTab 全部移入/一键清零
9. 构建 + 安装 + 联调验收
## 七、非目标(本期不做)
- 表格列可配置(T-006 数据池)
- 数据源状态全局监控(O5 澄清为加载兜底)
- 数据实时刷新/WebSocketT-005
- 持仓信息补全列(O1
- 网格参数管理、做T 记录、风控
@@ -0,0 +1,22 @@
# 迭代目标:02-分仓管理工具完善与优化
## 目标
对 R-002 分仓管理工具进行完善与优化:**O2 策略 tab 动态化、O3 策略 CRUD 设置界面、O5 数据加载失败兜底提示+重试、O6 份额快捷操作**。将「分仓管理」从写死的 3 tab 形态升级为「策略配置驱动 + 完整策略管理 + 健壮加载体验」的可用工具。
## 目标描述
- 范围:O2/O3/O5/O6(见 R-003 定稿,20 项决策收敛);不新增全新功能域;
- 要解决的问题:策略 tab 写死不同步设置、设置界面缺 CRUD、加载失败无兜底、份额编辑繁琐;
- 引用需求:**R-003(已定稿,2026-08-28**
- 实现方式:沿用独立插件 one-divine-lot,服务端 + 客户端改造。
## 目标讨论过程
- 2026-08-28 迭代 01 复盘提出下一步建议;
- 2026-08-28 R003 讨论:候选方向 O1~O7 → 老师圈定 O2/O3/O5/O6(第 2 轮);
- 2026-08-28 第 3 轮:16 项核心决策收敛(含删除份额回未分配、预置策略可删);
- 2026-08-28 第 4 轮:剩余不确定点收敛(手动刷新提示、删除确认弹窗、单票全部移入、清零确认);
- 2026-08-28 第 5 轮:O5 本质澄清(加载失败兜底提示,非数据源状态监控);
- 2026-08-28 老师确认定稿;O8 数据池拆分独立为 T-006 草稿。
## 对老师的配合需求
- 验收时提供 QMT 可用环境(数据加载失败场景可临时断网验证);
- 确认策略 CRUD 操作路径符合预期(新增/重命名/删除/排序/显隐)。
@@ -0,0 +1,29 @@
# 验收标准:02-分仓管理工具完善与优化
## 验收标准线
1. **O2 tab 动态化**:设置中增删/隐藏策略后,主窗口 tab 栏按策略配置更新(隐藏的策略 tab 不显示);策略变更后出现「手动刷新」提示;
2. **O3 策略 CRUD**:设置界面支持新增(自动英文 id)/重命名/删除(弹窗确认 + 份额回未分配)/排序(上移下移)/显隐;预置策略可删除;
3. **O5 加载失败兜底**:数据加载失败时显示失败原因 + 重试按钮;自动重试 2 次后仍失败可手动重试;超时给出提示;
4. **O6 份额快捷操作**:单票「全部移入」(未分配全部进当前策略)、策略「一键清零」(弹窗确认后全部移出);操作有轻提示反馈;
5. **无回归**:全部持仓/策略持仓展示、份额分配、本地持久化、响应式布局保持可用。
## 验收方法
1. 在设置中新增/重命名/删除/排序/隐藏策略,观察主窗口 tab 栏变化与手动刷新提示;
2. 删除有份额的策略,验证份额回到未分配(全部持仓 tab 未分配数增加);
3. 临时断开 QMT,验证 tab 加载失败提示 + 重试按钮;恢复后重试成功;
4. 在策略 tab 使用「全部移入」「一键清零」,验证份额变化与确认弹窗、轻提示;
5. 回归:全部持仓/策略持仓展示、添加/移出份额、重启持久化。
## 验收目标
- 老师能在设置中完整管理策略(CRUD + 显隐 + 排序),tab 栏实时反映;
- 数据加载失败时老师能获得清晰提示并重试;
- 份额编辑更高效(单票全部移入、一键清零),操作有反馈。
## 验收记录(待迭代完成后填写)
| 验收项 | 结果 |
|---|---|
| O2 tab 动态化 | 待验收 |
| O3 策略 CRUD | 待验收 |
| O5 加载失败兜底 | 待验收 |
| O6 份额快捷操作 | 待验收 |
| 无回归 | 待验收 |
+63
View File
@@ -0,0 +1,63 @@
# 04-迭代记录(Iteration Logs
> 执行过程的事实档案:每次迭代一个子目录,记录迭代目标、技术实现方案与验收标准。
## 角色
项目执行史的忠实记录。复盘、追溯、计划验收的事实依据。是"文档驱动"闭环里唯一记录"发生过的现实"的地方。
## 组织结构
每次迭代一个子目录。目录已位于迭代记录下,目录名不再重复"迭代"前缀,直接为 **`<编号>-<目标名称>`**(如 `01-建立神之一手框架`),编号放最前、后面直接跟本次迭代的目标名称,子目录内含三份文档:
```
04-迭代记录/
├── 说明.md
├── 01-建立神之一手框架/
│ ├── 迭代目标.md # 本次迭代的目标、目标描述、目标讨论过程、对老师的配合需求
│ ├── 技术实现方案.md # 实现该目标对应的技术实现方案
│ └── 验收标准.md # 本次迭代的验收标准线、验收方法、验收目标
├── 02-<目标名称>/
└── ...
```
### 迭代目标.md
记录本次迭代的:
1. **目标**:一句话说清本次迭代要达成什么。
2. **目标描述**:目标的详细描述(范围、边界、要解决的问题)。
3. **目标讨论过程**:本次迭代目标从提出到确定的讨论过程(关键论点、取舍、结论)。
4. **对老师(项目主理人)的配合需求**:执行本次迭代需要老师配合的事项(决策、素材、确认、评审等)。
> 迭代目标若引用需求池中的需求,需在目标描述中标注需求编号,并触发需求池索引记录联动(见需求池)。
### 技术实现方案.md
记录实现本次迭代目标对应的技术实现方案:技术选型、架构/设计要点、实现步骤、涉及的设计约束(引用 `03-设计约束/` 中对应条目编号)等。
### 验收标准.md
明确本次迭代的验收标准线 —— 如何判定本次迭代是否达成。记录:
1. **验收标准线**:本次迭代达成的标准在哪里(可验证的具体标准,作为"迭代完成"的判定线)。
2. **验收方法**:如何做验收(验证手段:测试、演示、评审、数据核对等)。
3. **验收目标**:验收要达到的目标(验收时逐项确认的最终结果)。
## 强约束(初始定义)
1. **只记事实**:记录"做了什么、结果如何",不写感想、不写评价、不粉饰。
2. **追加式,不改写**:历史记录不可修改、不可删除;纠正用追加的更正记录。
3. **每次迭代一个子目录**:迭代必须有自己的编号子目录,文档归入对应子目录,不混放。
4. **与计划/需求池对应**:每条迭代记录应能对应到计划中的任务或需求池条目。
5. **入范围门槛**:只有确定的需求才可进入迭代工作范围;未确定的需求不得建立迭代子目录。
6. **成功失败都记**:失败是复盘的原料,必须如实记录。
7. **时间可溯**:记录带有时间信息,支持按时间回溯。
## 待讨论细节
- [ ] 迭代编号规则(编号是否需带日期 / 序号是否全局递增 / 目标名称的写法规范)
- [ ] 迭代的粒度(多大算一次迭代?一次讨论 / 一个功能 / 一个周期?)
- [ ] 迭代目标.md 的讨论过程如何记录(详细纪要 vs 结论摘要)
- [ ] 验收标准.md 的写法模板(标准线 / 验收方法 / 验收目标的具体写法与示例)
- [ ] 复盘模板(复盘时如何从记录中提炼结论)