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,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 记录
- 不做风控引擎