3、订单簿事件驱动模型:订单到达、撤销、成交事件,事件流与状态更新。
好,咱们进入第三章。这一章我打算聊聊订单簿的核心运转机制——事件驱动模型。
你想想看,一个高频交易系统,每秒要处理几万甚至几十万笔订单。如果每来一个订单都去全量扫描一遍订单簿,那性能肯定崩了。所以,实际的做法是:用事件驱动。说白了,就是“有事才动,没事歇着”。
3.1 订单簿的三大核心事件
我个人习惯把订单簿的事件分成三类:到达、撤销、成交。其实还有修改(Modify),但很多交易所把修改拆成“撤销+新到达”,所以本质上还是这三类。
| 事件类型 | 触发条件 | 对订单簿的影响 |
|---|---|---|
| 订单到达(Add) | 新限价单进入系统 | 插入到对应的价格队列 |
| 订单撤销(Cancel) | 交易者主动撤单 | 从价格队列中移除 |
| 订单成交(Trade) | 买卖双方价格匹配 | 移除双方订单,更新成交量 |
嗯,这里要注意:成交事件其实是由“到达事件”触发的。当一个买单到达时,如果它和卖一的价格能对上,系统会先撮合,再决定是否把剩余部分挂到订单簿上。我在项目中遇到过不少新手,把成交和到达当成两个独立事件来处理,结果订单簿状态怎么都对不上。
3.2 事件流与状态更新的逻辑
事件驱动模型的核心,就是一个事件循环。系统不断从消息队列里取出事件,然后更新订单簿状态。我画了一张流程图,帮你理清这个逻辑:
这个流程其实很直观:事件从交易所网关进来,先排队,然后分发器根据事件类型,扔给对应的处理器。处理器干完活,更新订单簿状态。我曾经在优化一个低延迟系统时,发现瓶颈就在事件分发器上——它用了全局锁,导致三个处理器互相等待。后来改成无锁队列,延迟直接降了40%。
3.3 代码实现:一个精简的事件驱动订单簿
光说不练假把式。咱们直接看代码。下面是一个极简的订单簿事件驱动框架,只保留了核心逻辑:
class OrderBook:
def __init__(self):
self.bids = {} # 买单队列,key=价格,value=订单列表
self.asks = {} # 卖单队列
self.order_map = {} # 订单ID -> 订单对象
def process_event(self, event):
"""事件分发入口"""
if event.type == 'ADD':
self._handle_add(event)
elif event.type == 'CANCEL':
self._handle_cancel(event)
elif event.type == 'TRADE':
self._handle_trade(event)
else:
raise ValueError(f"未知事件类型: {event.type}")
def _handle_add(self, event):
"""订单到达处理"""
order = event.order
self.order_map[order.id] = order
queue = self.bids if order.side == 'BUY' else self.asks
if order.price not in queue:
queue[order.price] = []
queue[order.price].append(order)
# 检查是否能立即成交
self._try_match(order)
def _handle_cancel(self, event):
"""订单撤销处理"""
order_id = event.order_id
order = self.order_map.get(order_id)
if not order:
return # 订单不存在,忽略
queue = self.bids if order.side == 'BUY' else self.asks
price_queue = queue.get(order.price, [])
if order in price_queue:
price_queue.remove(order)
del self.order_map[order_id]
def _handle_trade(self, event):
"""成交处理(通常由对手单触发)"""
# 实际成交由 _try_match 处理
# 这里只做日志或统计
self.last_trade_price = event.price
self.last_trade_volume = event.volume
def _try_match(self, new_order):
"""尝试撮合新订单"""
# 省略具体撮合逻辑,后面章节会细讲
pass
3.4 事件顺序与状态一致性
做高频数据压缩,最怕什么?事件顺序乱了。你想想看,如果撤销事件先于到达事件被处理,那订单簿里就会多出一个幽灵订单。反过来,如果成交事件先于到达事件,那撮合逻辑就全错了。
所以,我一般会要求事件流必须满足以下约束:
- 全局有序:同一只股票的所有事件,必须按时间戳严格排序。
- 无重复:同一个订单ID的事件,不能出现两次(撤销后不能再撤销)。
- 因果一致:一个订单的撤销事件,必须在它的到达事件之后。
3.5 事件驱动的性能考量
做高频交易,性能就是生命。我总结了几条经验:
- 用数组代替链表:订单队列用Python的list,而不是deque。虽然deque两端操作快,但list在内存连续性上更好,CPU缓存命中率高。
- 预分配内存:对于已知最大深度的订单簿,提前分配好价格档位的数组,避免运行时动态扩容。
- 批量处理:如果事件到达速度太快,可以攒一批(比如100个)再一起处理,减少函数调用开销。
嗯,这一章的内容就到这里。事件驱动模型是订单簿的骨架,理解透了,后面的数据压缩和重构才能玩得转。记住:事件顺序就是生命线,任何时候都不要打破它。
- 三大事件:到达、撤销、成交
- 事件流必须严格有序,否则状态会乱
- 代码实现时注意幂等性和异常处理
- 性能优化:数组、预分配、批量处理