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%成功。因为从检查到发送,中间可能有延迟。
撤销流程的最佳实践:
- 查询订单当前状态
- 如果状态是PENDING或SUBMITTED,发送撤销指令
- 等待撤销确认(通常有超时机制)
- 如果超时未确认,再次查询订单状态
- 根据最终状态决定下一步操作
我曾经遇到过一个问题:撤销指令发出去后,交易所返回了成功,但订单实际上还是成交了。后来发现,是交易所的撤销接口有缓存,返回的是旧状态。从那以后,我每次撤销后都会主动查询一次订单状态,确保万无一失。
4.5 全链路追踪:把每个环节串起来
好了,订单的每个阶段我们都讲完了。但实际系统中,这些阶段是串在一起的。我们需要一个全链路追踪的机制,把每个环节的日志、状态、时间都记录下来。
我习惯用事件溯源的方式来做。每个订单的状态变更,都记录为一个事件。这样,任何时候我们都可以回溯订单的完整生命周期。
事件溯源的好处:
- 可以回放订单的完整流程
- 方便排查问题
- 支持审计需求
下面是一个简单的订单生命周期流程图,我用SVG画了一下,方便你理解:
嗯,这张图把整个流程串起来了。你可以看到,每个阶段都有对应的状态,而全链路追踪层则记录了每个事件的详细信息。
4.6 避坑指南
最后,分享几个我踩过的坑:
- 订单ID重复:曾经因为时钟回拨,导致订单ID重复,结果系统把两个不同的订单当成同一个处理了。后来我加了机器ID前缀,解决了这个问题。
- 成交回报丢失:WebSocket断线重连时,中间可能有成交回报没收到。我建议在重连后,主动查询一下未完成订单的状态。
- 撤销超时:有些交易所的撤销接口响应很慢,甚至可能超时。我建议设置一个合理的超时时间,超时后主动查询订单状态。
重要提醒:订单生命周期管理,核心是状态机。状态机设计得好,系统就稳定。状态机设计得不好,bug就层出不穷。我建议你在设计阶段就画好状态转换图,别等到上线了再改。
好了,订单生命周期就讲到这里。记住,每个阶段都有坑,但只要你把状态机设计好,把全链路追踪做好,这些问题都能解决。
公众号:蓝海资料掘金营,微信deep3321