订单簿核心操作:添加、撤销、成交与盘口更新
大家好,我是你们的量化交易讲师。今天我们来聊聊订单簿最核心的四个操作——说白了,就是订单从生到死的完整生命周期。
我个人习惯把订单簿想象成一个「排队系统」。每个订单进来,要么排队等着,要么插队成交,要么中途离开。嗯,就这么简单。但简单背后,藏着不少坑。
1. 订单添加(Add)—— 让订单进入队列
订单添加,就是把一个新订单挂到订单簿上。听起来简单?其实要考虑两个关键问题:插到哪和怎么插。
我见过不少新手直接往列表尾部追加,结果价格优先原则全乱了。正确的做法是:
- 价格优先:买单从高到低排,卖单从低到高排
- 时间优先:同价格下,先来的先成交
举个例子,假设当前卖单队列是这样的:
| 价格 | 数量 | 时间戳 |
|---|---|---|
| 10.02 | 100 | 09:30:01 |
| 10.01 | 200 | 09:30:00 |
| 10.00 | 150 | 09:29:59 |
现在来了一个卖单:价格10.01,数量50。它应该插在哪?
答案是:排在10.01那个200手的后面。因为价格相同,时间晚的排在后面。
2. 订单撤销(Cancel)—— 让订单离开队列
撤销操作,说白了就是「我不玩了,把我的单子撤回来」。但这里有个容易踩的坑——撤销的订单可能已经部分成交了。
我曾经在实盘项目中遇到过这种情况:一个1000手的买单,已经成交了300手,用户点击撤销。这时候你只能撤销剩下的700手,已经成交的部分是撤不回来的。
撤销的流程其实很简单:
- 根据订单ID找到对应的订单
- 检查订单状态(是否已完全成交)
- 从队列中移除该订单
- 更新对应的盘口深度
3. 订单成交(Execute)—— 订单簿的核心引擎
成交操作,是订单簿最刺激的部分。一个新订单进来,可能一口气吃掉好几个对手单。
你想想看,一个买单进来,它会从最低的卖单开始「吃」,直到自己的数量被满足,或者卖单被吃光。
成交的逻辑是这样的:
def execute_order(new_order, order_book):
# 新订单是买单,从卖单队列最低价开始匹配
if new_order.side == 'buy':
while new_order.remaining_qty > 0 and order_book.asks:
best_ask = order_book.asks[0] # 最低卖价
if new_order.price < best_ask.price:
break # 价格不够,无法成交
# 计算成交数量
trade_qty = min(new_order.remaining_qty, best_ask.remaining_qty)
# 更新双方剩余数量
new_order.remaining_qty -= trade_qty
best_ask.remaining_qty -= trade_qty
# 如果卖单完全成交,移除它
if best_ask.remaining_qty == 0:
order_book.asks.pop(0)
# 如果新订单还有剩余,挂到买单队列
if new_order.remaining_qty > 0:
order_book.add_order(new_order)
嗯,这里要注意一个细节:部分成交。一个订单可能只成交了一部分,剩下的部分要挂回订单簿。我刚开始做的时候,就忘了处理这个情况,结果订单凭空消失了,被测试同事骂了一顿。
4. 更新买卖盘口(Order Book Update)—— 保持数据新鲜
盘口更新,说白了就是每次操作后,重新计算「最佳买价」和「最佳卖价」。这个操作看似简单,但频率极高——每次添加、撤销、成交后都要做。
我个人习惯把盘口更新做成一个独立函数,而不是散落在各个操作里。这样代码更干净,也方便加日志。
def update_bbo(order_book):
# 最佳买价:买单队列的最高价
if order_book.bids:
order_book.best_bid = order_book.bids[0].price
order_book.best_bid_qty = order_book.bids[0].total_qty
else:
order_book.best_bid = 0
order_book.best_bid_qty = 0
# 最佳卖价:卖单队列的最低价
if order_book.asks:
order_book.best_ask = order_book.asks[0].price
order_book.best_ask_qty = order_book.asks[0].total_qty
else:
order_book.best_ask = float('inf')
order_book.best_ask_qty = 0
# 计算买卖价差
order_book.spread = order_book.best_ask - order_book.best_bid
这里有个坑:当买单队列为空时,最佳买价应该是0还是None?我建议用0,因为后续计算价差时不会报错。卖单队列为空时,最佳卖价用无穷大。
知识体系总览
说了这么多,我们来画张图,把四个操作的关系理清楚:
从这张图你能看到,四个操作不是孤立的。添加和撤销会改变订单队列,成交会消耗订单,而盘口更新则是每次操作后的「收尾工作」。
总结一下
订单簿的四个核心操作,说白了就是:
- 添加:按价格时间排队
- 撤销:检查状态再移除
- 成交:从最优价格开始吃单
- 盘口更新:每次操作后刷新最佳价格
嗯,这些基础操作看起来简单,但它们是整个订单簿仿真系统的地基。地基不稳,上面的策略验证全是白搭。我建议你动手写一遍代码,把每个操作的边界条件都测一测——比如空队列时撤销、价格刚好相等时成交、大单吃掉多个档位等等。
下一章我们会聊订单簿的数据结构设计,到时候你会看到,不同的数据结构对性能影响有多大。