订单簿深度解析:限价单与市价单、订单簿的构建与动态变化、买卖盘口与市场压力
大家好,我是老张。今天咱们来聊聊订单簿——这个在量化交易里最基础、也最容易被忽视的东西。
说实话,我刚入行那会儿,觉得订单簿不就是一堆买单卖单堆在那吗?有什么好研究的。直到有一次,我写的一个高频策略在实盘里连续亏损,回测却漂亮得很。排查了三天,最后发现是订单簿的模拟方式出了问题。嗯,从那以后,我再也不敢小看它了。
一、限价单与市价单:订单簿的两种基本元素
订单簿里只有两种订单:限价单和市价单。说白了,一个是你愿意等,一个是你急着走。
- 限价单(Limit Order):你指定一个价格,到了就成交,不到就挂着。它提供流动性,是订单簿的"骨架"。
- 市价单(Market Order):你不看价格,直接按当前最优价成交。它消耗流动性,是订单簿的"血液"。
我个人习惯把限价单比作"钓鱼"——你放下鱼饵(挂单),等着鱼来咬钩。市价单则是"直接下水抓鱼"——快,但可能抓到的鱼没那么大(滑点)。
核心观点:限价单构建了订单簿的静态结构,市价单驱动了订单簿的动态变化。两者缺一不可。
二、订单簿的构建:从零开始搭一个
你想想看,一个订单簿本质上就是个价格-数量的映射。买盘按价格从高到低排,卖盘从低到高排。
我在项目中遇到过一个问题:用Python的dict来存订单簿,结果每次更新都要排序,速度慢得离谱。后来改用sortedcontainers库,才解决了性能瓶颈。
下面是我常用的订单簿数据结构:
from sortedcontainers import SortedDict
class OrderBook:
def __init__(self):
self.bids = SortedDict() # 买盘,key是价格,value是数量
self.asks = SortedDict() # 卖盘,key是价格,value是数量
def update(self, side, price, size):
"""更新订单簿"""
if side == 'bid':
if size == 0:
# 删除该价格档位
self.bids.pop(price, None)
else:
self.bids[price] = size
else:
if size == 0:
self.asks.pop(price, None)
else:
self.asks[price] = size
def get_top(self, n=5):
"""获取前n档盘口"""
top_bids = list(self.bids.items())[-n:][::-1]
top_asks = list(self.asks.items())[:n]
return top_bids, top_asks
这段代码看着简单,但有个坑:SortedDict默认是升序排列,所以买盘要取最后几个再反转,才能得到从高到低的顺序。我曾经因为这个顺序搞反了,回测结果直接翻了个个儿。
三、订单簿的动态变化:每一秒都在变
订单簿不是静态的。它每时每刻都在变化。主要变化来源有三个:
- 新订单进入:新的限价单挂进来,增加流动性
- 订单被成交:市价单吃掉限价单,减少流动性
- 订单被撤销:挂单的人撤单了,流动性消失
为什么会这样?因为市场里每个人都在博弈。你挂一个买单,可能下一秒就被市价单吃掉,也可能挂了一天都没人理你。
我记得有一次做A股的高频策略,发现某个股票在开盘前5分钟,订单簿的更新频率能达到每秒几百次。那个数据量,用普通的list根本扛不住。后来我改用numpy数组预分配内存,才勉强跟上节奏。
实战技巧:处理订单簿更新时,建议用增量更新而不是全量替换。全量替换虽然简单,但高频场景下性能会崩。
四、买卖盘口与市场压力
买卖盘口,就是订单簿最上面那几档。买一、买二、卖一、卖二……这些数据直接反映了市场的短期供需关系。
我个人习惯用"盘口压力"来判断市场方向。盘口压力说白了就是:
- 卖盘压力大:卖一档的挂单量远大于买一档,说明卖方急于出手,价格可能下跌
- 买盘压力大:买一档的挂单量远大于卖一档,说明买方急于买入,价格可能上涨
但这里有个坑:挂单量大的档位,有时候是"假墙"。我曾经在期货市场看到卖一挂了1000手大单,结果价格一碰到那个位置,单子瞬间撤了——典型的"诱空"手法。
下面是我常用的盘口压力指标:
def calc_pressure(bids, asks, depth=5):
"""
计算盘口压力
bids: [(price, size), ...] 买盘列表
asks: [(price, size), ...] 卖盘列表
"""
bid_volume = sum(size for _, size in bids[:depth])
ask_volume = sum(size for _, size in asks[:depth])
# 压力比 > 1 表示买盘强,< 1 表示卖盘强
pressure_ratio = bid_volume / ask_volume if ask_volume > 0 else float('inf')
# 加权压力(考虑价格距离)
weighted_bid = sum(price * size for price, size in bids[:depth])
weighted_ask = sum(price * size for price, size in asks[:depth])
return {
'pressure_ratio': pressure_ratio,
'weighted_bid': weighted_bid,
'weighted_ask': weighted_ask
}
避坑指南:我曾经只用盘口压力做交易信号,结果在震荡市里被反复打脸。盘口压力只能作为辅助指标,不能单独使用。一定要结合成交量、波动率等其他维度一起分析。
五、知识体系结构图
下面这张图,是我自己梳理的订单簿知识体系。你把它理解了,订单簿这块就算入门了。
这张图把订单簿的核心要素串起来了。从最基础的限价单和市价单,到动态变化,再到盘口压力分析,最后落到实际应用。你顺着这个脉络学,不会迷路。
六、实战中的几个关键点
最后,分享几个我在实战中踩过的坑:
| 场景 | 常见问题 | 我的解决方案 |
|---|---|---|
| 高频更新 | Python dict更新太慢 | 用SortedDict或预分配numpy数组 |
| 盘口假墙 | 大单挂单后迅速撤单 | 观察挂单持续时间,过滤掉短命单 |
| 数据对齐 | 买卖盘时间戳不一致 | 使用交易所的snapshot序列号对齐 |
| 深度计算 | 只算前几档不够用 | 计算累计深度曲线,看整体分布 |
一个小技巧:分析盘口压力时,别只看数量,还要看价格间距。如果卖一和卖二之间价差很大,说明卖方在"跳空",压力可能没那么大。反之,如果价差很小,说明卖方在"堆叠",压力真实。
好了,订单簿这块就聊到这儿。记住一句话:订单簿是市场的"心电图",读懂了它,你就能感知市场的脉搏。