4. 订单生命周期管理:订单状态机
做市商报价引擎的核心,说白了就是跟订单打交道。你想想看,一个订单从进来开始,到最终被吃掉或者被取消,中间会经历哪些状态?这就是我们今天要聊的——订单生命周期管理。
我个人习惯把订单状态机比作一个「交通灯系统」。每个订单就像一辆车,在交易所这条路上行驶,红灯停、绿灯行、黄灯警告。搞不清楚这个状态流转,你的报价引擎迟早要出大问题。
4.1 订单的五大核心状态
在大多数交易所和做市系统中,订单状态基本可以归纳为以下五种。嗯,这里要注意,不同交易所的叫法可能略有差异,但本质是一样的。
| 状态 | 含义 | 触发条件 |
|---|---|---|
| New | 新订单,已提交但未成交 | 订单通过风控检查,进入订单簿 |
| PartiallyFilled | 部分成交 | 订单的一部分数量被吃掉了 |
| Filled | 完全成交 | 订单全部数量被吃掉 |
| Cancelled | 已取消 | 用户主动撤单或系统自动撤单 |
| Rejected | 已拒绝 | 风控不通过、参数非法、资金不足等 |
我在项目中遇到过最坑的一次,就是忽略了 PartiallyFilled 状态的处理。当时一个做市策略下了个大单,部分成交后,剩余部分被系统当成「已完成」给清理掉了。结果呢?仓位对不上,亏了不少钱。从那以后,我对状态机的敬畏心就上来了。
4.2 状态转换图
下面这张图,是我每次给团队做培训时必画的。它清晰地展示了订单从生到死的完整路径。
核心逻辑:订单一旦进入系统,只有一条「正向路径」——从 New 到 Filled。其他路径都是「异常退出」。
我的经验:实际生产环境中,PartiallyFilled 状态可能会反复出现。比如一个限价单挂在盘口,被多个小单慢慢吃掉。这时候状态机必须支持「多次部分成交」的循环,直到完全成交或被取消。
4.3 订单取消与修改逻辑
做市商跟普通交易者最大的区别是什么?就是我们经常要「反悔」。行情一变,挂着的单子得赶紧撤或者改。这里面的门道不少。
4.3.1 取消订单
取消订单看似简单,其实有坑。我曾经在对接某交易所时,发现取消请求发出去后,订单已经被成交了。结果呢?取消失败,但仓位已经变了。
正确的做法是:
- 先查状态:发送取消请求前,先确认订单当前状态
- 幂等处理:取消请求可能重复发送,系统要能处理重复取消
- 异步确认:取消请求发出后,等待交易所确认回调,不要立即认为订单已取消
// 伪代码:订单取消逻辑
bool cancelOrder(OrderId id) {
Order* order = getOrder(id);
if (order == nullptr) return false;
// 只有 New 和 PartiallyFilled 状态才能取消
if (order->status != NEW && order->status != PARTIALLY_FILLED) {
logWarning("订单 %s 当前状态 %d 不可取消", id, order->status);
return false;
}
// 发送取消请求到交易所
bool sent = exchange->sendCancel(id);
if (!sent) {
logError("取消请求发送失败: %s", id);
return false;
}
// 标记为「取消中」状态(中间态)
order->status = CANCELLING;
return true;
}
注意:千万不要在收到取消确认前就释放订单资源。我曾经见过一个系统,取消请求刚发出去就把订单从内存里删了,结果交易所返回「取消失败,订单已成交」,导致成交记录丢失。
4.3.2 修改订单
修改订单,说白了就是「先取消旧单,再下新单」。但这里有个性能问题——如果每次都先取消再下单,中间会有时间窗口,容易被别人抢跑。
我建议的做法是:
- 改价不改量:很多交易所支持直接修改价格,保留订单编号和成交优先级
- 改量不改价:减少数量通常可以直接改,增加数量需要重新排队
- 都改:建议拆成取消+新单,但要做好时间窗口保护
4.4 订单过期处理
做市商的订单通常有时效性。比如「GTC」(Good Till Cancelled)和「IOC」(Immediate or Cancel)。我个人习惯在订单结构体里加一个 expireTime 字段。
struct Order {
OrderId id;
double price;
double quantity;
double filledQuantity;
OrderStatus status;
int64_t createTime; // 创建时间戳
int64_t expireTime; // 过期时间戳(0表示永不过期)
OrderType type; // GTC, IOC, FOK 等
};
过期处理的逻辑其实不复杂:
- 每个订单创建时,根据订单类型设置
expireTime - 系统启动一个定时任务,每隔 100ms 扫描一次
- 发现
当前时间 > expireTime且订单状态为 New 或 PartiallyFilled,自动发起取消
避坑指南:我曾经在扫描过期订单时,直接遍历了整个订单簿。结果订单量一上来,扫描耗时飙升,影响了主线程的报价性能。后来改成用「最小堆」来管理过期时间,每次只检查堆顶的订单,性能提升了一个数量级。
4.5 状态机实现的几个关键点
最后,总结一下我在实现订单状态机时的一些心得:
- 状态转换必须是单向的:比如 Filled 状态不能回到 PartiallyFilled,这是铁律
- 引入中间态:比如 CANCELLING、PENDING_NEW,能有效防止并发问题
- 日志要打全:每次状态转换都要记录时间、旧状态、新状态、触发原因
- 状态机要可回放:万一系统崩溃,能从日志中恢复所有订单的最终状态
嗯,订单生命周期管理这块,说白了就是「规矩」二字。你把状态转换的规矩定好了,代码写严谨了,后面做策略开发、风控对接都会顺畅很多。我见过太多团队,一开始觉得状态机简单,随便写写,结果上线后各种灵异事件——订单丢了、重复成交了、仓位对不上了。说白了,都是状态机没搞明白。
一个小建议:刚开始做的时候,可以用一个「状态转换矩阵」来验证你的逻辑。比如行是旧状态,列是新状态,矩阵里标记哪些转换是合法的。这样一眼就能看出有没有遗漏。
公众号:蓝海资料掘金营,微信deep3321