NautilusTrader Rust 如何编写能提交订单的 Strategy?
【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader
在 NautilusTrader 中,Strategy 是在 Actor 基础上扩展出来的组件:它除了接收行情数据,还能通过Strategytrait 的方法向交易场所提交、修改和撤销订单。本文的任务是写一个纯 Rust 的最小 Strategy——订阅QuoteTick报价,在每笔报价到达时构建一个市价单并调用submit_order提交——然后把它挂到回测引擎上跑通,确认订单链路可用。适用前提:Rust 工具链满足最低版本 MSRV1.98.1,Nautilus 各 crate 已发布到 crates.io(当前文档给出的版本线为0.63),全程不需要 Python 运行时。Rust API 处于活跃开发中,方法签名和 trait 要求可能随版本变化,以你所依赖的版本文档为准。
准备条件:添加 crate 依赖
Nautilus 的 crate 位于仓库的crates/目录,外部项目通过 crates.io 引用。在Cargo.toml中加入:
[dependencies] nautilus-backtest = "0.63" nautilus-common = "0.63" nautilus-execution = "0.63" nautilus-model = { version = "0.63", features = ["test-support"] } nautilus-trading = { version = "0.63", features = ["examples"] } anyhow = "1" log = "0.4"两个 feature flag 的作用(来自 Rust 概念指南 的 Feature flags 表):
test-support(nautilus-model):提供测试 fixture、builder、spec 和默认值,示例中的模拟合约 stub 依赖它。examples(nautilus-trading):启用示例策略EmaCross、GridMarketMaker,后面验证环节会用到。
如果做加密市场,需要在nautilus-model上额外开启high-precision(默认 9 位精度,加密场所价格小数位更多时文档要求开启)。
如果希望跟踪最新开发分支,需把所有 Nautilus 依赖指向同一个 git 源,避免 crates.io 与 git 版本之间出现类型不匹配;本文主路径使用 crates.io 版本。
定义 Strategy 结构体:持有StrategyCore
Strategy 结构体必须持有一个StrategyCore字段,用于运行期接线。常规策略逻辑不直接使用该字段,而是通过self上的 facade 方法访问运行时:
use nautilus_common::actor::DataActor; use nautilus_model::{ data::QuoteTick, enums::OrderSide, identifiers::{InstrumentId, StrategyId}, types::Quantity, }; use nautilus_trading::{nautilus_strategy, strategy::{Strategy, StrategyConfig, StrategyCore}}; pub struct MyStrategy { core: StrategyCore, instrument_id: InstrumentId, trade_size: Quantity, }构造函数中,StrategyConfig接收两个关键字段:strategy_id和order_id_tag。tag 会追加到本策略发出的所有 client order ID 上,用于在多个策略交易同一标的时避免 ID 冲突。文档对 tag 有一个硬性限制:tag 不能包含连字符,因为运行时会从 strategy ID 最后一段以连字符分隔的部分把它读回来。StrategyCore::new遇到非法 tag 会 panic;希望以错误方式处理时改用StrategyCore::new_checked。
impl MyStrategy { pub fn new(instrument_id: InstrumentId) -> Self { let config = StrategyConfig { strategy_id: Some(StrategyId::from("MY_STRAT-001")), order_id_tag: Some("001".to_string()), ..Default::default() }; Self { core: StrategyCore::new(config), instrument_id, trade_size: Quantity::from("1.0"), } } }用nautilus_strategy!宏接线并实现Debug
nautilus_strategy!宏生成注册和Strategytrait 实现所需的 native runtime 接线,默认委托给名为core的字段,字段名不同时传第二个参数。宏还额外提供config()方法,返回传给StrategyCore::new的StrategyConfig。
运行期注册依赖 blanket 的Actor和Component实现,它们要求 native 接线(宏负责)和Debug(自己实现或 derive):
nautilus_strategy!(MyStrategy); impl std::fmt::Debug for MyStrategy { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("MyStrategy").finish() } }注意宏不会让 Strategy 或其StrategyCore自动 deref 到运行时内部,core字段不要直接当 API 用。
实现DataActor并调用submit_order提交订单
数据部分与 Actor 完全一致:在on_start里订阅,在 handler 里响应。提交订单的关键就两步——self.order()构建订单,self.submit_order发出:
impl DataActor for MyStrategy { fn on_start(&mut self) -> anyhow::Result<()> { self.subscribe_quotes(self.instrument_id, None, None); Ok(()) } fn on_quote(&mut self, quote: &QuoteTick) -> anyhow::Result<()> { let order = self.order().market( self.instrument_id, OrderSide::Buy, self.trade_size, None, None, None, None, None, None, None, ); self.submit_order(order, None, None, None)?; Ok(()) } }self.order()返回的OrderApi可构建的订单类型包括:market、limit、stop_market、stop_limit、market_to_limit、market_if_touched、limit_if_touched、trailing_stop_market、trailing_stop_limit、bracket、create_list,另有generate_client_order_id与generate_order_list_id。submit_order通过宏生成的Strategytrait 实现挂在self上。
仓库内的完整示例 EmaCross 展示了带注释的市价单提交写法:enter()方法里self.order().market(...)的参数依次为time_in_force、reduce_only、quote_quantity、exec_algorithm_id、exec_algorithm_params、tags、client_order_id,随后调用self.submit_order(order, None, None, None)。nautilus_strategy!(EmaCross)与Debug实现的位置、形态与上文一致,可直接对照。
覆盖订单事件钩子,观察提交结果
Strategytrait 的订单事件 handler(如on_order_rejected、on_order_filled、on_order_canceled)通过宏的第二个参数块传入。宏自动生成内部管道,DataActorhandler 保持在单独的impl DataActor块中:
nautilus_strategy!(MyStrategy, { fn on_order_rejected(&mut self, event: OrderRejected) { log::warn!("Order rejected: {}", event.reason); } });这是文档给出的直接观察订单结果的手段:订单被拒时会输出拒绝原因日志。Strategytrait 提供的完整订单管理 facade 如下:
| 方法 | 作用 |
|---|---|
submit_order | 向场所提交新订单 |
submit_order_list | 提交一组条件订单 |
modify_order | 修改价格、数量或触发价 |
modify_orders | 批量修改同一标的的多个订单 |
cancel_order | 撤销指定订单 |
cancel_orders | 按过滤条件撤销一组订单 |
cancel_all_orders | 撤销某标的的全部订单 |
close_position | 用市价单平掉一个仓位 |
close_all_positions | 平掉全部未平仓位 |
挂到回测引擎上运行验证
策略写好后,用低层级BacktestEngine注册并运行即可验证整条订单链路。流程:创建引擎 →add_venue加模拟场所 →add_instrument加合约并add_data加载数据 →add_strategy注册策略 →run:
use nautilus_backtest::{config::{BacktestEngineConfig, SimulatedVenueConfig}, engine::BacktestEngine}; use nautilus_model::{ enums::{AccountType, BookType, OmsType}, identifiers::Venue, instruments::{Instrument, InstrumentAny, stubs::audusd_sim}, types::Money, }; let mut engine = BacktestEngine::new(BacktestEngineConfig::default())?; engine.add_venue( SimulatedVenueConfig::builder() .venue(Venue::from("SIM")) .oms_type(OmsType::Hedging) .account_type(AccountType::Margin) .book_type(BookType::L1_MBP) .starting_balances(vec![Money::from("1_000_000 USD")]) .build()?, )?; let instrument = InstrumentAny::CurrencyPair(audusd_sim()); let instrument_id = instrument.id(); engine.add_instrument(&instrument)?; engine.add_data(quotes, None, true, true)?; // quotes 为你的行情数据加载结果 engine.add_strategy(MyStrategy::new(instrument_id))?; engine.run(None, None, None, false)?;SimulatedVenueConfig使用 builder 模式,只有必填字段需要设置,其余回退到文档化默认值;build()会校验配置并返回结果,需要传播或 unwrap。运行不报错即说明策略注册、数据分发和订单提交链路走通;订单层面的反馈(如拒单原因)通过上文的on_order_rejected日志观察。
不想自己写 main 时,可以跑仓库现成的示例验证环境与订单链路(EmaCross本身就是一个会提交市价单的 Strategy):
cargo run -p nautilus-backtest --features examples --example engine-ema-cross对应源码在 engine_ema_cross.rs:它用合成 AUD/USD 报价在SIM场所上回测EmaCross,engine.run(None, None, None, false)?后以Ok(())正常退出。高层BacktestNode路径(从 Parquet catalog 流式读数据,需nautilus-backtest的streamingfeature)运行命令为:
cargo run -p nautilus-backtest --features examples,streaming --example node-ema-cross源码见 node_ema_cross.rs。两条路径的详细步骤见 Run a Backtest (Rust)。
限制与下一步
- 常规策略代码不要导入
DataActorNative、StrategyNative,也不要直接调用core()、strategy_core_mut()、order_factory()这类 native handle——它们暴露的是借用态运行时状态,只在引擎、注册、PyO3、testkit 或明确追求低延迟的 native 代码中使用(适用矩阵见 Rust 概念指南 的 Native traits 一节)。 - Rust 编写的 Strategy 除了回测,还可以挂到
LiveNode对接真实场所,LiveNode路径下用node.add_strategy(strategy)?; node.run().await?;注册运行,各 adapter 提供自己的工厂与配置类型,完整流程见 Run Live Trading (Rust)。从 Python 侧注册内置示例策略的add_builtin_strategy路径仅用于 bundled 示例,自定义 native 策略应走纯 Rust 路径。 - 需要更复杂行为(多档挂单、补单、仓位对冲)时,可参考仓库内置的 GridMarketMaker 与 EmaCross 两个完整示例,以及 Strategies 概念指南。
【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考