Nautilus Trader 合成工具(Synthetic Instruments)完全指南:公式语言、创建流程与性能原理
2026/9/12 15:02:15 网站建设 项目流程

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.rscrates/model/src/expressions/表达式引擎与数据引擎订阅逻辑,系统讲解公式语言语法、运算符优先级、内置函数、类型规则、编译期限制、创建与更新流程、仿真订单触发(emulation trigger)以及热路径性能。读完本文,你将能够自行编写并接入价差(spread)、加权平均、条件输出等合成工具,并理解其底层"编译一次、求值多次"的零分配求值架构。

合成工具不是用于直接交易的:它存在于平台本地,是纯分析工具。其典型用途包括:让DataActorStrategy订阅由派生价格产生的行情或成交流、从派生价格触发仿真(emulated)订单、以及基于合成行情构建 K 线。Nautilus 未来可能会支持基于合成工具行为直接交易其组成工具,但当前版本不提供此能力。

合成工具的核心概念

从源码结构看,SyntheticInstrument在 crates/model/src/instruments/synthetic.rs 中定义,其公共字段包括:

  • id: InstrumentId:唯一标识,形如{symbol}.SYNTH
  • price_precision: u8price_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_namescompiled_formula:前者是组件 ID 的字符串列表,后者是公式编译后的字节码。构造时(build_checked/new_checked)会完成公式校验与编译,反序列化(Deserialize)时也会重新编译,保证加载即校验。

一个关键实现细节是:组件绑定在 crates/model/src/expressions/mod.rs 的Bindings结构中维护——纯标识符(字母、数字、下划线)走哈希表 O(1) 查找;包含/-.等特殊字符的 ID 则按首字符分组、按名称长度降序排序,采用最长匹配策略解析,这正是公式可以直接书写AUD/USD.SIMETH-USDT-SWAP.OKX这类 ID 的原因。

公式语言(Formula Language)

每个合成工具都定义一段派生公式。Nautilus 用内置数值表达式引擎求值公式,并把最终数值结果转换为合成工具的Price。公式支持引用组件InstrumentId原文(包括含/-的 ID)。

支持的语法

构造示例说明
组件引用BTCUSDT.BINANCE直接使用原始InstrumentId文本
组件引用AUD/USD.SIM/的 ID 合法
组件引用ETH-USDT-SWAP.OKX-的 ID 合法
数值字面量10.51.2e-3f64语义求值
布尔字面量truefalse用于条件与逻辑表达式
括号(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)
*/%乘法、除法、取模
+-加法、减法
<<=>>=数值比较
==!=相等/不等,两侧类型必须一致
最低&&\|\|布尔运算符

赋值不是表达式运算符。语句之间用;分隔,最后一条语句必须是合成工具要产出的值。

内置函数

函数签名说明
absabs(x)绝对值
ceilceil(x)向上取整
floorfloor(x)向下取整
roundround(x)按 Rustf64规则四舍五入到最近整数
minmin(x1, x2, ...)接受一个或多个数值参数
maxmax(x1, x2, ...)接受一个或多个数值参数
ifif(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 = 32MAX_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.BINANCEETHUSDT.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_checkedInstrumentId::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/Tradesinstrument_id.is_synthetic()时进入subscribe_synthetic_quotes/subscribe_synthetic_trades分支,而合成工具的InstrumentInstrumentStatusInstrumentClose订阅会被直接拒绝(见 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.012 ns
A * 0.4 + B * 0.3 + C * 0.2 + D * 0.118 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

求值扩展性(加权和)

组件数耗时
214 ns
418 ns
828 ns

编译(冷路径)

公式模式耗时
简单平均675 ns
4 输入加权1.4 us
条件式1.0 us
含局部变量1.3 us
连字符 ID755 ns

从求值扩展性可以看出,组件数翻倍带来的开销增量很小(2→4→8 组件为 14→18→28 ns),与"零分配栈式求值 + 输入槽位直接装载"的实现方式吻合。编译器在eval::compile阶段还会做常量折叠等优化,例如Instruction::PushNumberPushBool直接将字面量压栈。

错误处理

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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询