用 Rust 编写 NautilusTrader 交易策略:从结构体定义到订单管理全流程实战
【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader
本指南围绕 NautilusTrader 的 Rust 原生策略开发,逐步构建一个订阅行情报价(QuoteTick)并提交市价单的最小策略,完整覆盖StrategyCore运行时接线、nautilus_strategy!宏、DataActor回调、OrderApi订单构造与Strategy订单管理门面方法。阅读本文后,你将能够独立编写、注册并运行一个 Rust 原生交易策略,并理解其与引擎、运行时、缓存和组合(Portfolio)之间的交互边界。建议先阅读 Write an Actor (Rust) 掌握 Actor 基础,因为策略本质上是"扩展了订单管理能力的 Actor"。
策略与 Actor 的关系
在 NautilusTrader 中,Strategy构建于DataActor之上并叠加订单管理能力。从源码结构看,StrategyCore内部持有一个DataActorCore(字段名为actor),并额外持有OrderManager、OrderFactory、Portfolio引用、GTD 定时器表(gtd_timers)以及市场退出(market exit)状态机字段。这意味着策略天然继承了 Actor 的全部能力:
- 历史数据请求(historical data requests)
- 实时数据订阅(live data feed subscriptions)
- 定时器与时间警报(time alerts / timers)
- 缓存访问(cache access)
- 组合访问(portfolio access)
- 订单与持仓的创建和管理(orders & positions)
概念细节可参考 Strategies 与 Rust 两个概念指南。
第一步:定义策略结构体
一个 Rust 策略需要持有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, }从StrategyCore的文档注释可知,该结构体被设计为"作为用户自定义策略结构体的成员持有",并通过nautilus_strategy!宏提供Strategy、StrategyNative和DataActor所需的 trait 访问器。它不会 deref 到DataActorCore,这是门面模式在编译期层面的保证——策略代码无法直接触及内部运行时状态。
第二步:实现构造函数
策略构造的核心是构建StrategyConfig。它接受strategy_id与order_id_tag两个关键标识:
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"), } } }order_id_tag 的语义与约束
order_id_tag会被追加到该策略产生的所有 client order ID 上,用于防止多个策略交易同一标的时 ID 冲突。关键规则:
- 不能包含连字符(
-):因为运行时需要从策略 ID 的最后一个连字符分隔段中读回该 tag。StrategyCore::new遇到非法 tag 会panic;使用StrategyCore::new_checked则会返回错误,便于在构造期优雅处理。 - tag 与 strategy_id 的合成规则:从
StrategyCore::new_checked的源码可以看出,若同时提供strategy_id与order_id_tag,运行时会生成形如ExampleStrategy-XNAS-T01的最终策略 ID;若只提供strategy_id,则直接从其最后一段提取 tag(如ExampleStrategy-XNAS→ tag 为XNAS)。 - 未配置 strategy_id 时:构造期使用占位的
Strategy-Noneactor ID,注册时才替换为派生 ID 与分配的数值 tag(从000开始递增)。相关测试见 core.rs 测试模块。
仓库测试对非法 tag 的验证非常直观(core.rs):order_id_tag: Some("A-B")会触发"order_id_tag cannot contain the '-' strategy ID separator, was 'A-B'"错误;非 ASCII 字符同样会被拒绝。
StrategyConfig 完整字段
StrategyConfig还提供以下可配置项(均可用..Default::default()覆盖):
| 字段 | 默认值 | 说明 |
|---|---|---|
strategy_id | None | 策略唯一 ID |
order_id_tag | None | 订单 ID 标签,不能含- |
use_uuid_client_order_ids | false | 是否用 UUID4 生成 client order ID |
use_hyphens_in_client_order_ids | true | 生成的 client order ID 是否带连字符 |
oms_type | None | 订单管理系统类型,影响持仓 ID 的生成方式 |
external_order_instrument_ids | None | 策略声明认领的外部订单标的集合 |
manage_contingent_orders | false | 是否自动管理 OTO/OCO/OUO 开仓条件单 |
manage_gtd_expiry | false | 是否由策略管理 GTD 到期(到期自动撤单) |
manage_stop | false | stop()前先执行市场退出(平仓+撤单) |
market_exit_interval_ms | 100 | 市场退出完成度检查间隔 |
market_exit_max_attempts | 100 | 市场退出最大检查次数(默认 100ms×100≈10s) |
market_exit_time_in_force | GTC | 平仓市价单的 TIF(GTD 会被校验拒绝) |
market_exit_reduce_only | true | 平仓市价单是否 reduce-only |
log_events/log_commands | true | 是否记录事件/命令日志 |
log_rejected_due_post_only_as_warning | true | post-only 拒绝是否按警告记录 |
StrategyConfig::validate(config.rs)会收集所有违规项:market_exit_interval_ms必须为正且不超过纳秒转换上限(u64::MAX / 1_000_000),market_exit_max_attempts必须为正,market_exit_time_in_force不允许为GTD。
第三步:接入宏并实现 Debug
nautilus_strategy!宏是 Rust 策略与原生运行时之间的接线层。调用形式如下:
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() } }从nautilus_strategy!宏定义 可以看到它实际生成的代码:
- 固有方法
config():返回传入StrategyCore::new的StrategyConfig引用。 impl DataActorNative for MyStrategy:提供core()/core_mut()访问器,委托给core字段。impl StrategyNative for MyStrategy:提供strategy_core()/strategy_core_mut()。impl Strategy for MyStrategy:生成Strategytrait 实现。
宏默认委托给名为core的字段;若字段名不同,可传第二个参数(如nautilus_strategy!(MyStrategy, strat_core))。宏不会让策略或其StrategyCorederef 到运行时内部,因此正常回调中应使用self上的门面方法而非生成的 native 访问器。
运行时注册依赖 blanket 的Actor与Component实现,它们要求 native 接线和Debug:宏提供接线,Debug需要手动实现或 derive。仓库中的EmaCross正是这种"宏 + 手写Debug"的标准模式。
第四步:实现 DataActor trait
数据处理方式与 Actor 完全一致——在on_start中订阅,在对应 handler 中响应:
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(()) } }要点说明:
subscribe_quotes是DataActortrait 上的门面方法,可通过self直接调用(参数为instrument_id、可选的book_type与depth,此处均传None)。- 每个 handler 返回
anyhow::Result<()>,与 Actor 的回调约定一致。 self.order()返回用户级订单构造 API(OrderApi),submit_order则由宏生成的Strategytrait 实现提供。order_id_tag会体现在生成的 client order ID 中。仓库测试(core.rs)验证了注册后生成的 ID 格式为O-19700101-000000-001-T01-1(前缀O-、日期、序号、tag、自增计数)。
第五步:OrderApi 订单构造方法
self.order()(即OrderApi)内部持有OrderFactory的引用,提供以下订单构造方法:
| 方法 | 说明 |
|---|---|
market | 市价单(支持 TIF、reduce_only、quote_quantity、执行算法、tags、自定义 client_order_id) |
limit | 限价单(追加价格、到期时间、post_only、display_qty、模拟触发等参数) |
stop_market | 停止-市价单(需 trigger_price 与 trigger_type) |
stop_limit | 停止-限价单(需价格 + 触发价格) |
market_to_limit | 市价转限价单 |
market_if_touched | 触价市价单 |
limit_if_touched | 触价限价单 |
trailing_stop_market | 移动停止-市价单(trailing_offset + 偏移类型 + 激活价) |
trailing_stop_limit | 移动停止-限价单(price + limit_offset + trailing_offset) |
bracket | 括号单:一次性生成入场单 + 止盈腿 + 止损腿三条关联订单(builder 风格) |
create_list | 从给定订单创建订单列表(OrderList) |
generate_client_order_id | 手动生成 client order ID |
generate_order_list_id | 手动生成 order list ID |
各构造方法的完整参数签名可参考 api.rs(如market的time_in_force、reduce_only、quote_quantity、exec_algorithm_id、exec_algorithm_params、tags、client_order_id等全部为Option)。bracket方法支持对入场单、止盈腿、止损腿分别配置订单类型、触发类型、TIF、post_only、tags 等,是构建复杂风控结构的便捷入口(api.rs)。
注意:这些方法均标注"若订单参数校验失败或 order factory 已被可变借用则 panic",因此不要在回调中长时间持有借用。
第六步:原生运行时访问边界
策略逻辑中应使用公共门面(facade):
clock():时钟访问(时间戳、定时器)cache():缓存访问(行情、订单、持仓查询)order():订单构造 APIportfolio():组合读取 API(账户余额、保证金、盈亏、净敞口等)strategy_id():策略 IDStrategytrait 上的订单管理方法
不要在普通策略代码中导入DataActorNative或StrategyNative,也不要直接调用core()、core_mut()、strategy_core()、strategy_core_mut()、order_factory()、order_factory_rc()、portfolio_rc()等 native 句柄。这些句柄暴露了借用状态的运行时对象,属于 engine、runtime、注册流程、PyO3、testkit 或明确对延迟敏感的原生代码。
从StrategyNativetrait 的源码可见,order_factory()返回RefMut<OrderFactory>、portfolio_rc()返回Rc<RefCell<Portfolio>>,这些借用类型不会跨 Python 边界。若你的策略需要同时运行在 Python 端或插件化接口上,必须只使用门面方法。完整的 native-traits 适用性矩阵与方法表见 Rust 概念指南的 Native Traits 章节、DataActorNative方法表 与StrategyNative方法表。
第七步:重写 Strategy 钩子
若要覆盖Strategytrait 的方法(如订单、持仓事件处理器),把它们放在宏的 brace 块中传入;DataActor的 handler 则保持在独立的impl DataActor块中:
nautilus_strategy!(MyStrategy, { fn on_order_rejected(&mut self, event: OrderRejected) { log::warn!("Order rejected: {}", event.reason); } });宏会自动生成内部管道(plumbing),你只需关注业务逻辑。宏同样支持"自定义字段名 + 钩子块"的组合形式,如nautilus_strategy!(MyStrategy, strat_core, { ... }),见 macros.rs。
第八步:订单管理门面方法
Strategytrait 提供以下订单管理门面方法:
| 方法 | 作用 |
|---|---|
submit_order | 向交易所提交新订单 |
submit_order_list | 提交一组条件订单(订单列表) |
modify_order | 修改价格、数量或触发价 |
modify_orders | 批量修改同一标的的多笔订单 |
cancel_order | 撤销指定订单 |
cancel_orders | 撤销筛选出的一组订单 |
cancel_all_orders | 撤销某标的所有订单 |
close_position | 用市价单平掉某持仓 |
close_all_positions | 平掉所有未平持仓 |
提交的订单会进入引擎命令路由:若指定了emulation_trigger则先进入OrderEmulator;若指定了exec_algorithm_id则先进入对应ExecutionAlgorithm;否则先进入RiskEngine(风控引擎)。撤单/改单的路由规则、批量取消的约束(同标的、不含模拟单/本地单)以及cancel_all_orders的跨策略作用域细节,均可参考 Strategies 概念指南 的订单管理章节。
注册策略到引擎
策略写好之后即可注册运行。回测引擎与实盘节点使用相同的注册方式:
// 回测 let strategy = MyStrategy::new(instrument_id); engine.add_strategy(strategy)?; // 实盘 let strategy = MyStrategy::new(instrument_id); node.add_strategy(strategy)?;注册时StrategyCore::register会完成三件关键初始化:创建OrderFactory(以 trader ID + strategy ID 为参数)、创建OrderManager、挂载Portfolio引用。在此之前,self.order()等方法会因未注册而 panic(源码中以expect("Strategy not registered: OrderFactory not initialized")明确标注),这也解释了为什么应在on_start中做系统级工作、构造函数只初始化普通状态。
仓库中的完整参考实现
原仓库提供两个开箱即用的策略示例,是学习的最佳范本:
- EmaCross:双 EMA 交叉策略,演示了指标集成——在
on_start中订阅行情、在on_quote中喂入ExponentialMovingAverage指标并依据快慢线交叉产生买卖信号,完整结构见 strategy.rs,其配套 config.rs 展示了从配置构建策略的from_config模式。 - GridMarketMaker:网格做市策略,演示可配置的网格层级与重新报价(requoting)逻辑,目录内含 README 与测试。
这两个示例覆盖了本文的全部要点:StrategyCore接线、宏生成、门面方法使用、handler 覆盖与测试验证,可直接作为模板二次开发。
延伸阅读
- Write an Actor (Rust):Actor 基础,理解订阅、请求与回调行为
- Strategies 概念指南:策略生命周期 handler、时钟与定时器、缓存/组合访问、订单命令的完整路由
- Rust 概念指南:native traits 适用性矩阵与全部 handler 方法表
- Backtesting 概念:用历史数据回测策略
- Run a Rust Backtest:回测引擎的实际运行流程
【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考