Files
one_divine_lot/docs/04-迭代记录/01-分仓管理工具/程序结构设计.md
T
2026-08-29 16:20:33 +08:00

191 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 程序结构设计: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 记录
- 不做风控引擎