Files
one_divine_lot/docs/03-设计约束/技术方案约束.md
T
kyugao a4902bb730 feat(R-013+重构): 策略自定义字段需求文档 + 结构归类优化
需求与文档:
- R-013 策略自定义字段配置(定义随策略 configSchema 存 settings、值落
  strategy_holdings.values JSON、类型化文本/数字/布尔/枚举)定稿并进入
  PLAN-012 / 迭代 11(含目标数据模型 + 端到端示例 + 技术方案 + 验收标准)
- R-011(Tab设置)/ R-012(UI主题适配)归档至 已完成/,索引标记已归档
- 沉淀约束: 产品约束-010 / 技术约束-015 / 技术约束-016 / UI约束-005

结构重构(技术约束-016):
- src/component/ 平铺还原为语义分域目录: data-source/ storage/ position/
  market/ trades/(git rename 保留历史)
- 清理死代码: 删除 AllocationStorage.js(0 引用)、DataStore
  setDataset/removeDataset(无调用方)
- 同步更新 src/index.js / scripts/*.mjs / tsdown.config.ts 引用
- typecheck + build + test-r009 回归 14/14 通过
2026-09-02 15:38:51 +08:00

11 KiB
Raw Blame History

技术方案约束(Technical Constraints

技术方案层面的约束:技术栈、架构、实现规范、依赖原则。后续迭代进行技术实现时以此为依据。

记录格式:每条约束包含「编号 / 约束说明 / 添加日期 / 生效状态 / 失效日期 / 最后一次变更描述」。新增追加、变更更新、失效标记状态(不删除记录)。引用用编号,如 技术约束-001最后一次变更描述:概括该约束最近一次变更(新增 / 变更 / 失效)讨论的最后一层结论;新增时写新增结论,变更/失效时更新为当次结论。

约束列表

编号 约束说明 添加日期 生效状态 失效日期 最后一次变更描述
技术约束-002 分仓管理采用「全量持仓 + 标签」模型:持仓数据为账户真实持仓(来自数据源适配器),标签为逻辑分组元数据(独立于持仓存储,可增删改),聚合视图按标签汇总仓位 2026-08-27 生效 - 讨论确认(R-002):全量持仓为基础,标签体系做逻辑分仓
技术约束-001 行情/交易数据源必须通过统一接口抽象(定义统一的数据模型与操作接口),实现层为具体数据源适配器;当前限定实现 QMT Bridge 适配器,未来可新增其他行情/交易接口适配器,业务层不感知具体数据源 2026-08-27 生效 2026-08-27 变更(2026-08-27 R-002 讨论):原为「QMT Bridge MCP 适配器」,修正为直接调用 QMT Bridge RESTful 接口http://192.168.3.43:8610),MCP 仅作为 Agent 侧封装层,插件侧直连 REST
技术约束-003 QMT Bridge 数据源通过其 RESTful HTTP 接口接入(基础地址 http://192.168.3.43:8610OpenAPI v3 规范),不经过 MCP 层;MCP 是给 Agent 用的封装,插件内部直连 REST 2026-08-27 生效 - 讨论确认(R-002 第 7 轮):老师指出 MCP 是给智能体用的,插件应直连同一服务端口的 RESTful 接口;变更标注(2026-08-29 R-004 定稿):基础地址由固定单一地址改为多配置动态管理(由激活配置决定,见技术约束-008),直连 REST 原则不变
技术约束-004 插件服务端 API 用 webServer 自开路由(如 /odl/api/*),不直接使用 connection.rpc.intercept('/api') —— DSH 的 /api 通道只能一个 interceptorapi-gateway 已占用),重复 intercept 会抛错导致插件 apply 失败 2026-08-28 生效 - 迭代 01 复盘沉淀:RPC 冲突导致服务端插件 apply 失败、RPC 404
技术约束-005 客户端插件 bundle 必须是 CJS + window.ModuleLoader.load({id, factory}) 包装(tsdown 构建 + wrap 脚本),裸 ESM 无法被 DSH 客户端模块系统加载 2026-08-28 生效 - 迭代 01 复盘沉淀:客户端 bundle 未包装导致 loaded without registering via ModuleLoader.load
技术约束-006 客户端 slots.register 的 component 必须是第二参数(register({...}, Component));settings schema 必须用 schemastery z.object() 函数式定义 2026-08-28 生效 - 迭代 01 复盘沉淀:component 位置错误致 React #130;普通对象 schema 报 schema is not a function
技术约束-007 插件安装用 dsh plugin add(自动 reconcile bundles),不直接用 pnpm addbundle patch 顶层必须是 insert 操作 2026-08-28 生效 - 迭代 01 复盘沉淀:pnpm add 不会更新 dsh.profile.bundles
技术约束-008 QMT 连接配置存储复用 one-divine-lot settings namespace(新增 qmtConnections 字段:list[{id,name,baseUrl,order}] + activeId + defaultId),与策略配置同机制持久化;激活切换 = 更新数据源实例的 baseUrl(数据源按请求读取地址,已核实),立即生效无需重启 DSH;插件启动时激活默认配置(列表为空时回退 cordis 注入的 qmtBaseUrl 兜底,不做自动迁移);测试连接由服务端代理请求 {baseUrl}/health(避免浏览器跨域) 2026-08-29 生效 - R-004 定稿(2026-08-29):Q1/Q2/Q4/Q5/Q8 确认(启动自动激活默认、立即切换、不迁移、超时不配置化、复用 settings)
技术约束-012 插件数据存储遵循 docs/03-设计约束/数据存储设计.md:存储引擎为 SQLitenode:sqlite——strategy_holdings + market_quotes_cache + trade_orders + trade_fills(交易记录两表,R-009;存储层单票生命周期操作;交易记录两表零冗余 strategy_id/holding_id(外键链推导策略归属);策略定义仍存 DSH settings;旧 JSONstore.json / store.market.json / allocations.json)经一次性迁移脚本 + 启动自动迁移(幂等、迁移前自动备份)后废弃;仅存储引擎替换,对外行为不变 2026-09-01 生效 - 变更(2026-09-01 R-008 定稿 + 迭代 06 实施):JSON data store → SQLite;变更(2026-09-01 R-009 定稿 + 迭代 07 实施):+trade_orders/trade_fills 交易记录两表(零冗余,外键链推导策略归属)
技术约束-011 测试/回归脚本禁止在真实数据上执行写操作:份额写操作(add/remove/move/clear)必须使用独立数据目录(AllocationStorage 支持 ODL_TEST_DATA_DIR 环境变量或 dataDir 参数指向临时目录),只读端点(positions/summary/strategies/market-snapshot)可直连生产 API 2026-08-31 生效 - 2026-08-31 数据误删事故沉淀:回归测试误删大连热电/万顺新材份额分配,老师定「测试用独立数据目录」
技术约束-010 行情实时数据由服务端中转 + 缓存提供(R-005 演进,2026-08-31 老师改):DSH 服务端做「WS 订阅 + REST 轮询 + 行情缓存」,前端统一轮询 /odl/api/market-snapshot(不做前端直连,无跨域);行情持久化到 store.market.json(重启不丢价,首屏快速展现);QMT Bridge WS 推送是会话级/有状态行为(归属 QMT Bridge 工作空间) 2026-08-31 生效 - 变更(2026-08-31):老师由「前端直连 WS」改为「服务端中转 + 缓存」——解决跨域与 WS 语义不稳定问题;2026-09-01 加行情持久化与启动 prime
技术约束-009 会话头部快捷切换控件挂载 DSH 开放 slot conversation.session.header.actions(多实例挂载点,按 order 排序多插件共存):客户端插件以独立 id 并排注册(DSH 内置 PTC 标签 order=-10,本控件 order=-9),不改动 DSH 宿主;控件经 ConnectionProvider 包装复用现有 RPC 通道与 /odl/api/* 端点 2026-08-29 生效 - R-004 定稿(2026-08-29):Q9 确认;宿主代码审查核实 slot 机制与内置插件注册方式

| 技术约束-013 | 交易记录本地存储(R-009):QMT 当日交易数据(委托/成交)由服务端 TradeSync 定时同步落 SQLite(启动预热 + 60s 定时 + UPSERT 幂等,只同步当日);trade_orders(委托主行,order_id 主键 + insert_ts 派生时间列 + strategy_id/holding_id 手动归属列+ trade_fills(成交明细,trade_id 主键、order_id 外键)两表;委托归属由用户在交易记录 tab 手动设置(候选 = 该 code 当前持仓策略 + 未关联,全手动选、可随时改、以最终为准);UPSERT 不覆盖归属列(手动指定为插件逻辑);本地历史查询走 trades/history 端点(策略过滤 = 用户设置的归属);今日实时仍走 QMT Bridge;QMT 委托/成交 code 无后缀、持仓带后缀 —— 数据源映射层统一 normalizeInstrumentCode 归一化;委托交易日 = insertDatetradeDate 兜底) | 2026-09-01 | 生效 | - | 新增(2026-09-01 R-009 定稿 + 迭代 07 实施):两表 + 定时同步 + 本地历史查询;变更 12026-09-01):+code 归一化 + tradeDate 兜底;变更 22026-09-01 老师二次定稿):归属改手动设置trade_orders 冗余 strategy_id+holding_idUPSERT 不覆盖归属列),弃算法推导 | | 技术约束-015 | 策略自定义字段存储(R-013,2026-09-02):字段定义随策略定义存 settings.strategies 扩展 configSchema[{key,label,type,enum?,def}]type ∈ text|number|boolean|enum,旧项缺省空数组);字段值落 strategy_holdings 新增 values TEXTJSON 键值对,key 对齐 configSchema.key,允许额外键=可扩展,NULL=未配置);补列用幂等 ALTER(沿用 _ensureTradeAttributionColumns 模式,只读连接容忍);APIstrategy-positions 每行附 values,新增 holdings/values-update {holdingId, values} 写回,服务端按 configSchema 校验(number=有限数、enum=在选项内、boolean=布尔),空值/缺省可写入;持仓生命周期操作(openHolding/addShares/reduceShares/closeHolding)不碰 values 列 | 2026-09-02 | 生效 | - | 新增(2026-09-02 R-013 定稿 + PLAN-012):定义 settings + 值 SQLite 列 + 幂等补列 + 类型校验 | | 技术约束-014 | 会话 tab 注册与顺序显隐(R-011):客户端注册统一读 settings.tabs 有序数组(唯一顺序与显隐来源,内置条目 refKey + 策略条目 refId),按 order 排序、过滤 visible 后注册(builtin 走内置 render、strategy 走 StrategyTab),移除硬编码 order 间隔(原内置 10/11/12、策略 13+);settings.tabs 从布尔对象升级为有序数组,读取时对旧格式(布尔对象 + 策略自带 order/visible)静默归一化迁移(旧隐藏策略迁移后显示),写入即落库;策略定义表收窄为 {id,name}(去除 visible/order);tabs/update 语义改为整表更新(顺序 + 显隐),strategies/add 联动追加 tab 条目(末尾),strategies/remove 联动删除对应 tab 条目,废弃 strategies/move | 2026-09-02 | 生效 | - | R-011 定稿(2026-09-02 Q1-Q5 确认):统一 tabs 有序数组 + 自动迁移 + 联动增删 | | 技术约束-016 | src 目录按功能域归类(2026-09-02 结构优化):服务端代码禁止平铺,按职责域分目录 —— src/data-source/QmtBridgeRestDataSource + data-source-types + QmtHealthMonitor,数据源与连接健康)、src/storage/SqliteStore + DataStore,存储层)、src/position/PositionManager,分仓逻辑)、src/market/MarketDataHub + MarketFeed,行情)、src/trades/TradeSync,交易同步);api/ 按领域拆分子文件(positions/strategies/qmt-connections/market/trades),client/ 仅放 UIviews/ 组件 + market/ provider);文件命名 = 类名(PascalCase+ .js/.jsx;新增服务端模块必须先落对应域目录,无合适域时先讨论补域,不得回退平铺 | 2026-09-02 | 生效 | - | 新增(2026-09-02 结构审查 + 优化落地):component/ 平铺还原为语义分域,删除死代码 AllocationStorage、DataStore.setDataset/removeDataset |