订单管理:订单状态机与订单簿维护
订单管理模块,说白了就是交易系统的“心脏”。它负责接收策略的指令,跟踪订单的整个生命周期,最后把执行结果反馈回去。我这些年做过的系统里,订单管理这块出过的问题最多,也最考验细节。
咱们先看一个核心概念——订单状态机。一个订单从出生到结束,状态变化是有严格路径的。我习惯用一张图来理解它:
嗯,这张图我画了好几次才满意。你看,New是起点,所有订单都从这里出发。PartiallyFilled是个“中间态”,它可以多次部分成交,也可以直接跳到终态。Filled、Cancelled、Rejected这三个是终态,到了就不会再变了。
订单状态机的实现细节
我在项目中遇到过一个问题:状态转换的并发控制。多个线程同时更新同一个订单状态,很容易出现“状态回退”的bug。比如一个订单已经Filled了,另一个线程却把它改成了PartiallyFilled。
解决方案其实不复杂——用状态转换表做校验。我建议你写一个字典,明确列出每个状态允许跳转到哪些状态:
class OrderStatusMachine:
# 状态转换规则:当前状态 -> 允许的下一个状态集合
_transitions = {
OrderStatus.NEW: {
OrderStatus.PARTIALLY_FILLED,
OrderStatus.FILLED,
OrderStatus.CANCELLED,
OrderStatus.REJECTED
},
OrderStatus.PARTIALLY_FILLED: {
OrderStatus.PARTIALLY_FILLED, # 允许继续部分成交
OrderStatus.FILLED,
OrderStatus.CANCELLED,
OrderStatus.REJECTED
},
# 终态不允许任何转换
OrderStatus.FILLED: set(),
OrderStatus.CANCELLED: set(),
OrderStatus.REJECTED: set()
}
@classmethod
def can_transition(cls, current: OrderStatus, target: OrderStatus) -> bool:
return target in cls._transitions.get(current, set())
这段代码看着简单,但它是整个订单管理的基石。我曾经因为没做这个校验,导致生产环境出现了一个订单“死循环”——状态在PartiallyFilled和New之间来回跳,最后只能手动修数据库。
订单簿维护:内存里的“账本”
订单簿(Order Book)说白了就是维护所有活跃订单的集合。它需要支持快速增删改查,还要能按价格、时间排序。我个人习惯用两个数据结构:
- 订单ID索引:用字典(dict),key是订单ID,value是订单对象。查询O(1)。
- 价格-订单列表索引:用有序字典(OrderedDict)或跳表(SkipList),按价格排序,方便做价格聚合。
你想想看,如果只用字典,想查某个价格档位有多少订单,就得遍历所有订单,效率太低了。反过来,如果只用价格索引,想根据订单ID查详情,又得遍历。所以两个索引配合使用才是正解。
class OrderBook:
def __init__(self):
self._orders_by_id = {} # 订单ID -> Order对象
self._orders_by_price = {} # 价格 -> [Order对象列表]
self._sorted_prices = [] # 排序后的价格列表
def add_order(self, order):
self._orders_by_id[order.id] = order
price_orders = self._orders_by_price.setdefault(order.price, [])
price_orders.append(order)
# 维护价格排序(这里用二分插入优化)
bisect.insort(self._sorted_prices, order.price)
def remove_order(self, order_id):
order = self._orders_by_id.pop(order_id, None)
if order:
price_orders = self._orders_by_price[order.price]
price_orders.remove(order)
if not price_orders:
del self._orders_by_price[order.price]
self._sorted_prices.remove(order.price)
订单路由:把指令送到对的地方
订单路由(Order Routing)解决的是“往哪发”的问题。如果是单一交易所,路由很简单——直接发。但如果是多交易所、多经纪商,路由就复杂了。
我建议把路由逻辑抽象成一个策略模式。不同的路由策略可以随时切换:
| 路由策略 | 适用场景 | 缺点 |
|---|---|---|
| 固定路由 | 单一交易所,或指定经纪商 | 无法应对交易所故障 |
| 智能路由 | 多交易所,追求最优价格 | 实现复杂,延迟较高 |
| 轮询路由 | 负载均衡,分散风险 | 可能错过最优价格 |
我在项目中遇到过智能路由的坑:两个交易所的行情延迟不同,导致路由决策基于过时的数据。后来我加了一个“延迟补偿”机制,用历史数据估算当前的真实价格。
执行报告处理:别漏掉任何一条
执行报告(Execution Report)是交易所返回的订单状态更新。它可能包含成交、部分成交、拒绝、撤单确认等信息。处理执行报告的核心原则是:幂等性。
为什么?因为网络可能重传,同一个执行报告可能收到两次。如果不做幂等处理,订单状态会被重复更新,导致数据不一致。
class ExecutionReportHandler:
def __init__(self):
self._processed_reports = set() # 已处理的报告ID
def handle_report(self, report):
# 幂等检查:如果已经处理过,直接跳过
if report.id in self._processed_reports:
return
# 更新订单状态
order = self._order_book.get_order(report.order_id)
if not order:
# 这里要记录日志,可能是幽灵订单
self._logger.warning(f"收到未知订单的执行报告: {report.order_id}")
return
# 检查状态转换是否合法
if not OrderStatusMachine.can_transition(order.status, report.status):
self._logger.error(f"非法状态转换: {order.status} -> {report.status}")
return
# 更新订单
order.update_from_report(report)
self._processed_reports.add(report.id)
# 通知策略层
self._event_bus.publish(OrderEvent(order))
核心要点:执行报告处理必须保证“至少一次”语义。也就是说,宁可重复处理,也不能漏掉。重复处理可以通过幂等性来规避,但漏掉一条报告,订单状态就永远不对了。
嗯,这里要注意一个细节:执行报告里的成交数量可能和订单的剩余数量对不上。比如订单是100股,第一次成交30股,第二次成交80股。第二次成交时,实际只能成交70股(因为只剩70股了)。这种情况交易所会返回一个“超量成交”的拒绝,或者自动调整成交数量。我建议你在代码里做一次校验:
# 检查成交数量是否合理
if report.filled_quantity > order.remaining_quantity:
# 这种情况很少见,但一旦发生就是大问题
self._logger.critical(f"成交数量超过剩余数量: {report}")
# 按剩余数量截断
report.filled_quantity = order.remaining_quantity
我曾经因为没做这个校验,导致一个订单的累计成交数量超过了原始数量,最后在结算时对不上账,花了整整两天才排查出来。
订单管理模块就讲到这里。记住三个核心:状态机保证逻辑正确,订单簿保证数据高效,执行报告保证不丢不重。把这三点做好,订单管理就稳了。