NautilusTrader 模拟订单(Emulated Orders)实战指南:用 OrderEmulator 在任意交易场所启用条件单
【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader
本篇指南围绕 NautilusTrader 的OrderEmulator(模拟订单引擎)展开,讲解如何借助本地行情监控,在交易场所不支持原生条件单的情况下,依然使用STOP_LIMIT、TRAILING_STOP_MARKET、LIMIT_IF_TOUCHED等高级订单类型。读完本文,你将掌握emulation_trigger三种触发模式的选型、模拟订单从提交到释放的完整生命周期、Cache 查询 API,以及重启恢复与最佳实践,并能在策略中直接落地可运行的模拟订单代码。
什么是模拟订单
模拟订单(Emulated Order)是 NautilusTrader 提供的一种本地订单机制:当你的交易场所(venue)本身不支持某种订单类型时,OrderEmulator会在本地替你"模拟"这张订单的存在与触发逻辑。
OrderEmulator持续监控由emulation_trigger选定的行情数据。当本地订单满足释放条件(release condition)时,模拟器将其转换为一张MARKET或LIMIT订单,并让这张新订单走标准的风险检查与执行通道(RiskEngine → ExecutionEngine → ExecutionClient)。例如,一张模拟的STOP_LIMIT订单在止损价格被触发后,会变成一张真实的LIMIT订单提交给场所。
核心实现位于 emulator.rs,OrderEmulator结构体内维护了:
matching_cores:按InstrumentId索引的本地撮合核心(OrderMatchingCore),用于判断触发条件;subscribed_quotes/subscribed_trades:当前已订阅的 Quote 与 Trade 行情集合;manager:负责缓存原始SubmitOrder命令并管理订单状态转换的OrderManager;pending_messages:命令/事件缓冲队列,用于避免重入问题。
提交一张模拟订单
在订单构造函数或OrderFactory方法上设置emulation_trigger即可将订单交由本地模拟器处理。本地模拟器只接受以下三种取值:
| 触发类型(TriggerType) | 使用的行情数据 |
|---|---|
DEFAULT | 报价(Quotes),本地行为等同于BID_ASK。 |
BID_ASK | 最优买一/卖一(Best bid and ask quotes)。 |
LAST_PRICE | 成交(Trades)。 |
将emulation_trigger保留为None则关闭本地模拟,订单直接走常规提交通道(RiskEngine → ExecutionEngine)。
需要特别注意的是,TriggerType枚举本身还包含其他取值。从 enums.rs 可以看到完整定义:Default、LastPrice、MarkPrice、IndexPrice、BidAsk、DoubleLast、DoubleBidAsk、LastOrBidAsk、MidPoint。其中MarkPrice、IndexPrice等描述了部分交易场所支持的触发方法,但本地OrderEmulator不会接受它们作为emulation_trigger的值——emulator.rs的handle_submit_order中明确断言仅接受TriggerType::Default | BidAsk | LastPrice,其余取值会记录错误并直接取消订单(见 emulator.rs)。
触发类型的选择直接决定模拟订单的行为方式:
- 对于止损单(stop orders),模拟器将触发价与所选行情数据进行比较;
- 对于移动止损单(trailing-stop orders),模拟器根据该行情数据持续更新移动触发价;
- 对于模拟的
LIMIT订单,模拟器将限价与所选行情比较,价格被触及时释放一张MARKET订单。
在策略中提交模拟订单的完整示例
在 Python 侧,emulation_trigger是各订单构造器的标准参数。从 limit.rs 等 Python 绑定可见,LimitOrder、StopMarketOrder、StopLimitOrder、MarketIfTouchedOrder、LimitIfTouchedOrder、TrailingStopMarketOrder、TrailingStopLimitOrder的构造函数均接受emulation_trigger与trigger_instrument_id参数。
结合 strategies.md 中的官方示例,一张基于最新成交价触发的模拟LIMIT买单可以这样提交:
from nautilus_trader.model import LimitOrder from nautilus_trader.model import OrderSide from nautilus_trader.model import TriggerType def buy(self) -> None: order: LimitOrder = self.order_factory.limit( instrument_id=self.instrument_id, order_side=OrderSide.BUY, quantity=self.instrument.make_qty(self.trade_size), price=self.instrument.make_price(5000.00), emulation_trigger=TriggerType.LAST_PRICE, ) self.submit_order(order)命令路由规则
SubmitOrder/SubmitOrderList命令的"第一站"取决于订单属性(见 strategies.md):
- 指定了
emulation_trigger→ 命令首先发送给OrderEmulator; - 指定了
exec_algorithm_id(且无emulation_trigger)→ 命令首先发送给对应的ExecutionAlgorithm; - 否则 → 命令首先发送给
RiskEngine。
另外,模拟订单与执行算法可以叠加使用:订单先进入OrderEmulator,释放后才被路由到ExecutionAlgorithm。这一行为在emulator.rs的释放逻辑中有直接体现——释放时若订单带有exec_algorithm_id,则命令被发送至算法端,否则发送至执行端(见 emulator.rs)。
技术细节
所有受支持的模拟订单类型,在所有环境上下文(回测 backtest、沙箱 sandbox、实盘 live)中,都由同一个OrderEmulator组件统一管理。这意味着你在回测中验证过的模拟订单逻辑,可以在实盘中以相同语义运行。
从架构组件看,OrderEmulator由四个核心文件组成(位于 order_emulator):
- emulator.rs:主体逻辑,负责订单持有、行情处理、触发与释放;
- config.rs:
OrderEmulatorConfig配置,目前仅含debug: bool(开启额外调试日志),其余字段取默认值; - handlers.rs:
OrderEmulatorExecuteHandler(接收交易命令)与OrderEmulatorOnEventHandler(接收订单事件); - adapter.rs:
OrderEmulatorAdapter,负责将组件挂接到消息总线。
关于数量限制,NautilusTrader 并不为模拟订单配置固定的数量上限。实际可用上限由可用内存与行情处理成本决定——每个被模拟的订单都会在本地匹配核心中驻留、并可能触发相应的行情订阅,因此大规模模拟订单集会带来可预期的内存与 CPU 开销。
模拟订单的生命周期
一张模拟订单按以下阶段推进:
Strategy通过submit_order提交订单;RiskEngine执行交易前检查(pre-trade checks),可能拒绝该订单;OrderEmulator在本地持有并监控该订单;- 匹配的行情更新将其转换为
MARKET或LIMIT订单并释放; - 释放后的订单在提交场所前,再次经过
RiskEngine检查。
在消息流层面,OrderEmulator::execute统一分派SubmitOrder、SubmitOrderList、ModifyOrder、BatchModifyOrders、CancelOrder、CancelAllOrders等交易命令(见 emulator.rs),并消费QuoteTick、TradeTick、OrderBookDeltas三类行情事件来驱动触发判定。
需要强调的两点语义:
- 模拟订单照常通过标准风险控制。策略可以修改或取消它们,
cancel-all(全部撤单)请求也会包含它们; - 模拟订单在转换时保留其 client order ID,因此后续缓存查询仍使用同一 ID,策略侧的状态关联不会断裂。
持有的模拟订单(Held)
当OrderEmulator持有一张订单时,会发生以下事情(与handle_submit_order的实现一一对应,见 emulator.rs):
- 缓存原始
SubmitOrder命令:manager.cache_submit_order_command保存命令,供释放时原样重放; - 在本地匹配核心中处理订单:为触发标的物(
trigger_instrument_id,默认等于订单自身instrument_id,支持合成标的物 synthetic instrument)获取或创建OrderMatchingCore,订单以RestingOrder形式驻留; - 订阅所需行情:若不存在匹配的订阅,
BID_ASK/DEFAULT触发类型会订阅报价(subscribe_quotes_for_instrument),LAST_PRICE触发类型会订阅成交(subscribe_trades_for_instrument),订阅通过DataCommand::Subscribe下发到数据引擎(见 emulator.rs); - 接受策略修改与市场驱动更新:在释放或取消之前,
ModifyOrder(改价、改触发价、改数量)与移动止损的价格更新都会被受理。
进入持有状态时,若订单仍处于Initialized状态,模拟器会生成OrderEmulated事件并写入缓存、发布到策略事件主题(events.order.<strategy_id>),将订单状态推进为Emulated。
释放的模拟订单(Released)
当行情满足模拟订单的触发条件时,释放过程执行以下动作(见 fill_market_order 与 fill_limit_order):
- 通过另一个
OrderInitialized事件,将订单转换为MARKET或LIMIT订单; - 将订单的
emulation_trigger置为None,使各组件不再将其视为模拟订单; - 将转换后的订单与缓存的原始
SubmitOrder命令重新送回RiskEngine; - 若风险引擎未拒绝,则由
ExecutionEngine路由到对应的ExecutionClient。
在行情驱动层面,on_quote_tick/on_trade_tick/on_order_book_deltas会将最新买一/卖一/最新成交价写入匹配核心,随后iterate_orders依次迭代买单侧与卖单侧(先处理买单侧,以保证 OCO/OUO 等跨侧条件单在两侧之间正确变更状态),产出MatchAction::FillLimit(模拟 LIMIT 触价转市价单)或MatchAction::TriggerStop(止损触发)动作并分派(见 emulator.rs)。若触发时对应侧尚无可用行情(如买单需要卖一价),订单不会被释放,而是保留在队列中等待下一次行情更新重试(validate_release逻辑,见 emulator.rs)。
释放时会生成OrderReleased事件,携带released_price(释放时的参考价),并发布给策略与风险引擎。
可被模拟的订单类型
原始模拟订单类型与释放后类型的关系如下:
| 用于模拟的订单类型 | 是否可模拟 | 释放后的类型 |
|---|---|---|
MARKET | - | N/A |
MARKET_TO_LIMIT | - | N/A |
LIMIT | ✓ | MARKET |
STOP_MARKET | ✓ | MARKET |
STOP_LIMIT | ✓ | LIMIT |
MARKET_IF_TOUCHED | ✓ | MARKET |
LIMIT_IF_TOUCHED | ✓ | LIMIT |
TRAILING_STOP_MARKET | ✓ | MARKET |
TRAILING_STOP_LIMIT | ✓ | LIMIT |
该映射在源码中有直接对应:trigger_stop_order中,StopLimit/LimitIfTouched/TrailingStopLimit走fill_limit_order(释放为 LIMIT),而StopMarket/MarketIfTouched/TrailingStopMarket走fill_market_order(释放为 MARKET,见 emulator.rs);普通LIMIT订单在fill_limit_order中被直接转市价单释放。MARKET与MARKET_TO_LIMIT本身是即时执行的订单类型,不具备"先持有、后触发"的语义,因此不可模拟。
对于移动止损单,update_trailing_stop_order会在每次行情更新后基于买一/卖一/最新价调用trailing_stop_calculate重算触发价;在activation_price被触及前,订单保持惰性持有状态(is_order_activated判定,见 emulator.rs)。
查询模拟状态
可以通过 Cache 或订单对象本身查询模拟状态。
通过 Cache 查询
Cache提供以下方法(实现位于 cache/mod.rs):
self.cache.orders_emulated(...):返回所有匹配过滤条件的模拟订单。过滤参数包括venue、instrument_id、strategy_id、account_id、side,全部可选(见 cache/mod.rs);self.cache.is_order_emulated(...):按单个 client order ID 判断该订单是否处于模拟状态(见 cache/mod.rs);self.cache.orders_emulated_count(...):返回匹配过滤条件的模拟订单数量(见 cache/mod.rs)。
更详细的签名与行为可参考 API 参考文档(cache 部分),或直接阅读上述缓存源码。
直接查询订单对象
使用order.is_emulated可直接查询订单对象。返回False意味着订单已被释放,或从未被模拟。
⚠️警告切勿在本地长期持有模拟订单的引用。当模拟订单被释放时,订单对象会发生转换(类型可能从
STOP_LIMIT变为LIMIT),你持有的旧引用将失效。请改用Cache查询。
持久化与恢复
模拟订单的跨重启恢复由OrderEmulator::on_start完成(见 emulator.rs):
- 启动时,模拟器从配置的缓存数据库(cache database)中读取恢复出来的模拟订单(
orders_emulated查询); - 仅对状态仍为
Initialized或Emulated的订单执行恢复,已脱离模拟状态的订单被跳过; - 若订单存在父订单(如括号单/条件单的附属订单),会先检查父订单是否已关闭、持仓是否已平仓,并处理
OTO(一触即发)条件单的延迟激活逻辑; - 对每个待恢复订单,基于其
OrderInitialized事件重建SubmitOrder命令,重新进入handle_submit_order流程,从而恢复行情订阅与本地匹配核心中的驻留状态。
这套机制保证模拟订单的状态(触发价、数量、关联关系)在进程重启后得以延续。
最佳实践
在实际使用模拟订单时,建议遵循以下三条原则:
- 通过
Cache查询订单,而非保存本地订单引用:释放会转换订单对象,持有旧引用会导致状态失真; - 注意订单类型在释放时发生变更:策略中的条件分支、日志与风控逻辑应基于"释放前类型 + 释放后类型"的组合进行判断;
- 同时处理初始与释放时两次风险检查的拒绝:模拟订单在持有前和释放后各经过一次
RiskEngine检查,任何一次被拒都应妥善处理(释放时被拒意味着转换后的订单不会到达场所)。
相关指南
- 订单总览:订单概念、执行指令与订单工厂(OrderFactory)。
- 高级订单:订单列表(order lists)、条件类型(contingency types)与括号订单(bracket orders)。
- 策略:在策略中进行订单管理与提交。
【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考