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毫秒查一次订单状态。说实话,这方法在回测里跑得挺欢,一到实盘就露馅了。为什么?因为轮询有延迟,而且浪费资源。

事件回调的核心逻辑是这样的:

  1. 交易所推送订单状态变化事件
  2. 回调函数被触发,传入事件数据
  3. 系统根据事件类型做相应处理

来看一个实际的回调处理示例:

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 订单处理的核心流程图

下面这张图,是我做订单处理模块时画的顶层设计。你看一遍,基本就能理解整个流程了:

订单事件处理核心流程 策略信号生成 订单创建(New) 订单状态机 新单 → 部分成交 → 完全成交/撤单 事件回调处理 订单簿维护 关键说明 1. 策略信号触发订单创建 2. 订单进入状态机流转 3. 状态变化触发回调事件 4. 回调更新订单簿和持仓 5. 订单簿反馈给策略层 ⚠ 注意点 • 回调函数不要做IO操作 • 状态机要覆盖所有路径 • 订单簿要支持快速查询 • 注意并发安全问题 • 记录所有状态变更日志

这张图里,我特意把「事件回调」和「订单簿维护」分成了两个独立模块。为什么?因为在实际系统中,回调处理是高频操作,订单簿维护是数据持久化。两者混在一起,性能会出问题。我见过有人把订单簿更新写在回调函数里,结果回调一多,订单簿的锁竞争直接把系统拖垮了。

总结一下:订单事件处理这块,核心就三件事——状态机管生命周期,回调管事件通知,订单簿管数据存储。这三件事做好了,你的交易系统就有了一个稳固的底层。别小看这些基础模块,我见过太多系统因为订单处理没做好,实盘第一天就出事故的。


公众号:蓝海资料掘金营,微信deep3321