news 2026/9/12 5:06:49

NautilusTrader 订单提交事件 OrderSubmitted 完全指南:状态机、字段解析与策略处理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NautilusTrader 订单提交事件 OrderSubmitted 完全指南:状态机、字段解析与策略处理

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)这一动作。它的完整处理链路是:

  1. ExecutionEngine(执行引擎)将事件应用到订单对象上;
  2. 更新Cache(缓存)中的订单状态;
  3. 通过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
OrderDeniedInitialized -> Deniedon_order_denied
OrderEmulatedInitialized -> Emulatedon_order_emulated
OrderReleasedEmulated -> Releasedon_order_released
OrderSubmittedInitialized/Released -> Submittedon_order_submitted
OrderAcceptedSubmitted -> Acceptedon_order_accepted
OrderRejectedSubmitted -> Rejectedon_order_rejected
.........

从这张表可以看出两条进入Submitted的路径,对应两种交易模式:

  • 直接提交:订单在本地初始化(INITIALIZED)后直接提交至交易所;
  • 模拟(emulation)后提交:订单先进入OrderEmulator组件被模拟(EMULATED),释放(RELEASED)后再提交至交易所。

Submitted之后的去向则由交易所决定:可能是OrderAccepted(受理成功)、OrderRejected(被拒),也可能因网络或场所问题一直停留在该状态等待超时处理。

引擎中事件的实际生成位置

在回测/撮合场景中,OrderSubmittedMatchingEngine(撮合引擎)生成。源码中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_eventts_init在生成时均取当前时钟的纳秒时间戳(ts_now);
  • event_id使用UUID4::new()实时生成;
  • 事件经dispatch_order_event进入执行管道,随后被应用到订单、写入Cache并发布到MessageBus

在撮合引擎处理新订单提交流程中,generate_order_submittedgenerate_order_accepted连续被调用(matching_engine/mod.rs#L2751-L2752),即撮合场景下提交后立刻受理;而在实盘环境中,两个事件之间的间隔取决于交易所网络往返时延。另外注意 order_manager/manager.rs 的说明:对于订单管理器(order manager)而言,OrderSubmitted这类事件是 no-op(空操作),真正驱动订单状态机的是OrderManager层的其他处理逻辑。

字段解析:在公共字段之外,OrderSubmitted 还携带什么

OrderSubmitted继承所有 Python 订单事件共有的字段(详见 Events 索引文档):

字段说明
trader_idTrader 实例标识符
strategy_id与该订单关联的策略标识符
instrument_id该订单的合约/标的标识符
client_order_id客户端分配的订单标识符
event_id事件唯一标识符
ts_event事件发生时的 UNIX 纳秒时间戳
ts_init事件初始化时的 UNIX 纳秒时间戳
causation_id导致该事件的源事件或报告(如已知)

在此之上,OrderSubmitted类型专属字段只有一个

字段Python 类型是否必需/默认值说明
account_idAccountId必需与该订单关联的账户

account_id用于将订单归属到具体交易账户,从而支持多账户场景下账户维度的对账、风险与持仓核算。这一点在 Rust 结构体定义中得到印证(crates/model/src/events/order/submitted.rs#L49-L69):OrderSubmitted除公共字段外仅含account_id: AccountId一个业务字段。

与其他订单事件的字段对比

一个值得注意的对比是:OrderSubmitted没有venue_order_id(交易所订单号)。原因是显而易见的——事件发生时订单刚被送出、交易所尚未回执,自然还不存在交易所分配的订单标识符。这一点在 Events 索引文档 中有明确说明:venue_order_idaccount_idreconciliation等字段"只出现在暴露它们的 Python 事件类上"。例如OrderAccepted会新增venue_order_idOrderFilled会新增last_qtylast_pxtrade_idcommission

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 索引文档 的分发规则,当订单事件到达策略时,系统按固定顺序调用处理器:

  1. 具体处理器(如on_order_submitted)先执行;
  2. 聚合处理器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才表示受理成功。

从源码结构看,OrderSubmittedOrderEventtrait 中大量字段访问器(如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 编码同样覆盖该事件类型。

DebugDisplay输出格式也值得注意(submitted.rs#L117-L129):Display紧凑格式为OrderSubmitted(instrument_id=..., client_order_id=..., account_id=..., ts_event=...),与日志中常见的OrderAcceptedOrderFilled等事件的格式风格一致,便于统一解析。

与相邻事件的关系

理解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之后,才是AcceptedRejected

【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 5:05:23

ARM Cortex-M边缘语音唤醒系统源码深度解析

1. 项目概述:这不是一次简单的代码阅读,而是一次嵌入式AI工程的“X光扫描” 我第一次打开 ML-KWS-for-MCU 这个仓库时,没急着编译,也没急着跑demo,而是先关掉IDE,打开终端,敲下 find . -name…

作者头像 李华
网站建设 2026/9/12 5:02:57

Gitee研发一体化实战:从代码托管到CI/CD的项目管理选型指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 5:01:17

自然语言生成可编辑CAD模型:技术路线与实操指南

做机械设计、3D打印或者结构件的朋友,大概率都有过这种经历:脑子里明明已经把零件的形状想得很清楚了,结果打开CAD软件,先建草图、给约束、拉伸出实体、再切孔倒角,一套流程走下来少说十几分钟,要是遇到复杂…

作者头像 李华
网站建设 2026/9/12 5:01:06

ESP32+MAX30102心率检测实战:I²C硬件调试与MicroPython嵌入式算法

1. 为什么“听心跳”不是玄学,而是可复现的IC信号工程你拆开一块MAX30102模块,看到那几根细小的金手指和背面密密麻麻的焊点时,第一反应可能是:“这玩意儿真能测心跳?它又没耳朵。”——我第一次上电时也这么想。但很快…

作者头像 李华
网站建设 2026/9/12 4:58:19

SSM框架助农电商平台开发与优化实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华