4. 订单事件处理:订单状态机与订单簿维护
订单事件处理,说白了就是量化交易系统的心脏跳动。你策略写得再好,信号再准,如果订单处理这块出了岔子,那一切都是白搭。我个人习惯把这块叫做「交易系统的最后一公里」——前面99%的功夫都花在分析上,最后这1%的订单处理,决定了你到底是赚钱还是亏钱。
4.1 订单状态机:从新单到终态的完整生命周期
每个订单从出生到消亡,都会经历一系列状态变化。我刚开始做量化的时候,觉得状态机这东西太简单了,不就是几个状态来回切换吗?直到有一次实盘,我的系统因为没处理好「部分成交后撤单」这个边缘情况,导致仓位计算全乱套了……嗯,从那以后我再也不敢小看状态机了。
一个标准的订单状态机,包含以下几个核心状态:
- 新单(New):订单刚提交到交易所,还没开始撮合
- 部分成交(Partially Filled):订单被部分撮合,还有剩余数量在排队
- 完全成交(Filled):订单全部撮合完毕,生命周期结束
- 撤单(Canceled):订单被主动取消,生命周期结束
- 拒绝(Rejected):订单因风控或格式问题被交易所拒绝
核心要点:状态机不是简单的线性流转。比如「新单」可以直接变成「完全成交」(一次性全部成交),也可以先变成「部分成交」,再变成「完全成交」或「撤单」。你想想看,如果代码里只写了线性流转,遇到部分成交后撤单的情况,系统直接就崩了。
下面是我在实际项目中用到的订单状态机代码骨架:
class OrderStateMachine:
def __init__(self):
self.state = 'NEW'
self.filled_qty = 0
self.canceled_qty = 0
def on_fill(self, fill_qty):
"""处理成交事件"""
self.filled_qty += fill_qty
if self.filled_qty >= self.total_qty:
self.state = 'FILLED'
else:
self.state = 'PARTIALLY_FILLED'
def on_cancel(self):
"""处理撤单事件"""
if self.state in ['NEW', 'PARTIALLY_FILLED']:
self.state = 'CANCELED'
else:
raise ValueError(f'订单状态 {self.state} 不允许撤单')
个人经验:我建议在状态机里加一个「已提交(Submitted)」状态,放在「新单」之前。因为从你发出指令到交易所确认收到,中间有网络延迟。如果网络断了,你以为是新单,实际上交易所根本没收到。加一个Submitted状态,能帮你区分「我发了」和「交易所确认了」。
4.2 订单事件回调:让系统活起来
订单状态变了,系统得知道。怎么知道?靠回调。回调机制说白了就是「你告诉我,别让我一直问」。我见过不少新手喜欢用轮询——每隔100毫秒查一次订单状态。说实话,这方法在回测里跑得挺欢,一到实盘就露馅了。为什么?因为轮询有延迟,而且浪费资源。
事件回调的核心逻辑是这样的:
- 交易所推送订单状态变化事件
- 回调函数被触发,传入事件数据
- 系统根据事件类型做相应处理
来看一个实际的回调处理示例:
class OrderEventCallback:
def __init__(self):
self.order_book = OrderBook()
self.position_manager = PositionManager()
def on_order_event(self, event):
"""订单事件统一入口"""
event_type = event['type']
order_id = event['order_id']
if event_type == 'fill':
self._handle_fill(event)
elif event_type == 'cancel':
self._handle_cancel(event)
elif event_type == 'reject':
self._handle_reject(event)
def _handle_fill(self, event):
"""处理成交回调"""
# 更新订单状态
order = self.order_book.get_order(event['order_id'])
order.on_fill(event['fill_qty'])
# 更新持仓
self.position_manager.update_position(
symbol=event['symbol'],
side=event['side'],
qty=event['fill_qty'],
price=event['fill_price']
)
# 更新订单簿
self.order_book.update_order(order)
避坑指南:我曾经在回调函数里直接做数据库写入操作,结果交易所一秒推送了上千个成交事件,数据库连接池直接被打满了。回调函数里只做内存操作,持久化放到另一个线程去做。记住:回调要快,别阻塞。
4.3 订单簿维护:你的交易大脑
订单簿是什么?说白了就是交易所里所有未成交订单的集合。你的订单在里面,别人的订单也在里面。维护好订单簿,你才能知道自己的订单现在排第几,有没有可能成交。
订单簿的核心数据结构其实很简单:
| 字段 | 类型 | 说明 |
|---|---|---|
| order_id | string | 订单唯一标识 |
| symbol | string | 交易品种 |
| side | enum | 买卖方向 |
| price | float | 委托价格 |
| qty | float | 委托数量 |
| filled_qty | float | 已成交数量 |
| status | enum | 当前状态 |
| create_time | datetime | 创建时间 |
我习惯用字典来维护订单簿,key是order_id,value是订单对象。这样查找订单的时间复杂度是O(1),性能杠杠的。
class OrderBook:
def __init__(self):
self.orders = {} # order_id -> Order
self.active_orders = {} # 只存未完全成交的订单
def add_order(self, order):
self.orders[order.order_id] = order
if order.status in ['NEW', 'PARTIALLY_FILLED']:
self.active_orders[order.order_id] = order
def update_order(self, order):
"""更新订单状态"""
self.orders[order.order_id] = order
if order.status in ['FILLED', 'CANCELED', 'REJECTED']:
# 从活跃订单中移除
self.active_orders.pop(order.order_id, None)
else:
self.active_orders[order.order_id] = order
def get_active_orders(self, symbol=None):
"""获取所有活跃订单"""
if symbol:
return {oid: o for oid, o in self.active_orders.items()
if o.symbol == symbol}
return self.active_orders
一个小技巧:订单簿里除了存订单本身,我还会维护一个「按价格排序的活跃订单列表」。这样在做撤单策略时,可以快速找到哪些订单价格不好、需要撤掉重下。说白了就是:别只存数据,要存能帮你做决策的数据结构。
4.4 订单处理的核心流程图
下面这张图,是我做订单处理模块时画的顶层设计。你看一遍,基本就能理解整个流程了:
这张图里,我特意把「事件回调」和「订单簿维护」分成了两个独立模块。为什么?因为在实际系统中,回调处理是高频操作,订单簿维护是数据持久化。两者混在一起,性能会出问题。我见过有人把订单簿更新写在回调函数里,结果回调一多,订单簿的锁竞争直接把系统拖垮了。
总结一下:订单事件处理这块,核心就三件事——状态机管生命周期,回调管事件通知,订单簿管数据存储。这三件事做好了,你的交易系统就有了一个稳固的底层。别小看这些基础模块,我见过太多系统因为订单处理没做好,实盘第一天就出事故的。
公众号:蓝海资料掘金营,微信deep3321