4. 订单管理系统(OMS):订单生命周期管理、订单状态机设计、订单路由策略、订单簿数据结构设计
订单管理系统,简称OMS,是整个暗池交易系统的核心中枢。说白了,它就是负责处理订单从生到死的全过程。我参与过好几个暗池项目,每次重构OMS都是最头疼的环节。为什么?因为订单的状态流转一旦出错,轻则丢单,重则引发交易纠纷。今天我们就来拆解OMS的四个核心模块。
4.1 订单生命周期管理
一个订单在暗池里会经历哪些阶段?我习惯把它分成三个阶段:接收前、处理中、完成/取消。每个阶段都有严格的约束。
核心原则:订单一旦进入系统,状态变更必须通过状态机控制,不允许直接修改状态字段。
订单的生命周期大致如下:
- 新建(New):客户端提交订单,系统校验通过后进入此状态。
- 待成交(Pending):订单已进入订单簿,等待匹配。这是暗池中最常见的状态。
- 部分成交(Partially Filled):匹配引擎撮合了一部分数量,剩余部分继续等待。
- 完全成交(Filled):订单全部成交,生命周期结束。
- 已取消(Cancelled):用户主动撤销,或系统因超时等原因强制撤销。
- 已拒绝(Rejected):校验失败,比如价格超出限制、数量不合法等。
嗯,这里要注意:暗池里订单的生命周期比交易所要简单一些。因为暗池没有公开的深度行情,所以订单不会出现「部分成交后修改价格」这种复杂操作。我见过有些团队把订单状态设计得过于复杂,结果维护成本极高。
4.2 订单状态机设计
状态机是OMS的骨架。我个人习惯用有限状态机(FSM)来建模。为什么?因为状态和事件是有限的,而且每个状态转移都有明确的触发条件。
下面是我常用的订单状态机设计:
| 当前状态 | 触发事件 | 下一状态 | 备注 |
|---|---|---|---|
| New | 校验通过 | Pending | 进入订单簿 |
| New | 校验失败 | Rejected | 返回错误信息 |
| Pending | 匹配到对手单 | Partially Filled | 部分成交 |
| Pending | 用户取消 | Cancelled | 主动撤销 |
| Partially Filled | 继续匹配 | Partially Filled | 剩余部分继续等待 |
| Partially Filled | 剩余全部匹配 | Filled | 完全成交 |
| Partially Filled | 用户取消剩余 | Cancelled | 剩余部分撤销 |
你想想看,如果不用状态机,直接用if-else判断状态流转,代码会变成什么样?我曾经接手过一个项目,订单状态用int表示,到处是switch-case,结果新增一个「冻结」状态时,改了十几个地方才搞定。用状态机的话,只需要在状态定义里加一条转移规则就行。
避坑指南:状态机一定要支持「回滚」操作。比如订单在Pending状态时,如果系统检测到重复提交,应该能回滚到New状态重新校验。我曾经因为没做回滚,导致一笔大单被重复处理了两次,差点引发资金问题。
4.3 订单路由策略
暗池的订单路由和交易所不太一样。交易所通常只有一个订单簿,而暗池可能有多个流动性池子。比如,一个暗池可能同时对接了多个券商、多个算法交易平台。订单该往哪送?这就是路由策略要解决的问题。
我常用的路由策略有三种:
- 最优价格路由:把订单送到当前价格最优的池子。适合市价单。
- 流动性均衡路由:根据各池子的深度和成交量,动态分配订单。适合大单,避免冲击市场。
- 固定优先级路由:按预设的优先级顺序尝试,直到找到可成交的池子。适合对特定池子有偏好的用户。
举个例子,假设我们有三个流动性池子:A(深度大)、B(深度中等)、C(深度小)。一个1000股的买单进来,最优价格路由会直接送到A,因为A的价格最优。但流动性均衡路由可能会把订单拆成三份:500股给A,300股给B,200股给C。这样能避免A的价格被瞬间拉高。
注意:路由策略必须考虑「延迟」。暗池对延迟极其敏感,如果路由逻辑太复杂,反而会拖慢成交速度。我建议把路由策略做成可配置的,上线后根据实际表现调整。
4.4 订单簿数据结构设计
订单簿是OMS的心脏。暗池的订单簿和交易所的订单簿有本质区别:交易所的订单簿是公开的,而暗池的订单簿是隐藏的。也就是说,用户看不到深度,只能看到自己的订单状态。
数据结构上,我推荐使用跳表(Skip List)或红黑树。为什么不用数组?因为订单簿需要频繁的插入和删除操作,数组的O(n)复杂度扛不住高频场景。
下面是一个简化的订单簿数据结构示例:
// 订单簿节点
struct OrderBookNode {
double price; // 价格
int64_t quantity; // 数量
uint64_t order_id; // 订单ID
OrderType type; // 买单/卖单
uint64_t timestamp; // 时间戳
OrderBookNode* next; // 链表指针
};
// 订单簿
class OrderBook {
private:
OrderBookNode* buy_head; // 买单链表头(价格降序)
OrderBookNode* sell_head; // 卖单链表头(价格升序)
std::unordered_map<uint64_t, OrderBookNode*> order_map; // 订单ID到节点的映射
public:
void add_order(Order order);
void cancel_order(uint64_t order_id);
void match_order(); // 匹配引擎调用
};
嗯,这里要注意:暗池的订单簿不需要维护「深度快照」,因为用户看不到。但内部必须维护一个价格-数量映射,用于匹配引擎快速查找对手单。我习惯用std::map来维护价格层级,因为它是红黑树实现,插入和查找都是O(log n)。
下面这张图展示了订单簿的核心结构:
订单簿的核心操作有三个:插入、删除、查询。插入时,按价格找到合适的位置;删除时,通过订单ID的哈希映射快速定位节点;查询时,从链表头开始遍历,找到价格最优的对手单。
个人经验:订单簿的并发控制一定要做好。暗池里多个线程可能同时操作订单簿,比如匹配线程和取消线程。我建议用读写锁:读操作(查询)可以并发,写操作(插入/删除)必须互斥。千万别用全局锁,否则性能会崩。
好了,订单管理系统的核心内容就这些。状态机保证订单状态不乱,路由策略保证订单送到正确的地方,订单簿数据结构保证匹配引擎能高效工作。这三者配合好了,OMS才能稳定运行。