NautilusTrader 模拟订单(Emulated Orders)实战指南:用 OrderEmulator 在任意交易场所启用条件单
2026/9/12 16:54:25 网站建设 项目流程

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_LIMITTRAILING_STOP_MARKETLIMIT_IF_TOUCHED等高级订单类型。读完本文,你将掌握emulation_trigger三种触发模式的选型、模拟订单从提交到释放的完整生命周期、Cache 查询 API,以及重启恢复与最佳实践,并能在策略中直接落地可运行的模拟订单代码。

什么是模拟订单

模拟订单(Emulated Order)是 NautilusTrader 提供的一种本地订单机制:当你的交易场所(venue)本身不支持某种订单类型时,OrderEmulator会在本地替你"模拟"这张订单的存在与触发逻辑。

OrderEmulator持续监控由emulation_trigger选定的行情数据。当本地订单满足释放条件(release condition)时,模拟器将其转换为一张MARKETLIMIT订单,并让这张新订单走标准的风险检查与执行通道(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 可以看到完整定义:DefaultLastPriceMarkPriceIndexPriceBidAskDoubleLastDoubleBidAskLastOrBidAskMidPoint。其中MarkPriceIndexPrice等描述了部分交易场所支持的触发方法,但本地OrderEmulator不会接受它们作为emulation_trigger的值——emulator.rshandle_submit_order中明确断言仅接受TriggerType::Default | BidAsk | LastPrice,其余取值会记录错误并直接取消订单(见 emulator.rs)。

触发类型的选择直接决定模拟订单的行为方式:

  • 对于止损单(stop orders),模拟器将触发价与所选行情数据进行比较;
  • 对于移动止损单(trailing-stop orders),模拟器根据该行情数据持续更新移动触发价;
  • 对于模拟的LIMIT订单,模拟器将限价与所选行情比较,价格被触及时释放一张MARKET订单。

在策略中提交模拟订单的完整示例

在 Python 侧,emulation_trigger是各订单构造器的标准参数。从 limit.rs 等 Python 绑定可见,LimitOrderStopMarketOrderStopLimitOrderMarketIfTouchedOrderLimitIfTouchedOrderTrailingStopMarketOrderTrailingStopLimitOrder的构造函数均接受emulation_triggertrigger_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 开销。

模拟订单的生命周期

一张模拟订单按以下阶段推进:

  1. Strategy通过submit_order提交订单;
  2. RiskEngine执行交易前检查(pre-trade checks),可能拒绝该订单;
  3. OrderEmulator在本地持有并监控该订单;
  4. 匹配的行情更新将其转换为MARKETLIMIT订单并释放;
  5. 释放后的订单在提交场所前,再次经过RiskEngine检查。

在消息流层面,OrderEmulator::execute统一分派SubmitOrderSubmitOrderListModifyOrderBatchModifyOrdersCancelOrderCancelAllOrders等交易命令(见 emulator.rs),并消费QuoteTickTradeTickOrderBookDeltas三类行情事件来驱动触发判定。

需要强调的两点语义:

  • 模拟订单照常通过标准风险控制。策略可以修改或取消它们,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事件,将订单转换为MARKETLIMIT订单;
  • 将订单的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
LIMITMARKET
STOP_MARKETMARKET
STOP_LIMITLIMIT
MARKET_IF_TOUCHEDMARKET
LIMIT_IF_TOUCHEDLIMIT
TRAILING_STOP_MARKETMARKET
TRAILING_STOP_LIMITLIMIT

该映射在源码中有直接对应:trigger_stop_order中,StopLimit/LimitIfTouched/TrailingStopLimitfill_limit_order(释放为 LIMIT),而StopMarket/MarketIfTouched/TrailingStopMarketfill_market_order(释放为 MARKET,见 emulator.rs);普通LIMIT订单在fill_limit_order中被直接转市价单释放。MARKETMARKET_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(...):返回所有匹配过滤条件的模拟订单。过滤参数包括venueinstrument_idstrategy_idaccount_idside,全部可选(见 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查询);
  • 仅对状态仍为InitializedEmulated的订单执行恢复,已脱离模拟状态的订单被跳过;
  • 若订单存在父订单(如括号单/条件单的附属订单),会先检查父订单是否已关闭、持仓是否已平仓,并处理OTO(一触即发)条件单的延迟激活逻辑;
  • 对每个待恢复订单,基于其OrderInitialized事件重建SubmitOrder命令,重新进入handle_submit_order流程,从而恢复行情订阅与本地匹配核心中的驻留状态。

这套机制保证模拟订单的状态(触发价、数量、关联关系)在进程重启后得以延续。

最佳实践

在实际使用模拟订单时,建议遵循以下三条原则:

  1. 通过Cache查询订单,而非保存本地订单引用:释放会转换订单对象,持有旧引用会导致状态失真;
  2. 注意订单类型在释放时发生变更:策略中的条件分支、日志与风控逻辑应基于"释放前类型 + 释放后类型"的组合进行判断;
  3. 同时处理初始与释放时两次风险检查的拒绝:模拟订单在持有前和释放后各经过一次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),仅供参考

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

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

立即咨询