Files
2026-08-29 16:20:33 +08:00

8.5 KiB
Raw Permalink Blame History

程序结构设计: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(插件入口)

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 路由。

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(在对话/轨迹/上下文之后):
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 记录
  • 不做风控引擎