DSL 如何编译成规则:从规则树到执行引擎
很多量化平台的策略系统最终都会走到一个问题:
策略到底应该“写代码”,还是“可视化搭积木”?
写代码灵活,但不可治理、不可复用、难协作;
可视化好交付,但容易被质疑“能力不够”。
结论先行:EasyQuant 选择用 DSL 规则树 描述策略,用 编译(compile) 的方式把 DSL 映射到 Indicator/Rule/Strategy,从而同时获得:
- 平台级的版本治理与可视化编辑
- 接近代码级的表达能力(AND/OR/CROSS/比较/自定义扩展)
- 可解释与可复现的输入口径(见 03、04)
一、为什么一定要“编译”,而不是“解释执行”?
平台常见两种做法:
解释执行:每次评估直接走 DSL 树,边走边算
- 优点:实现快,直观
- 缺点:性能难控,复用难做,错误上下文难沉淀
编译:把 DSL 编译成指标与规则对象图
- 优点:可缓存复用,便于优化与治理;错误更可定位
- 缺点:需要设计良好的中间结构与工厂体系
EasyQuant 更偏“平台交付”,因此选择编译路线:把策略变成可复用的可执行对象,并为未来优化(缓存、并行、增量计算)留空间。
二、DSL 规则树能表达什么?
从产品视角,关键不是 DSL 长什么样,而是它能覆盖什么常见策略表达:
- 逻辑组合:AND / OR / NOT
- 比较:GT / LT / EQ(价格 vs 指标、指标 vs 指标)
- 交叉:CROSS_UP / CROSS_DOWN(均线金叉死叉、价格突破等)
- 常量与参数引用:const / ref
- 指标引用:ind(把指标当作一等公民)
- 可扩展节点:自定义规则(例如 Groovy 扩展)
这套表达能力的意义在于:策略作者关注“逻辑”,平台负责“执行与治理”。
三、编译流水线:从 JSON 到可执行策略对象
用一句话描述编译流水线:
- 读取激活版本(或回退模板)得到 DSL + 参数(见 04 的 provenance)
- 构建指标图:把每个 indicator 节点编译成 系统
Indicator<Num> - 构建规则图:把 entry/exit 的 rule 节点编译成 系统
Rule - 组合成
Strategy,交给执行引擎在 bars 序列上评估
这里的关键是 “指标图”:同一个指标可能被多个规则引用,必须复用,否则会造成重复计算与不一致。
四、可解释性从哪里来?
很多系统做了 DSL,但仍然解释不清“为什么没信号”。
根因是:只做了“规则计算”,没做“运行证据”。
EasyQuant 的产品化方向是:
- 规则评估结果(true/false)要能落到运行事件里
- 阻塞原因要能归类(NO_BARS / NO_SYMBOLS / SIGNAL_NONE / ENGINE_ERROR 等)
- 关键输入要能在 provenance 里对齐(版本/参数/数据窗口/成本口径)
最终,用户在 UI 上看到的不是“一个黑盒买卖点”,而是:
这次为什么触发/为什么没触发,属于哪一类问题,下一步查哪里。
五、可扩展点:如何在不破坏平台治理的情况下“更灵活”
平台的挑战是:既要给高级用户空间,又不能把系统变成“随便写脚本”。
推荐的扩展边界:
- 指标扩展:新增 indicator 类型(参数有中文说明、可校验、可复用)
- 规则扩展:新增 rule 节点(例如 N 日突破、分形、形态识别)
- 自定义规则脚本:仅作为“受控扩展”,必须有沙箱与白名单(防止不可控 I/O 与安全风险)
原则是:扩展必须仍然能被版本化、可复现、可诊断,否则交付能力会被破坏。
六、一个验收:从“策略没生效”到“问题可定位”
你可以用一个常见坑来验收 DSL 编译体系是否真具备平台价值:
场景:你改了规则但回测行为没变。
验收步骤:
- 确认是否发布并激活版本(VERSION_NOT_ACTIVE)
- 查看 provenance 中记录的 versionId 与 params 是否符合预期
- 若 version 正确但仍无信号,检查 SIGNAL_NONE / NO_BARS / ENGINE_ERROR 的归类
如果这条路径能在 5–10 分钟内收敛问题,而不是“改完再试”,说明 DSL 编译体系不仅能跑策略,更能支撑交付。
结语:DSL 的价值不在“可视化”,在“可治理”
可视化只是入口。真正的价值是:策略变成可版本化、可复现、可诊断、可运维的可执行对象。
这也是 EasyQuant 与“纯回测框架”最本质的区别之一。
下一篇(08)我们回到产品化细节:指标库与前后端一致性(catalog、中文参数说明、DSL 最小化),它决定了可视化策略是否真的好用、好维护。