Files
one_divine_lot/docs/04-迭代记录/23-计算字段判定型扩展/技术实现方案.md
T
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

89 lines
6.7 KiB
Markdown
Raw 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.
# 技术实现方案:23-计算字段判定型扩展
> 依据:R-028(已定稿)+ R-026 既有实现(迭代 22)| 日期:2026-09-10 状态:**已实施**
## 1. 公式引擎(src/formula/evaluator.js
- **词法**:新增多字符运算符识别(`>= <= == != && ||` 优先于单字符);全角/符号归一扩展
`≥→>= ≤→<= ≠→!= >→> <→< =→=`
- **语法优先级**(低 → 高):`or``and` → 比较(`> < >= <= == !=`) → 加减 → 乘除 → 一元负 → 主;
`and`/`or` 以标识符(大小写不敏感)识别,`&&`/`||` 等价;
- **求值**:比较返回布尔;逻辑返回布尔(`!!l && !!r`);两者**缺值短路**(任一操作数为 null → null);
数字运算维持原语义(除零 → null、最终结果浮点归一);`normalizeResult` 对布尔直通;
- **新增 `inferResultKind(ast)`**:顶层为比较/逻辑 → `'boolean'``neg` 递归下钻),否则 `'number'`
- 校验(`validateFormula`)逻辑不变(变量存在性 / 函数白名单 / arity)。
## 2. 存储与校验
- **src/settings.js**`fieldSchema``resultKind: z.union([z.const('number'), z.const('boolean')]).default('number')`
`normalizeFields` 对 formula 字段归一 `resultKind`(非 `'boolean'` 一律 `'number'`),`decimals` 维持夹取 0-4
- **src/api/strategies.js#validateConfigSchema**:公式校验通过后增加**结果类型一致性**校验 ——
`declared = resultKind`(默认 number)必须等于 `inferResultKind(compileFormula(formula).ast)`
不一致抛 `field-validation`
- 声明「判定」但公式无比较 → 「结果类型选了「判定」,但公式没有比较运算(如 涨停价 > 基准值 + 1)」;
- 公式是判定但声明「数字」 → 「公式结果是判定,请把「结果类型」改为「判定」」。
## 3. 现算(src/formula/FormulaService.js
`computed[key]` 布尔直通:
```js
computed[c.key] = (typeof v === 'boolean') ? v
: ((typeof v === 'number' && Number.isFinite(v)) ? v : null);
```
其余不变(无 formula 字段零开销、只读 QuoteHub 内存缓存、不落库、历史行不计算)。
## 4. 前端
**StrategyFieldsEditor.jsx**
- 表单草稿增 `resultKind`(默认 `number`,编辑时回填);
- 「类型」行新增「结果」下拉(数字 / 判定);
- 公式框 placeholder 按结果类型切换(判定示例:`涨停价 > 基准值 + 网格大小`);
- 公式框下新增运算符提示行:`运算:+ - × ÷ 括号;判定:> < >= <= == != ,组合:and / or`
- `resultKind === 'boolean'` 时**隐藏小数位**输入;
- 列表「类型」列显示 `计算(判定)`
- **判定型试算**`value` 为布尔时显示 `试算:<名称> <代码> → ✓ 满足 / — 不满足`
**StrategyTab.jsx**
- 计算列渲染:**先判布尔**`typeof raw === 'boolean'`)—— `true``✓ <字段展示名>``state-success-primary`),
`false` / 缺值 → `—`(tertiary);否则走原数字格式化(`toFixed(decimals)` + 单位);
顺序不可颠倒(`Number(false) === 0` 会被误格式化成 `0.00`);
- 表头 `ƒ` 前缀、悬停公式原文、历史行 `—` 维持不变。
## 4.5 附:公式引擎选型论证(2026-09-10 老师提问「有没有现成的库」)
**结论:维持自写引擎**`src/formula/evaluator.js`,约 250 行 / 零依赖 / 71 条回归断言)。
| 候选 | 中文变量名 | 能力覆盖 | 体积/依赖 | 风险与维护 |
|---|---|---|---|---|
| **expr-eval** | ✗ ASCII 标识符 | 四则/比较/and·or/**三元 ?:**/属性访问/数组索引 | 小 | **CVE-2025-12735**:构造 variables 对象可致**任意代码执行**GHSA-jc85-fpwf-qm7x);上游维护停滞,社区有 fork |
| **mathjs** | ✗ 明确枚举可用字符(拉丁/希腊/字母类/数学字母数字),**不含 CJK** | 极全(矩阵/单位/复数/函数库) | 大(数百 KB 级) | 重;unicode 标识符诉求自 2015 年 issue #265 起未覆盖 CJK |
| **Jexl** | ✗ ASCII 标识符(支持 `a.b` / `a[b]` 取值) | 比较/逻辑/三元/自定义运算符 | 中 | 取值语义与「缺值短路」需自行包装 |
| **CELcel-js 等)** | ✗ ASCII 标识符 | 受限表达式语言(工业标准、可静态检查) | 中 | 语义偏严格,接入成本不低 |
| **解析器工具包**chevrotain / ohm-js | ✓(自定义词法) | 自己写语法 + 求值 | 中 | **等于仍自己实现**,只是换成语法文件 + 依赖 |
| **自写引擎(现状)** | ✓ 原生支持 | 四则/括号/比较/and·or/round·abs·min·max | 0 | 无 eval/Function、无属性访问、无数组索引、变量来自白名单目录 → **结构上无任意代码执行面** |
**为什么不换**
1. **中文变量名是产品决策**(R-026 Q2 老师拍板)。主流库标识符均限 ASCII → 引库就得加「中文名 → 占位符」映射层
(按长度降序替换防前缀冲突 + 错误位置与提示回译),**复杂度不降反升**,报错体验还变差;
2. 所需能力集很小,自写约 250 行即覆盖,且**语义完全可控**(缺值短路 → `—`、除零 → `—`、最终值浮点归一);
3. **攻击面**:表达式与变量都来自用户输入,expr-eval 恰恰因变量对象注入出过 RCE;自写引擎不做属性访问/动态函数调用(仅 4 个纯数学函数白名单),无此类面;
4. 项目为 DSH 外部插件,依赖越少越稳(现依赖仅 schemastery + MCP SDK)。
**什么时候值得换成库(判据)**
- 需要**条件分支/三元**(如三态信号「空/多/观望」)→ 自写加 `?:` 约 30 行即可,仍不必引库;
- 需要**字符串/日期/正则** → 能力超出「数值公式」,届时评估 CEL / Jexl(并重做安全评估);
- 需要**Excel 级函数库**SUM/IF/VLOOKUP…)→ 考虑 hyperformula / Formula.js,但那是产品形态变化(表格公式),应先讨论需求。
> 若后续决定改用第三方库,需同步修订**技术约束-022**(现规定「自写零依赖、禁止 eval/Function」)。
- `scripts/test-r026-formula-fields.mjs` 新增两节(第 7 节判定型 15 条 + 第 8 节端到端 8 条):
比较/and/or/全角符号/短路、`inferResultKind` 三例、结果类型一致性双向拒绝、布尔直通、
`strategy-positions` 附判定 `computed`、判定试算;
- **端到端夹具 = 真实数据**:以 2026-09-10 实盘涨跌停(积成电子 8.22/6.72 等)+ 真实持仓基准值构造三行,
断言 `可下空单 / 可下多单` 判定与实算一致;
- `npx tsc --noEmit` 0 错;`npm run build` 通过;全量 **17 个回归脚本全绿**r026 由 48 → 71 条断言)。