迭代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 通过
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# 技术实现方案: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]` 取值) | 比较/逻辑/三元/自定义运算符 | 中 | 取值语义与「缺值短路」需自行包装 |
|
||||
| **CEL(cel-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 条断言)。
|
||||
Reference in New Issue
Block a user