4. 订单生命周期:从创建、路由、成交到撤销的全链路追踪

订单生命周期,说白了就是一笔订单从诞生到消亡的全过程。我做了这么多年量化系统,发现很多团队把精力都花在策略和回测上,却忽略了订单本身的管理。结果呢?实盘一跑,各种幺蛾子就出来了。

今天我们就来拆解一下,一个订单到底经历了哪些阶段。我个人习惯把订单生命周期分成四个核心阶段:创建路由成交撤销。每个阶段都有坑,咱们一个一个说。

4.1 订单创建:一切从这里开始

订单创建看起来简单,不就是拼个结构体吗?其实不然。这里涉及两个关键问题:订单ID的生成订单状态的初始化

先说说订单ID。我在项目中遇到过,直接用自增ID或者UUID,结果在高并发场景下,数据库写入成了瓶颈。后来我改用「交易所前缀 + 时间戳 + 自增序列」的方式,既保证了唯一性,又方便了后续的追踪。

订单ID设计示例:

// 订单ID结构
struct OrderID {
    string exchange;    // 交易所前缀,如 "BIN"
    int64_t timestamp;  // 毫秒级时间戳
    int32_t seq;        // 自增序列号
};

// 生成示例
OrderID oid = {"BIN", 1699000000123, 1};
// 最终ID: BIN-1699000000123-00001

订单状态初始化这块,我建议用状态机来管理。别小看这个,很多bug都是状态混乱导致的。

状态 说明 触发条件
PENDING 待提交 订单创建完成,等待发送
SUBMITTED 已提交 成功发送到交易所
PARTIAL_FILLED 部分成交 订单部分数量被匹配
FILLED 完全成交 订单全部数量被匹配
CANCELLED 已撤销 用户主动撤销或系统撤销
REJECTED 已拒绝 交易所拒绝该订单

4.2 订单路由:把订单送到对的地方

订单创建好了,接下来就是路由。说白了,就是决定把订单发到哪个交易所、哪个交易对。

你想想看,一个订单可能同时发往多个交易所,也可能只发往一个。这里有个关键点:路由策略。我见过最粗暴的做法是轮询,但效果很差。为什么?因为不同交易所的流动性、延迟、费率都不一样。

我的路由策略建议:

  • 按流动性路由:优先发往深度最好的交易所
  • 按延迟路由:对速度敏感的单子,选延迟最低的
  • 按费率路由:大单优先考虑费率低的交易所

我曾经在一个项目中,因为路由策略没做好,导致一个大单被拆成了几十个小单,结果成交价格远低于预期。嗯,那次教训挺深刻的。

4.3 订单成交:等待匹配的那一刻

订单到了交易所,剩下的就是等待成交了。这个阶段,我们需要关注的是成交回报的处理

交易所的成交回报通常有两种方式:WebSocket推送REST轮询。我个人习惯用WebSocket,延迟低,实时性好。但要注意,WebSocket可能会断线重连,这时候就需要做好消息去重。

注意:成交回报的处理一定要幂等。也就是说,同一个成交回报处理多次,结果应该是一样的。否则,一旦出现重复推送,你的账户余额就乱了。

成交回报的数据结构一般包含以下字段:

// 成交回报结构
struct TradeReport {
    string order_id;      // 订单ID
    string trade_id;      // 成交ID
    double price;         // 成交价格
    double quantity;      // 成交数量
    int64_t timestamp;    // 成交时间
    string side;          // 买卖方向
};

4.4 订单撤销:该放手时就放手

订单撤销,听起来简单,但实际操作中坑很多。比如,你发了一个撤销指令,但交易所还没来得及处理,订单就已经成交了。这时候怎么办?

我建议的做法是:先检查订单状态,再发撤销指令。但即使这样,也不能保证100%成功。因为从检查到发送,中间可能有延迟。

撤销流程的最佳实践:

  1. 查询订单当前状态
  2. 如果状态是PENDING或SUBMITTED,发送撤销指令
  3. 等待撤销确认(通常有超时机制)
  4. 如果超时未确认,再次查询订单状态
  5. 根据最终状态决定下一步操作

我曾经遇到过一个问题:撤销指令发出去后,交易所返回了成功,但订单实际上还是成交了。后来发现,是交易所的撤销接口有缓存,返回的是旧状态。从那以后,我每次撤销后都会主动查询一次订单状态,确保万无一失。

4.5 全链路追踪:把每个环节串起来

好了,订单的每个阶段我们都讲完了。但实际系统中,这些阶段是串在一起的。我们需要一个全链路追踪的机制,把每个环节的日志、状态、时间都记录下来。

我习惯用事件溯源的方式来做。每个订单的状态变更,都记录为一个事件。这样,任何时候我们都可以回溯订单的完整生命周期。

事件溯源的好处:

  • 可以回放订单的完整流程
  • 方便排查问题
  • 支持审计需求

下面是一个简单的订单生命周期流程图,我用SVG画了一下,方便你理解:

订单生命周期流程图 创建 路由 成交 撤销 PENDING SUBMITTED FILLED CANCELLED 全链路追踪层 事件1: 订单创建 事件2: 订单路由 事件3: 订单成交 事件4: 订单撤销 时间戳: 1699000000123 延迟: 12ms 成交价: 50000.00 撤销原因: 超时

嗯,这张图把整个流程串起来了。你可以看到,每个阶段都有对应的状态,而全链路追踪层则记录了每个事件的详细信息。

4.6 避坑指南

最后,分享几个我踩过的坑:

  • 订单ID重复:曾经因为时钟回拨,导致订单ID重复,结果系统把两个不同的订单当成同一个处理了。后来我加了机器ID前缀,解决了这个问题。
  • 成交回报丢失:WebSocket断线重连时,中间可能有成交回报没收到。我建议在重连后,主动查询一下未完成订单的状态。
  • 撤销超时:有些交易所的撤销接口响应很慢,甚至可能超时。我建议设置一个合理的超时时间,超时后主动查询订单状态。

重要提醒:订单生命周期管理,核心是状态机。状态机设计得好,系统就稳定。状态机设计得不好,bug就层出不穷。我建议你在设计阶段就画好状态转换图,别等到上线了再改。

好了,订单生命周期就讲到这里。记住,每个阶段都有坑,但只要你把状态机设计好,把全链路追踪做好,这些问题都能解决。


公众号:蓝海资料掘金营,微信deep3321