NautilusTrader 订单提交事件 OrderSubmitted 完全指南:状态机、字段解析与策略处理
【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader
OrderSubmitted是 NautilusTrader 事件驱动执行管道中连接本地订单状态与交易所反馈的关键节点——它标志着系统已将订单正式发送至交易场所,正处于等待交易所确认(acknowledgement)的"在途"(in-flight)状态。本文围绕该事件,深入讲解其触发时机、状态迁移、字段语义、策略侧处理方法,并结合仓库 Rust 源码(事件模型定义、撮合引擎生成逻辑)与 Python 类型桩(trading 类型桩)验证其底层实现,帮助读者在回测与实盘场景中正确使用on_order_submitted处理器。
事件语义:什么叫做"订单已提交"
根据官方事件文档,OrderSubmitted表示系统已将订单提交给交易场所(trading venue)这一动作。它的完整处理链路是:
ExecutionEngine(执行引擎)将事件应用到订单对象上;- 更新
Cache(缓存)中的订单状态; - 通过
MessageBus(消息总线)将事件发布给订阅方(包括策略)。
该事件触发的时机是:系统向交易场所发出订单、并等待交易所回执确认的这段时间。换言之,此刻订单已离开本地"准备区",进入交易所的受理队列,但尚未得到交易所"已受理且有效"的答复(那是OrderAccepted的职责)。
Rust 源码中该事件模型的文档注释与此完全一致(crates/model/src/events/order/submitted.rs#L36-L38):
Represents an event where an order has been submitted by the system to the trading venue.
从状态机角度,OrderStatus::Submitted在 enums.rs 中被定义为:
The order was submitted by the Nautilus system to the external service or trading venue (awaiting acknowledgement).
并且有一处极易被忽略但非常重要的语义细节:Submitted属于"在途"状态,而非"生效"(working)状态。源码在is_open()的注释中特别提醒(crates/model/src/enums.rs#L1338-L1343):
is_open()用于判断订单在交易所是否处于生效挂单状态,它显式排除了Submitted;- 当对账(reconciliation)时,交易所把某个挂单报告映射为
Submitted,如果只用is_open()过滤会静默丢失该订单,必须使用is_open() || is_inflight()的组合判断。
这是OrderSubmitted区别于OrderAccepted的核心:前者是单向"已发出、未确认",后者才是交易所的正式受理确认。
状态迁移:INITIALIZED / RELEASED -> SUBMITTED
文档给出的典型迁移路径为:
INITIALIZED / RELEASED -> SUBMITTED结合 Events 索引文档 中的完整订单事件状态流表格可以看到,OrderSubmitted在整个订单生命周期中处于第一道"对外关口":
| 事件 | 主要状态迁移 | 处理器 |
|---|---|---|
OrderInitialized | 创建/物化订单 | on_order_initialized |
OrderDenied | Initialized -> Denied | on_order_denied |
OrderEmulated | Initialized -> Emulated | on_order_emulated |
OrderReleased | Emulated -> Released | on_order_released |
OrderSubmitted | Initialized/Released -> Submitted | on_order_submitted |
OrderAccepted | Submitted -> Accepted | on_order_accepted |
OrderRejected | Submitted -> Rejected | on_order_rejected |
| ... | ... | ... |
从这张表可以看出两条进入Submitted的路径,对应两种交易模式:
- 直接提交:订单在本地初始化(
INITIALIZED)后直接提交至交易所; - 模拟(emulation)后提交:订单先进入
OrderEmulator组件被模拟(EMULATED),释放(RELEASED)后再提交至交易所。
而Submitted之后的去向则由交易所决定:可能是OrderAccepted(受理成功)、OrderRejected(被拒),也可能因网络或场所问题一直停留在该状态等待超时处理。
引擎中事件的实际生成位置
在回测/撮合场景中,OrderSubmitted由MatchingEngine(撮合引擎)生成。源码中generate_order_submitted方法(crates/execution/src/matching_engine/mod.rs#L6381-L6394)清晰地展示了事件的构造过程:
fn generate_order_submitted(&self, order: &OrderAny, account_id: AccountId) { let ts_now = self.clock.borrow().timestamp_ns(); let event = OrderEventAny::Submitted(OrderSubmitted::new( order.trader_id(), order.strategy_id(), order.instrument_id(), order.client_order_id(), account_id, UUID4::new(), ts_now, ts_now, )); self.dispatch_order_event(event); }可以看到:
ts_event与ts_init在生成时均取当前时钟的纳秒时间戳(ts_now);event_id使用UUID4::new()实时生成;- 事件经
dispatch_order_event进入执行管道,随后被应用到订单、写入Cache并发布到MessageBus。
在撮合引擎处理新订单提交流程中,generate_order_submitted与generate_order_accepted连续被调用(matching_engine/mod.rs#L2751-L2752),即撮合场景下提交后立刻受理;而在实盘环境中,两个事件之间的间隔取决于交易所网络往返时延。另外注意 order_manager/manager.rs 的说明:对于订单管理器(order manager)而言,OrderSubmitted这类事件是 no-op(空操作),真正驱动订单状态机的是OrderManager层的其他处理逻辑。
字段解析:在公共字段之外,OrderSubmitted 还携带什么
OrderSubmitted继承所有 Python 订单事件共有的字段(详见 Events 索引文档):
| 字段 | 说明 |
|---|---|
trader_id | Trader 实例标识符 |
strategy_id | 与该订单关联的策略标识符 |
instrument_id | 该订单的合约/标的标识符 |
client_order_id | 客户端分配的订单标识符 |
event_id | 事件唯一标识符 |
ts_event | 事件发生时的 UNIX 纳秒时间戳 |
ts_init | 事件初始化时的 UNIX 纳秒时间戳 |
causation_id | 导致该事件的源事件或报告(如已知) |
在此之上,OrderSubmitted类型专属字段只有一个:
| 字段 | Python 类型 | 是否必需/默认值 | 说明 |
|---|---|---|---|
account_id | AccountId | 必需 | 与该订单关联的账户 |
account_id用于将订单归属到具体交易账户,从而支持多账户场景下账户维度的对账、风险与持仓核算。这一点在 Rust 结构体定义中得到印证(crates/model/src/events/order/submitted.rs#L49-L69):OrderSubmitted除公共字段外仅含account_id: AccountId一个业务字段。
与其他订单事件的字段对比
一个值得注意的对比是:OrderSubmitted没有venue_order_id(交易所订单号)。原因是显而易见的——事件发生时订单刚被送出、交易所尚未回执,自然还不存在交易所分配的订单标识符。这一点在 Events 索引文档 中有明确说明:venue_order_id、account_id、reconciliation等字段"只出现在暴露它们的 Python 事件类上"。例如OrderAccepted会新增venue_order_id,OrderFilled会新增last_qty、last_px、trade_id与commission。
Rust 侧的OrderEventtrait 实现也印证了这一设计(submitted.rs#L280-L286):venue_order_id()返回None,而account_id()返回Some(self.account_id)。
策略处理:on_order_submitted 处理器
在策略中读取该事件的推荐写法(来自官方文档示例):
def on_order_submitted(self, event: OrderSubmitted) -> None: self.log.info(f"Order {event.client_order_id} submitted ({event.account_id})")在策略回调签名层面,Python 类型桩文件(python/nautilus_trader/trading/init.pyi#L447)给出了on_order_submitted的准确签名:
def on_order_submitted(self, event: model.OrderSubmitted) -> None: ...处理器分发顺序
根据 Events 索引文档 的分发规则,当订单事件到达策略时,系统按固定顺序调用处理器:
- 具体处理器(如
on_order_submitted)先执行; - 聚合处理器
on_order_event(接收所有订单事件)后执行。
这意味着你既可以在单个事件粒度上处理,也可以在所有订单事件的聚合层面统一处理,两者可以并存。例如,若要在策略中对所有订单事件做统一日志或监控:
def on_order_event(self, event: OrderEvent) -> None: # 所有订单事件都会经过这里,包括 OrderSubmitted self.log.info(f"Order event: {event}")处理器中能做什么、不能做什么
基于事件语义可以给出如下实践建议:
- 适合做:记录订单提交日志与时间戳(
ts_event);基于account_id做账户维度分流;启动提交超时监控(若一段时间内未收到OrderAccepted/OrderRejected,可视为提交异常); - 不适合做:依赖
venue_order_id(此时尚不存在);将Submitted当作"已在交易所生效"来触发后续逻辑——状态机要求等到OrderAccepted才表示受理成功。
从源码结构看,OrderSubmitted在OrderEventtrait 中大量字段访问器(如order_type()、price()、quantity()、venue_order_id()等)均返回None(submitted.rs#L140-L294),说明该事件是"轻量"的纯状态通告事件,不携带价格、数量、成交信息等业务载荷,字段语义高度聚焦于"订单已送出 + 归属账户"。
序列化与持久化
OrderSubmitted实现了完整的 Rust 序列化支持:结构体上标注了Serialize/Deserialize派生(submitted.rs#L39-L40),并带有#[serde(tag = "type")]标签,使序列化 JSON 中包含"type": "OrderSubmitted"字段,便于多态反序列化。仓库中的单元测试验证了 JSON 序列化-反序列化往返一致性(submitted.rs#L338-L346):
let json = serde_json::to_string(&original).unwrap(); let deserialized: OrderSubmitted = serde_json::from_str(&json).unwrap(); assert_eq!(original, deserialized);这意味着该事件可安全地落入事件溯源(event sourcing)存储或消息队列,用于回放重建订单状态。此外,序列化 schema 定义位于 crates/serialization/schemas/capnp/events/order.capnp,Cap'n Proto 编码同样覆盖该事件类型。
Debug与Display输出格式也值得注意(submitted.rs#L117-L129):Display紧凑格式为OrderSubmitted(instrument_id=..., client_order_id=..., account_id=..., ts_event=...),与日志中常见的OrderAccepted、OrderFilled等事件的格式风格一致,便于统一解析。
与相邻事件的关系
理解OrderSubmitted最好的方式是在事件序列中定位它。一次成功的直接下单流程,其事件链大致为:
OrderInitialized -> OrderSubmitted -> OrderAccepted -> OrderFilled(可能伴随 PartiallyFilled)OrderInitialized:订单在 Nautilus 系统内被实例化(本地状态);OrderSubmitted:订单被送出至交易所(本文主题,在途等待确认);OrderAccepted:交易所确认收到且有效(进入 working 状态,开始具备venue_order_id);OrderRejected:交易所拒绝(与Accepted二选一);OrderFilled:成交(后续事件)。
若涉及模拟交易,则链为OrderInitialized -> OrderEmulated -> OrderReleased -> OrderSubmitted -> ...。
值得再次强调状态语义差异(详见 enums.rs 中 is_open 注释):Submitted处于 in-flight 状态、不视为 working,因此在任何"过滤工作订单"的逻辑中(如对账、风控扫描、撤单检查)务必使用is_open() || is_inflight()组合,而不是单独调用is_open()。
相关指南
- Events:事件分类、分发机制与公共订单事件字段;
- Orders:订单类型与完整状态机(含部分成交、外部单、触发单等附加迁移);
- Positions:持仓生命周期与 PnL 核算;
- Execution:执行流程与风险检查;
- Strategies:策略中各处理器的实现方式;
- Architecture:数据流与执行流模式。
小结
OrderSubmitted是 NautilusTrader 订单状态机中连接本地系统与外部交易场所的"第一道关口",语义上代表"已送出、待确认"。它只携带account_id一个专属字段,配合公共事件字段即可完成账户归属与状态通告;策略侧通过on_order_submitted(或聚合的on_order_event)处理该事件,适合做提交日志、超时监控等轻量逻辑,而不应把它误当作交易所受理确认。无论是回测中的撮合引擎生成(matching_engine/mod.rs#L6381)还是实盘中的执行客户端回执,这一事件都遵循相同的语义:提交 ≠ 受理,Submitted之后,才是Accepted或Rejected。
【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考