Nautilus Trader 合成工具(Synthetic Instruments)完全指南:公式语言、创建流程与性能原理
【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader
合成工具(Synthetic Instrument)是 Nautilus Trader 平台中的一类本地定义工具:其价格由其他(一个或多个)真实工具通过一段派生公式计算得出,并以标准 Nautilus 工具的形式暴露给策略层,交易所代码固定为SYNTH。本文以 docs/concepts/synthetics.md 为核心骨架,结合crates/model/src/instruments/synthetic.rs、crates/model/src/expressions/表达式引擎与数据引擎订阅逻辑,系统讲解公式语言语法、运算符优先级、内置函数、类型规则、编译期限制、创建与更新流程、仿真订单触发(emulation trigger)以及热路径性能。读完本文,你将能够自行编写并接入价差(spread)、加权平均、条件输出等合成工具,并理解其底层"编译一次、求值多次"的零分配求值架构。
合成工具不是用于直接交易的:它存在于平台本地,是纯分析工具。其典型用途包括:让DataActor与Strategy订阅由派生价格产生的行情或成交流、从派生价格触发仿真(emulated)订单、以及基于合成行情构建 K 线。Nautilus 未来可能会支持基于合成工具行为直接交易其组成工具,但当前版本不提供此能力。
合成工具的核心概念
从源码结构看,SyntheticInstrument在 crates/model/src/instruments/synthetic.rs 中定义,其公共字段包括:
id: InstrumentId:唯一标识,形如{symbol}.SYNTH;price_precision: u8与price_increment: Price:价格精度与最小价格增量(由精度推导,见new_checked中对Price::from_mantissa_exponent_checked(1, -price_precision, price_precision)的调用);components: Vec<InstrumentId>:组成工具的 ID 列表;formula: String:派生公式原文;ts_event/ts_init:事件时间与初始化时间(纳秒级 Unix 时间戳)。
此外还有两个私有字段component_names与compiled_formula:前者是组件 ID 的字符串列表,后者是公式编译后的字节码。构造时(build_checked/new_checked)会完成公式校验与编译,反序列化(Deserialize)时也会重新编译,保证加载即校验。
一个关键实现细节是:组件绑定在 crates/model/src/expressions/mod.rs 的Bindings结构中维护——纯标识符(字母、数字、下划线)走哈希表 O(1) 查找;包含/、-、.等特殊字符的 ID 则按首字符分组、按名称长度降序排序,采用最长匹配策略解析,这正是公式可以直接书写AUD/USD.SIM、ETH-USDT-SWAP.OKX这类 ID 的原因。
公式语言(Formula Language)
每个合成工具都定义一段派生公式。Nautilus 用内置数值表达式引擎求值公式,并把最终数值结果转换为合成工具的Price。公式支持引用组件InstrumentId原文(包括含/与-的 ID)。
支持的语法
| 构造 | 示例 | 说明 |
|---|---|---|
| 组件引用 | BTCUSDT.BINANCE | 直接使用原始InstrumentId文本 |
| 组件引用 | AUD/USD.SIM | 含/的 ID 合法 |
| 组件引用 | ETH-USDT-SWAP.OKX | 含-的 ID 合法 |
| 数值字面量 | 1、0.5、1.2e-3 | 按f64语义求值 |
| 布尔字面量 | true、false | 用于条件与逻辑表达式 |
| 括号 | (a + b) / 2 | 覆盖默认优先级 |
| 一元运算符 | -x、!flag | 一元-取负、一元!取反 |
| 二元运算符 | + - * / % ^、== !=、< <= > >=、&& \|\| | 算术作用于数值,逻辑作用于布尔 |
| 局部赋值 | spread = a - b; spread / 2 | 语句从左到右执行,公式必须以一个值结尾 |
| 注释 | // line、/* block */ | 注释被忽略 |
注意:新公式应使用原始
InstrumentId。为了向后兼容,把组件 ID 中的-替换为_的旧式公式仍然被接受——这对应源码 crates/model/src/instruments/synthetic.rs 中build_bindings为连字符 ID 注册replace('-', "_")别名的逻辑(测试用例test_hyphenated_instrument_ids_support_legacy_sanitized_formula验证了这一点)。
运算符优先级
表达式引擎按下表从高到低求值:
| 级别 | 运算符 | 说明 |
|---|---|---|
| 最高 | ^ | 幂运算,右结合 |
一元-、一元! | -2 ^ 2求值为-(2 ^ 2) | |
*、/、% | 乘法、除法、取模 | |
+、- | 加法、减法 | |
<、<=、>、>= | 数值比较 | |
==、!= | 相等/不等,两侧类型必须一致 | |
| 最低 | &&、\|\| | 布尔运算符 |
赋值不是表达式运算符。语句之间用;分隔,最后一条语句必须是合成工具要产出的值。
内置函数
| 函数 | 签名 | 说明 |
|---|---|---|
abs | abs(x) | 绝对值 |
ceil | ceil(x) | 向上取整 |
floor | floor(x) | 向下取整 |
round | round(x) | 按 Rustf64规则四舍五入到最近整数 |
min | min(x1, x2, ...) | 接受一个或多个数值参数 |
max | max(x1, x2, ...) | 接受一个或多个数值参数 |
if | if(condition, when_true, when_false) | 条件必须为布尔;两个分支类型必须一致;只求值被选中的分支 |
类型规则
- 组件输入是数值类型;
- 算术运算符要求数值操作数并返回数值;
<、<=、>、>=要求数值操作数并返回布尔;==与!=接受任意匹配类型(同为数值或同为布尔),返回布尔;&&、\|\|与一元!要求布尔操作数;&&与\|\|会短路,右侧仅在需要时才求值;- 局部变量必须先赋值后使用;
- 局部变量名必须以 ASCII 字母或
_开头,其后只能包含 ASCII 字母、数字或_; - 公式最终结果必须是数值。以赋值结尾或产出布尔结果的公式对合成工具无效——源码中
compile_numeric会检查result_type()并返回NonNumericResult错误(见 crates/model/src/expressions/mod.rs)。
编译期限制
表达式引擎在编译期强制以下限制,超限公式会在构造时得到明确报错:
| 限制 | 值 | 说明 |
|---|---|---|
| 栈深度 | 32 | 求值栈上最多可容纳的中间值数量 |
| 局部变量 | 16 | 最多可用的不同局部变量名数量 |
| 嵌套深度 | 128 | 最大语法嵌套与表达式树深度,顶层表达式计为一层 |
以上常量对应 crates/model/src/expressions/eval.rs 中的MAX_STACK = 32、MAX_LOCALS = 16,以及 crates/model/src/expressions/parser.rs 中的MAX_EXPRESSION_DEPTH = 128。
一个 8 组分的加权和峰值栈深度为 3、零局部变量;N 个组分的加权和会构建深度为 N + 1 的表达式树,因此嵌套深度限制把加权和封顶在 127 个组分。测试用例test_new_checked_rejects_excessive_expression_depth(129 项1 + 1 + ...)验证了超限报错文案:"Expression nesting depth 129 exceeds maximum 128"。
公式示例
# Simple spread formula = "BTCUSDT.BINANCE - ETHUSDT.BINANCE" # Average of two FX pairs formula = "(AUD/USD.SIM + NZD/USD.SIM) / 2" # Reuse an intermediate value formula = "spread = BTCUSDT.BINANCE - ETHUSDT.BINANCE; spread / 2" # Conditional output formula = "if(BTCUSDT.BINANCE > ETHUSDT.BINANCE, BTCUSDT.BINANCE, ETHUSDT.BINANCE)"创建合成工具
创建前请确保所有组件工具都已存在于缓存(cache)中,并同时订阅每个组件的报价或成交流以及合成工具自身的流。合成报价只由组件报价推导,合成成交只由组件成交推导。
当某个组件 tick 到达时,引擎会把该 tick 与其他组件最新的缓存价格组合起来计算合成价格。在所有组件都至少产生过一个 tick 之前,合成工具不会发布任何东西。
下面的示例在一个 actor 或 strategy 中创建合成工具,表示 Binance 上 BTC 与 ETH 现货的简单价差,并假定BTCUSDT.BINANCE与ETHUSDT.BINANCE已存在于缓存:
from nautilus_trader.model import SyntheticInstrument btcusdt_binance_id = InstrumentId.from_str("BTCUSDT.BINANCE") ethusdt_binance_id = InstrumentId.from_str("ETHUSDT.BINANCE") synthetic = SyntheticInstrument( symbol=Symbol("BTC-ETH:BINANCE"), price_precision=8, components=[ btcusdt_binance_id, ethusdt_binance_id, ], formula=f"{btcusdt_binance_id} - {ethusdt_binance_id}", ts_event=self.clock.timestamp_ns(), ts_init=self.clock.timestamp_ns(), ) self._synthetic_id = synthetic.id self.add_synthetic(synthetic) self.subscribe_quotes(self._synthetic_id)注意:上例中合成工具的
instrument_id是{symbol}.SYNTH,即BTC-ETH:BINANCE.SYNTH。这与源码SyntheticInstrument::new_checked中InstrumentId::new(symbol, Venue::synthetic())的构造逻辑一致。
price_precision决定合成价格的精度与最小增量:例如精度 8 时最小价格增量为1e-8(源码中由Price::from_mantissa_exponent_checked推导,test_new_checked_constructs_exact_price_increment用例对精度 0、5、FIXED_PRECISION均做了验证)。
从数据引擎源码看,订阅行为是分类的:SubscribeCommand::Quotes/Trades且instrument_id.is_synthetic()时进入subscribe_synthetic_quotes/subscribe_synthetic_trades分支,而合成工具的Instrument、InstrumentStatus、InstrumentClose订阅会被直接拒绝(见 crates/data/src/engine/mod.rs)。
更新公式
合成公式可以随时更新:
synthetic = self.cache.synthetic(self._synthetic_id) new_formula = "(BTCUSDT.BINANCE + ETHUSDT.BINANCE) / 2" synthetic.change_formula(new_formula) self.update_synthetic(synthetic)源码中change_formula会针对现有组件重新编译公式:编译失败则返回错误且不修改原公式(测试test_change_formula_rejects_invalid_formula_without_mutation验证了失败时公式与计算结果均保持原样)。update_synthetic是 actor/strategy 层的公开接口(crates/common/src/actor/data_actor.rs中的update_synthetic委托给核心组件,且要求合成工具已注册,见test_update_synthetic_panics_when_unregistered)。
触发仿真订单(Trigger Instrument ID)
可以用合成价格触发仿真订单。下面的示例中,一旦合成价格达到触发条件,合成工具就释放一个仿真订单:
order = self.order_factory.limit( instrument_id=InstrumentId.from_str("ETHUSDT.BINANCE"), order_side=OrderSide.BUY, quantity=Quantity.from_str("1.5"), price=Price.from_str("30000.00000000"), emulation_trigger=TriggerType.DEFAULT, trigger_instrument_id=self._synthetic_id, ) self.submit_order(order)这里订单的instrument_id是真实工具ETHUSDT.BINANCE,而trigger_instrument_id指向合成工具。相关文档可继续阅读 Orders,其中说明订单可以使用合成工具 ID 作为仿真触发条件。
性能
公式在构造时编译一次,然后在每个到来的组件价格 tick 上求值。表达式引擎采用"编译一次、求值多次"(compile-once/eval-many)架构,配合零分配的f64栈(let mut stack = [0.0_f64; MAX_STACK]的定长内联栈,见 crates/model/src/expressions/eval.rs),求值对 tick 处理路径的额外开销可以忽略不计。
以下数据在 Apple M4 Pro、rustc 1.94.1、release profile(opt-level 3)下测得:
求值(热路径)
| 公式模式 | 耗时 |
|---|---|
(A + B) / 2.0 | 12 ns |
A * 0.4 + B * 0.3 + C * 0.2 + D * 0.1 | 18 ns |
if(A > B, A - B, B - A) | 12 ns |
spread = A - B; mid = ...; mid + ... | 19 ns |
max(min(A, B * 20), abs(A - B)) | 15 ns |
求值扩展性(加权和)
| 组件数 | 耗时 |
|---|---|
| 2 | 14 ns |
| 4 | 18 ns |
| 8 | 28 ns |
编译(冷路径)
| 公式模式 | 耗时 |
|---|---|
| 简单平均 | 675 ns |
| 4 输入加权 | 1.4 us |
| 条件式 | 1.0 us |
| 含局部变量 | 1.3 us |
| 连字符 ID | 755 ns |
从求值扩展性可以看出,组件数翻倍带来的开销增量很小(2→4→8 组件为 14→18→28 ns),与"零分配栈式求值 + 输入槽位直接装载"的实现方式吻合。编译器在eval::compile阶段还会做常量折叠等优化,例如Instruction::PushNumber与PushBool直接将字面量压栈。
错误处理
Nautilus 在每一个边界都会校验合成工具:
- 编译期:公式编译会拒绝未知符号、类型错误与容量超限。例如引用未在
components中声明的标识符会得到 "Unknown symbolmissing" 错误(见 crates/model/src/expressions/error.rs 与合成工具测试用例)。 - 求值期:求值会拒绝错误的输入数量与非有限价格(NaN、Infinity)。输入数量不匹配返回
InputCountMismatch("Expected 2 input values, received 1"),缺失组件价格返回MissingInput("Missing price for component: ..."),非有限输入返回NonFiniteInput;若公式结果无法构成合法Price,则返回InvalidPriceResult。所有错误类型定义在 crates/model/src/instruments/synthetic.rs 的SyntheticInstrumentError中。 - 除了求值校验,
calculate还会对组件名称与输入值逐项做有限性检查,且calculate_from_map在组件数超过 8(MAX_INLINE_COMPONENTS)时会回退到堆分配路径,保证大组件集同样可用。
合成工具的输入要求与异常详见SyntheticInstrument的 API 参考(模型工具 API)。
关联阅读
- Instruments(工具定义与各交易所工具类型)
- Data(引用工具的市场数据类型)
- Orders(订单可使用合成工具 ID 作为仿真触发条件)
上述内容中,公式语言、创建/更新流程、触发与性能数据均直接继承自 docs/concepts/synthetics.md;表达式引擎的栈/局部变量/嵌套限制常量、绑定解析策略、求值期错误类型与"编译一次求值多次"的实现,均由仓库源码 crates/model/src/expressions/、crates/model/src/instruments/synthetic.rs 与 crates/data/src/engine/mod.rs 印证。
【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考