3、订单簿动态过程:订单到达、撤销与成交、订单簿事件驱动模型
好,咱们直接进入正题。
订单簿不是静态的。它每时每刻都在变化。你看到的那个买一卖一价,可能下一秒就变了。我刚开始做高频策略时,总觉得订单簿就是一张快照,后来发现——真正赚钱的机会,藏在订单簿的动态变化里。
3.1 订单簿的三大核心事件
说白了,订单簿的变化就三种:
- 订单到达(Arrival):新订单挂进来
- 订单撤销(Cancellation):挂单被撤走
- 订单成交(Execution):买卖双方对上眼了
你想想看,每一笔交易,都是这三个事件的排列组合。我在项目中遇到过一种情况:某个币种在盘口附近频繁出现「挂单-撤单-再挂单」的循环,后来发现是某个做市商在试探市场深度。嗯,这就是典型的订单到达与撤销的博弈。
3.2 事件驱动模型:订单簿的「心跳」
我个人习惯把订单簿看作一个事件驱动的状态机。每次事件发生,订单簿的状态就更新一次。这个模型长这样:
这个图我画了很多遍。核心逻辑就是:事件进来 → 处理 → 状态更新 → 等待下一个事件。循环往复,直到收盘。
3.3 订单到达:挂单的「瞬间」
订单到达时,系统要做三件事:
- 校验订单合法性:价格、数量、方向对不对
- 插入价格队列:按价格优先、时间优先排队
- 触发行情推送:告诉市场「有新单子了」
这里有个坑。我曾经在实盘中发现,某些交易所的订单到达事件和行情推送之间,有微小的延迟。如果你做高频策略,这个延迟可能就是亏损的根源。所以,永远不要假设事件到达和行情更新是同步的。
关键点:订单到达事件是订单簿变化的「第一推动力」。没有新订单,市场就是一潭死水。
3.4 订单撤销:看不见的「暗流」
撤销事件比到达事件更隐蔽。你想想看,一个订单挂在那里,突然撤走了——如果你只看成交数据,根本发现不了。
我个人习惯把撤销事件分为两类:
- 主动撤销:交易者自己撤单,比如觉得价格不合适
- 被动撤销:系统强制撤单,比如行情波动太大触发了风控
我在做市商策略中,经常利用撤销事件来「试盘」。比如在买一位置挂一个100手的单子,然后观察卖盘的反应。如果卖盘迅速撤单,说明对方在盯着我的单子。嗯,这就是订单簿微观结构的博弈。
注意:撤销事件占比很高。在流动性好的市场,撤销率可能超过70%。也就是说,你看到的10个挂单,可能7个都是「假单」。
3.5 订单成交:最终的「审判」
成交事件是订单簿变化的终点。一个订单成交了,意味着买卖双方达成了共识。
成交事件会触发:
- 移除成交订单:从订单簿中删除
- 更新最新成交价:这个价格会影响后续的订单
- 计算成交量:统计一段时间内的交易量
这里有个细节:大单成交后,订单簿的形态会剧烈变化。比如一个1000手的卖单吃掉买一,买一价格会瞬间下移好几个档位。这种「断层」现象,是很多策略的入场信号。
3.6 事件驱动模型的代码实现
光说不练假把式。我写一个简单的Python示例,展示事件驱动模型的核心逻辑:
class OrderBook:
def __init__(self):
self.bids = {} # 买盘
self.asks = {} # 卖盘
self.last_price = None
def on_order_arrival(self, order):
"""订单到达事件处理"""
if order.side == 'buy':
self.bids[order.price] = self.bids.get(order.price, 0) + order.quantity
else:
self.asks[order.price] = self.asks.get(order.price, 0) + order.quantity
self._check_match()
def on_order_cancel(self, order):
"""订单撤销事件处理"""
if order.side == 'buy':
if order.price in self.bids:
self.bids[order.price] -= order.quantity
if self.bids[order.price] <= 0:
del self.bids[order.price]
else:
if order.price in self.asks:
self.asks[order.price] -= order.quantity
if self.asks[order.price] <= 0:
del self.asks[order.price]
def on_order_execution(self, order):
"""订单成交事件处理"""
self.last_price = order.price
# 移除成交的订单
self.on_order_cancel(order)
def _check_match(self):
"""检查是否有可成交的订单"""
if not self.bids or not self.asks:
return
best_bid = max(self.bids.keys())
best_ask = min(self.asks.keys())
if best_bid >= best_ask:
# 有成交机会
print(f"成交!买一{best_bid} vs 卖一{best_ask}")
这个代码很基础,但核心逻辑都在了。我在实际项目中,会在这个基础上加上时间戳、事件队列、多线程处理等。但万变不离其宗——事件驱动模型的核心就是「来一个事件,处理一个事件」。
3.7 实战中的注意事项
| 事件类型 | 常见陷阱 | 我的建议 |
|---|---|---|
| 订单到达 | 行情推送延迟 | 使用本地时间戳,不要依赖交易所时间 |
| 订单撤销 | 撤销率过高导致数据失真 | 过滤掉存活时间太短的订单 |
| 订单成交 | 大单成交后的流动性断层 | 设置最小成交量阈值 |
小技巧:如果你在做回测,建议把事件驱动模型的时间精度控制在毫秒级。我曾经用秒级数据回测,结果策略表现很好,实盘却亏得一塌糊涂——因为秒级数据丢失了太多微观结构信息。
好了,订单簿的动态过程就讲到这里。记住:订单到达、撤销、成交,这三个事件构成了订单簿的「生命线」。理解它们,你就能看懂市场的微观结构。