Files
kyugao ec02a791ee 迭代22+23: 策略tab静态化 + 计算字段(数字/判定型)+ 网格超市信号字段
- R-027 静态化:策略恒为内置两项(不可增删改名,保留显隐/排序);设置页移除「策略分组」;
  字段配置入口迁到策略 tab 内「列设置」旁(FieldConfigDialog + schema-update 单策略写入)
- R-026 计算字段:自写公式引擎(中文变量、四则/括号/round·abs·min·max、缺值短路→—)+
  变量目录(行情/合约/持仓/自定义字段)+ strategy-positions 逐行现算(只读内存缓存,不落库、列只读)
- R-028 判定型:比较运算 + and/or + inferResultKind + 结果类型一致性校验 + 判定列 ✓/— 渲染;
  网格超市落地 可下空单=涨停价>基准值+网格大小、可下多单=跌停价<基准值-网格大小
- 变量选择改标签平铺(老师反馈);公式手册 docs/99-其他材料/计算字段公式说明.md
- 约束同步:产品约束-002/003/004/009/013/014、技术约束-014/015/022/023/024、UI约束-002/003/005/008
- 回归:新增 test-r026/test-r027,更新 r011/r013/r017,17 个脚本全绿;typecheck/build 通过
2026-09-10 14:05:06 +08:00

106 lines
8.9 KiB
Markdown
Raw Permalink 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.
# 迭代复盘:22-策略计算字段与策略 tab 静态化
## 结论
**实现完成(两阶段全范围),待老师人工验收**(尚未宣告验收通过)。事实:策略 tab 静态化 + 字段配置入口迁移(R-027)
与计算字段(R-026)均已落地;`pnpm typecheck` 0 错、`pnpm build` 通过、17 个回归脚本全绿(含新增 2 个)。
## 事实记录
### 阶段一 · 策略 tab 静态化与入口迁移(R-027)
- **src/settings.js**:新增 `BUILTIN_STRATEGIES`(内置两项:网格超市 grid-supermarket / 手动做T manual-t)、
`isBuiltinStrategy / assertBuiltinStrategy / normalizeFields`;字段定义真相源迁到 **`strategyFields`**
`getStrategies` 恒返回内置两项、缺失时兼容读取旧 `strategies[].configSchema``getStrategyFields` /
`updateStrategyFields` 为唯一读写入口);`normalizeTabs` 改为**常量派生**3 内置 tab + 2 策略 tab
只保留存量 visible/order 偏好、丢弃未知条目、补齐缺失条目),`updateTabs` 只接受内置 id
`normalizeStrategyColumns` 的字段列来源改读 `getStrategyFields`;新增 `scopeValue` 容错读取
(测试桩无 `get` 不炸);**退役** `addStrategy / removeStrategy / renameStrategy / updateStrategies /
appendStrategyTab / removeStrategyTab / DEFAULT_STRATEGIES`
- **src/api/strategies.js****退役** `strategies/add | strategies/remove | strategies/update`(调用返回 not-found);
新增 `strategies/schema-update { strategyId, configSchema }`(唯一写路径)+ `validateConfigSchema`
(结构 / key 唯一 / 展示名唯一 / 内置变量名冲突 / 枚举选项 / 公式校验,失败统一 `code='field-validation'`);
新增 `formula/variables`(变量目录)与 `formula/trial`(试算)。
- **设置页**SettingsSection.jsx):「策略分组」子 tab 整体移除(含 CRUD 与删除确认)→ 设置页仅
「Tab 设置 / QMT 连接配置」。
- **字段配置弹层**FieldConfigDialog.jsx 新建 + StrategyFieldsEditor.jsx 改造):策略 tab 顶栏
「字段配置」(列设置左侧)→ 锚定弹层(520px / 70vh / 内部滚动);字段列表 + 添加/编辑/删除 + 底部
[关闭]/[保存字段]dirty 时关闭/外点/Esc 二次确认;保存走 `strategies/schema-update`
### 阶段二 · 计算字段(R-026)
- **src/formula/evaluator.js**(自写零依赖引擎,不用 `eval/Function`):词法支持中文标识符 + 全角归一
(()+-×÷),递归下降解析(四则 / 括号 / 一元负 / round·abs·min·max;长度 ≤500、深度 ≤32);
`validateFormula` 返回 `{ok,vars}``{ok:false,error:{code:empty|syntax|unknown-var|unknown-func|arity,message,position}}`
求值**缺值短路**(任一变量 null → 整式 null)、除零/非有限 → null、**最终结果浮点归一**|v|<1e12 四舍五入 10 位,
消除 `0.7000000000000002` 类噪声;中间步骤不归一再保精度——首版曾因每步归一导致 `6.66666667` 精度劣化,已改)。
- **src/formula/variables.js**:变量目录单一入口(行情 7 项 / 合约 2 项 / 持仓账本 3 项 + 运行时按策略
非 formula 字段展示名展开「自定义字段」组);`allowedVarNames` 排除 formula 字段(零循环依赖);
`buildContext` 取数(昨收兜底合约 preClose、布尔 → 0/1、空值 → null)。
- **src/formula/FormulaService.js**`computeRows``strategy-positions` 行附 `computed`;行情/合约
**只读 QuoteHub 内存缓存(不读穿透)**;**无 formula 字段直接返回原数组(零开销)**;值不落库。
- **前端**:字段表单类型「计算」分支(等宽公式框 + 「+ 插入变量」分组菜单 + 试算 + 小数位 0-4 默认 2;
保存失败公式框描红 + 服务端文案);持仓表计算列**只读**(表头 `ƒ` + title 公式原文、按 `toFixed(decimals)` + 单位
格式化如 `6.67 %` / `700.00 元`、缺数据 `—`、点击不进内联编辑);历史持仓行 `—`;列设置弹层列名带 `ƒ`
### 实机数据核实(实施前)
- `~/.dsh/settings.yaml`:策略仅两个内置(网格超市 5 字段 / 手动做T 1 字段);
- `~/.dsh/one-divine-lot/store.db``strategy_holdings` 13 / 3 行,交易与归属数据只涉及这两个策略
**无自建策略,静态化零数据迁移**
### 验证
- `pnpm typecheck` → 0 错;`pnpm build` → 通过(lib 32 文件 + client bundle 195545 bytes`scripts/wrap-client.mjs` 正常);
- 回归脚本 **17 / 17 全绿**
- 新增 `scripts/test-r027-static-strategies.mjs` 35/35(策略常量 / 旧定义兼容 / 单策略写入 / tabs 归一化 / 端点退役);
- 新增 `scripts/test-r026-formula-fields.mjs` 48/48(引擎 / 目录 / 上下文 / 现算 / 保存校验 / 试算);
- 更新 `test-r011-tabs` 22/0、`test-r011-api` 17/0、`test-r013-custom-fields` 23/0、
`test-r013-columns` 18/0(期望值按 R-015 行情列 + R-027 真相源更新)、`test-r017-candidates` 12/0(策略 id 改用内置);
- 其余(r009 系列 / r016 / r018 / quote-sync / position-sync / health / mcp)零改动全绿。
## 偏差、顺带修复与发现
1. **`generateStrategyId` / `PINYIN_MAP` 保留**(方案原文列入退役):QMT 连接 id 生成
`generateQmtConnectionId` 仍依赖,本轮不动(已回写技术方案)。
2. **真 bug(实施中发现并修复)**`normalizeStrategyColumns` 仍读旧 `strategies[].configSchema`
字段定义迁到 `strategyFields` 后策略列会丢失字段列 → 改为 `getStrategyFields(scope, strategyId)`
教训:真相源迁移必须全量搜「读点」而不只是「写点」。
3. **兼容修复**`strategy-positions` 新增 settings 依赖后,测试里无 `get` 的 settings 桩会抛错
test-r016/test-r017 暴露)→ 新增 `scopeValue` 容错读取。
4. **顺带修复迭代 20 复盘遗留第 1 项**`test-r013-columns` 期望过期(R-015 增加 4 个默认隐藏行情列后应为
12 列)——本次随实现更新期望并补「strategyFields 优先」用例。
5. **`test-r017-candidates` 策略 id 迁移**`attribution-targets``getStrategies` 取策略表,
静态化后仅内置两项 → 测试改用 `manual-t / grid-supermarket`R-027 的必然结果,非缺陷)。
6. **交互边界修复**:字段配置弹层的「外点关闭」原以弹层面板为界,点「字段配置」按钮自身会触发放弃确认
(dirty 时甚至静默卸载)→ 改为以**锚点容器**(含两个按钮)为界。
7. **工作区背景(非本轮产出)**:仓库工作区另含迭代 19/20/21R-022/R-023/R-024/R-025)的未提交改动,
本轮未触碰其逻辑,仅在其上叠加;建议老师确认后一并提交。
## 遗留与风险
- **行情 miss 显示 `—`**:计算列取数只读内存缓存、不读穿透(技术约束-023);某 code 无快照时该格为空,
等 QuoteSync 下轮(≤5s)覆盖。若老师更希望「宁可慢也要立刻有价」,可改判为读穿透。
- **需要重载插件才能人工验收**`~/.dsh/profiles/web/node_modules/one-divine-lot` 是指向本工作区的软链,
lib 已构建完成;老师侧需重载插件(或重启 dsh web)后在页面验收(未由我启动任何服务)。
- **旧 `strategies[].configSchema` 仅只读兼容**:本机字段定义在其首次保存后落入 `strategyFields`
(不写迁移脚本、不动库数据)。
- 自建策略若未来出现:读取忽略、tab 丢弃,其 `strategy_holdings` 数据保留在库(本机无此情况)。
## 待老师确认事项(人工验收)
`验收标准.md`**A1A11**(重构)+ **B1B12**(计算字段),重点四条:
1. 设置页无「策略分组」;策略 tab 恒两个、不可增删改名,但 Tab 设置仍可显隐/拖拽排序;
2. 策略 tab 顶栏「字段配置 / 列设置」可用,字段增删保存后表格列跟随、重启后定义仍在;
3. 计算字段可用:中文变量公式 + 变量选择器 + 试算 → 持仓列只读显示结果(`ƒ` 标记 + 悬停看公式);
4. 边界:非法公式/未知变量/名称冲突被拒 + 描红;缺数据与历史行显示 `—``strategy_holdings.values` 不含计算字段键。
## 经验沉淀(候选)
- **并行委派的契约必须在开工前冻结**:本轮前端由子代理并行实现,期间服务端 `settings.js` 仍在演进
(子代理观察到 mtime 变化、并在报告里提出「疑似工作区未冻结」)——契约冻结 + 文件集互斥是并行可行性的前提。
- **真相源迁移要全量搜读点**:本次 `normalizeStrategyColumns` 差点漏改,靠既有回归脚本的红灯暴露;
遗留的旧键兼容读取(而非直接删键)让迁移零风险、零脚本。
- **测试期望也需要跟着设计演进**`test-r013-columns` 的过期期望在迭代 20 复盘就被记录却未处理,
本轮顺手清偿,避免「红灯常态化」。