NautilusTrader SyntheticInstrument 合成工具完全指南:从公式语言到模拟订单触发
2026/9/12 3:57:43 网站建设 项目流程

NautilusTrader SyntheticInstrument 合成工具完全指南:从公式语言到模拟订单触发

【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader

本篇技术指南聚焦 NautilusTrader 模型层中的SyntheticInstrument(合成工具)——一种价格由其他工具公式推导而来的本地工具,广泛应用于价差(spread)、篮子(basket)、比值(ratio)等衍生价格场景。读完本文,你将掌握合成工具的字段语义、公式表达式引擎的完整语法与编译限制、Rust/Python 双端构建方式,以及如何在策略中订阅合成行情、动态更换公式,并用合成价格触发模拟订单。

核心概念:什么是 SyntheticInstrument

SyntheticInstrument表示一种本地工具(local instrument),其价格来自对若干其他工具(component instruments)价格的公式运算。它并不是交易所上真实可下单的市场,而是作为系统内一个标准工具形态出现,服务于价差、篮子、比值以及其他衍生价格的分析需求。

典型示例包括:

  • (BTC.BINANCE + LTC.BINANCE) / 2.0——两个币对价格的简单均值;
  • 由若干成分工具价格构成的 ratio 型交易对。

从源码看,SyntheticInstrument定义于 crates/model/src/instruments/synthetic.rs,其核心成员包括idprice_precisionprice_incrementcomponentsformulats_eventts_init,以及两个内部私有字段component_namescompiled_formula(后者保存编译后的表达式,用于每次 tick 的快速求值)。

字段与默认值

构造一个合成工具需要以下字段,其中idprice_increment由系统自动推导:

字段Rust 类型Python 类型必填/默认说明
symbolSymbolSymbol必填合成工具的符号,与SYNTH交易所(venue)组合使用
idInstrumentIdInstrumentId派生symbol.SYNTH组成的工具 ID
price_precisionu8int必填合成价格允许的小数位数
price_incrementPricePrice派生由精度推导出的最小价格步长
componentsVec<InstrumentId>list[InstrumentId]必填公式所使用的成分工具列表
formulaStringstr必填基于成分工具 ID 的数值表达式
ts_eventUnixNanosint必填事件时间戳(纳秒)
ts_initUnixNanosint必填初始化时间戳(纳秒)

注意:Python 端由symbolSYNTHvenue 构造工具 ID;Rust 端在构造时即生成并存储相同的id值。从 crates/model/src/instruments/synthetic.rs 可以看到,new_checked内部通过InstrumentId::new(symbol, Venue::synthetic())生成{symbol}.SYNTH

price_increment的推导逻辑在new_checked中通过Price::from_mantissa_exponent_checked(1, -price_precision, price_precision)完成,即精度为 2 时增量恰为0.01;源码测试test_new_checked_constructs_exact_price_increment验证了精度为 0、5 以及FIXED_PRECISION时的精确步长。

行为特征

  • SyntheticInstrument仅存在于 Nautilus 本地,不代表任何交易所可下单市场;
  • 它始终使用合成 venueSYNTH
  • 公式必须在构造时针对所给的成分工具 ID 成功编译,对象才有效——编译失败会直接返回错误;
  • 它没有 venue 限制、保证金、手续费、订单簿或适配器专属元数据。

这些行为也直接体现在 Python API 存根 python/nautilus_trader/model/init.pyi 中:SyntheticInstrument的构造参数为symbolprice_precisioncomponentsformulats_eventts_init,并提供is_valid_formulachange_formulacalculatecalculate_from_map等方法。

公式语言详解

合成工具的价格由公式引擎求值。Nautilus 使用内置数值表达式引擎对公式进行编译(compile-once)与反复求值(eval-many),最终数值结果转换为合成工具的Price类型。公式语言的完整规范详见 docs/concepts/synthetics.md。

支持的语法

公式可以直接引用成分工具 ID,包括含/-的 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 里的-替换为_的写法仍然被接受。源码中build_bindings会为每个成分注册主绑定,并为含-的 ID 尽力添加_别名(别名与主绑定冲突时自动跳过),这正是 legacy 公式可用的底层原因。

运算符优先级

表达式引擎按以下顺序求值(从最高优先级到最低):

级别运算符说明
最高^幂运算,右结合
一元-、一元!-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 字母、数字或_
  • 公式最终结果必须是数值:以赋值结尾或产出布尔结果的公式,对合成工具而言非法。

编译期限制

表达式引擎在编译期强制执行以下限制,超限的公式会在构造时直接报出明确的错误:

| 限制 | 值 | 说明 | | ---- | -- | ---- | | 栈深度 | 32 | 求值栈上中间值的最大数量 | | 局部变量 | 16 | 不同局部变量名的最大数量 | | 嵌套深度 | 128 | 最大语法嵌套与表达式树深度,顶层表达式计为 1 层 |

这些常量在 crates/model/src/expressions/eval.rs 中即为MAX_STACK = 32MAX_LOCALS = 16。一个 8 成分的加权求和峰值栈深度仅 3、不占用局部变量;N 成分的加权求和会构建深度为 N + 1 的树,因此嵌套深度限制将加权求和的成分数上限封顶在 127 个。源码测试test_new_checked_rejects_excessive_expression_depth也验证了 129 层嵌套会得到 “Expression nesting depth 129 exceeds maximum 128” 的报错。

公式示例

# 简单价差 formula = "BTCUSDT.BINANCE - ETHUSDT.BINANCE" # 两个外汇对的均值 formula = "(AUD/USD.SIM + NZD/USD.SIM) / 2" # 复用中间值 formula = "spread = BTCUSDT.BINANCE - ETHUSDT.BINANCE; spread / 2" # 条件输出 formula = "if(BTCUSDT.BINANCE > ETHUSDT.BINANCE, BTCUSDT.BINANCE, ETHUSDT.BINANCE)"

构建 SyntheticInstrument

Rust 示例

原文档给出的 Rust 构建方式使用 fluent builder:

use nautilus_core::UnixNanos; use nautilus_model::{ identifiers::{InstrumentId, Symbol}, instruments::SyntheticInstrument, }; let synthetic = SyntheticInstrument::builder() .symbol(Symbol::from("BTC-LTC")) .price_precision(2) .components(vec![ InstrumentId::from("BTC.BINANCE"), InstrumentId::from("LTC.BINANCE"), ]) .formula("(BTC.BINANCE + LTC.BINANCE) / 2.0") .ts_event(UnixNanos::default()) .ts_init(UnixNanos::default()) .build() .unwrap();

Rust 端build的底层是build_checkednew_checked:在构造时就完成 ID 生成、price_increment推导、成分名提取与公式编译,任一环节失败都会返回SyntheticInstrumentError。源码测试test_builder_matches_new_checked确认了 builder 构造与位置参数构造new_checked在序列化结果与求值结果上完全一致。

Python 示例

from nautilus_trader.model import InstrumentId from nautilus_trader.model import Symbol from nautilus_trader.model import SyntheticInstrument synthetic = SyntheticInstrument( symbol=Symbol("BTC-LTC"), price_precision=2, components=[ InstrumentId.from_str("BTC.BINANCE"), InstrumentId.from_str("LTC.BINANCE"), ], formula="(BTC.BINANCE + LTC.BINANCE) / 2.0", ts_event=0, ts_init=0, )

在策略/Actor 中创建并订阅合成行情

创建合成工具前,请确保所有成分工具已存在于缓存(cache)中,并且同时订阅每个成分的 quote 或 trade 流以及合成工具本身的流。合成 quote 仅由成分 quote 推导,合成 trade 仅由成分 trade 推导。

当某个成分 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

动态更新公式

合成公式可以随时更新:

synthetic = self.cache.synthetic(self._synthetic_id) new_formula = "(BTCUSDT.BINANCE + ETHUSDT.BINANCE) / 2" synthetic.change_formula(new_formula) self.update_synthetic(synthetic)

Rust 端对应方法为change_formula(见 crates/model/src/instruments/synthetic.rs),它基于现有成分重新编译公式,编译失败返回错误且不会破坏原公式状态——源码测试test_change_formula_rejects_invalid_formula_without_mutation验证了失败后formula与计算结果均保持原值。构建期之外,is_valid_formula/is_valid_formula_for_components可用于在修改前预检公式是否可编译。

用合成价格触发模拟订单

合成工具的一个典型用途是作为模拟订单(emulated order)的触发源:一旦合成价格达到触发条件,就释放一笔模拟订单。以下示例中,当合成价格触发条件满足时,在ETHUSDT.BINANCE上提交一笔限价买单:

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)

trigger_instrument_id指向合成工具 ID,即可让订单价格与合成价格联动触发,这也是 docs/concepts/synthetics.md 中“从衍生价格触发模拟订单”的核心用法。

价格计算 API 与底层实现

除引擎自动求值外,SyntheticInstrument还暴露显式计算接口,便于测试与离线验证:

  • calculate(inputs: Sequence[float]) -> Price:按成分顺序传入f64输入数组;
  • calculate_from_map(inputs: dict) -> Price:以{InstrumentId文本: 价格}字典传入输入。

从源码看,calculate会先校验输入数量与成分数一致(否则返回InputCountMismatch),再逐个检查输入是否为有限数(NaN/Infinity 直接返回NonFiniteInput),随后调用编译后表达式的eval_number,最后用Price::new_checkedprice_precision量化结果。calculate_from_map对不超过 8 个成分(MAX_INLINE_COMPONENTS)的情况使用栈上定长缓冲区零分配求值,成分更多时回退到堆分配路径,再委托给calculate

源码测试覆盖了这些路径:test_calculatetest_calculate_from_map验证(BTC.BINANCE + LTC.BINANCE) / 2.0在输入100.0200.0时产出150.0test_slashed_instrument_ids_calculate_from_map验证含/的 FX ID;test_hyphenated_instrument_ids_preserve_raw_formulatest_hyphenated_instrument_ids_support_legacy_sanitized_formula分别验证含-ID 的原始公式与 legacy 清洗公式均可求值;test_components_with_colliding_legacy_aliases_coexist则确认FOO-BAR.VENUEFOO_BAR.VENUE共存时互不干扰。

错误处理与验证边界

Nautilus 在每个边界都校验合成工具:

  • 编译期:公式编译拒绝未知符号(如Unknown symbol \missing``)、类型错误与容量超限(栈深/局部变量/嵌套深度);
  • 求值期:拒绝输入数量不匹配与非有限价格(NaN、Infinity),避免其进入公式;
  • 价格结果:公式结果无法构造成合法Price(如产生inf)时报InvalidPriceResult

上述错误统一封装为SyntheticInstrumentError枚举(Validation / Expression / MissingInput / InputCountMismatch / NonFiniteInput / InvalidPriceResult),测试test_builder_rejects_unknown_formula_symboltest_calculate_rejects_wrong_input_counttest_calculate_rejects_non_finite_inputstest_calculate_rejects_invalid_price_result分别对应验证了这些路径。此外,SyntheticInstrument实现了Serialize/Deserialize,反序列化时会重新编译公式,因此反序列化同样会拒绝未知符号(见test_deserialize_rejects_unknown_formula_symbol);其PartialEq/Hash仅基于id

性能特性

公式在构造时编译一次,此后在每个成分价格 tick 上反复求值。表达式引擎采用 compile-once/eval-many 架构,配合零分配的f64栈,求值对 tick 处理路径的开销可忽略(相关说明见 docs/concepts/synthetics.md)。按官方文档在 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;带局部变量的公式约 19 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。

适配器与成分来源

SyntheticInstrument仅存在于本地,其价格从已加载到系统中的任意适配器(adapter)成分工具推导而来。它不绑定任何单一交易所适配器,也没有 venue 限制、保证金、手续费、订单簿或适配器专属元数据——这些特性使其成为跨交易所组合价差、篮子与比值的通用载体。

相关指南

  • Synthetics 合成工具——公式推导工具与合成 K 线的完整规范;
  • Instruments 工具——工具定义与各类 venue 专属工具类型;
  • Data 数据——引用工具的市场数据类型;
  • Orders 订单——订单可通过合成工具 ID 作为模拟触发源;
  • 源码实现:crates/model/src/instruments/synthetic.rs 与表达式引擎 crates/model/src/expressions/eval.rs;
  • Python API 存根:python/nautilus_trader/model/init.pyi。

【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询