# 程序结构设计: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 API:connection.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)` - schema:strategies: array[{ id, name, visible, order }] - 预置:网格超市、手动做T - 提供 getStrategies() / watchStrategies()(客户端 API 读取) ### 3. storage.js(本地存储) - 路径:`~/.dsh/one-divine-lot/allocations.json` - 结构:`{ [positionCode]: { [strategyId]: shares } }` - API:load() / save() / get(code) / set(code, strategyShares) ### 4. data-source/qmt-bridge-rest.js(REST 数据源) - baseUrl:http://192.168.3.43:8610(config 可配) - 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 原生 RPC(connection.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)` → SettingsScope(get/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-source(REST 适配 + 类型) 3. settings(策略注册) 4. storage(分仓 JSON) 5. position/manager(分仓逻辑) 6. api(服务端路由) 7. client(tab 注册 + 组件) 8. 宿主挂载 + 联调验收 ## 七、非目标(本期不做) - 不做份额编辑 UI(二期) - 不做策略参数的完整管理(网格区间等,后续迭代) - 不做做T 记录 - 不做风控引擎