订单簿与盘口分析:买卖盘口数据结构、订单簿动态更新、盘口深度与价差分析、大单与小单识别
大家好,我是老李。在量化交易这行摸爬滚打了十来年,今天咱们聊聊A股T0交易里最基础、也最关键的一环——订单簿与盘口分析。
说白了,订单簿就是交易所给你的一张“实时报价单”。它告诉你,此时此刻,有多少人想买,有多少人想卖,各自出什么价。我刚开始做T0策略时,觉得这东西太简单了,不就是看个买一卖一吗?后来吃了不少亏才明白,订单簿里的门道深着呢。
买卖盘口数据结构
先看最基础的数据结构。A股的盘口数据,通常包含以下字段:
| 字段 | 含义 | 示例 |
|---|---|---|
| BidPrice[1..5] | 买一到买五的价格 | 10.01, 10.00, 9.99, 9.98, 9.97 |
| BidVolume[1..5] | 买一到买五的挂单量(手) | 100, 200, 150, 80, 60 |
| AskPrice[1..5] | 卖一到卖五的价格 | 10.02, 10.03, 10.04, 10.05, 10.06 |
| AskVolume[1..5] | 卖一到卖五的挂单量(手) | 90, 110, 130, 70, 50 |
| LastPrice | 最新成交价 | 10.015 |
| TotalVolume | 当日总成交量 | 1234567 |
嗯,这里要注意一点:A股是“价格优先、时间优先”的撮合原则。买盘价格从高到低排,卖盘价格从低到高排。所以买一永远是出价最高的那个买家,卖一永远是出价最低的那个卖家。
我个人习惯用Python的字典或namedtuple来封装这个结构,方便后续处理:
from collections import namedtuple
OrderBookLevel = namedtuple('OrderBookLevel', ['price', 'volume'])
OrderBook = namedtuple('OrderBook', ['bid_levels', 'ask_levels', 'last_price'])
# 示例数据
bid_levels = [
OrderBookLevel(10.01, 100),
OrderBookLevel(10.00, 200),
OrderBookLevel(9.99, 150),
]
ask_levels = [
OrderBookLevel(10.02, 90),
OrderBookLevel(10.03, 110),
OrderBookLevel(10.04, 130),
]
book = OrderBook(bid_levels, ask_levels, 10.015)
print(book)
订单簿动态更新
订单簿不是静态的。每一秒,甚至每一毫秒,它都在变。我遇到过最夸张的情况,一只热门股在开盘后10秒内,买一卖一变了上百次。
动态更新主要分三种情况:
- 新增订单:新的买单或卖单进入队列
- 撤销订单:已有的挂单被撤掉
- 成交订单:买卖双方匹配成功,订单消失
举个例子,假设当前买一是10.01元,挂了100手。突然来了一个10.02元的买单,那这个新买单就会成为新的买一,原来的买一变成买二。这就是“价格优先”的体现。
我曾经踩过一个坑:以为订单簿更新是“全量推送”,每次收到数据就替换整个盘口。结果发现数据对不上,回测成绩很好,实盘一跑就亏。后来才搞清楚,交易所推送的是“增量更新”——只告诉你哪些变了,没变的你得自己保留。
正确的做法是维护一个本地的订单簿快照,然后根据增量事件来更新:
class OrderBookManager:
def __init__(self):
self.bids = {} # price -> volume
self.asks = {}
def update(self, side, price, volume):
"""side: 'bid' or 'ask', volume=0 表示撤单"""
if side == 'bid':
if volume == 0:
self.bids.pop(price, None)
else:
self.bids[price] = volume
else:
if volume == 0:
self.asks.pop(price, None)
else:
self.asks[price] = volume
def get_top_n(self, n=5):
sorted_bids = sorted(self.bids.items(), reverse=True)[:n]
sorted_asks = sorted(self.asks.items())[:n]
return sorted_bids, sorted_asks
盘口深度与价差分析
盘口深度,说白了就是“某个价位上到底有多少单子”。价差呢,就是买一和卖一之间的距离。
价差越小,说明流动性越好,交易成本越低。我一般用这个公式算:
Spread = (AskPrice1 - BidPrice1) / MidPrice
其中MidPrice = (AskPrice1 + BidPrice1) / 2。如果价差突然变大,往往意味着市场情绪紧张,或者有大消息要出来。
盘口深度我习惯用“累积深度”来看。比如,我想知道在买一到买五这个区间里,总共挂了多少钱:
def cumulative_depth(levels, max_price=None):
total_volume = 0
total_value = 0
for price, volume in levels:
if max_price and price > max_price:
break
total_volume += volume
total_value += price * volume * 100 # 1手=100股
return total_volume, total_value
# 示例
bid_levels = [(10.01, 100), (10.00, 200), (9.99, 150)]
vol, val = cumulative_depth(bid_levels)
print(f"买盘总挂单量: {vol}手, 总价值: {val:.2f}元")
你想想看,如果买盘深度突然变厚,但价格没怎么动,说明有大资金在下面托底。反过来,卖盘深度变厚,可能就是有人在上面压盘。
大单与小单识别
这是T0交易里最实用的技巧之一。大单和小单,识别方法有很多,我分享一个我自己用了很久的经验法则。
首先,得定义“大”和“小”。不能一刀切,得看这只股票的流动性。我一般用这个公式:
大单阈值 = 过去N日平均每笔成交量 × 3
举个例子,某只股票过去20天平均每笔成交是50手,那超过150手的单子,我就认为是“大单”。
识别大单有什么用?我告诉你,大单往往是“聪明钱”的痕迹。比如:
- 买盘出现大单,但价格不涨——可能是主力在吸筹
- 卖盘出现大单,但价格不跌——可能是主力在压盘吸货
- 大单频繁撤单又挂单——可能是主力在试盘
我曾经遇到过一个经典案例:某只股票盘口一直很平静,突然在买二位置挂了一笔500手的大单,但买一只有几十手。我当时觉得奇怪,这不符合常理啊。后来发现,这是主力在“托单”,吸引散户跟风买入,然后自己在卖一悄悄出货。嗯,这种套路在A股很常见。
小单呢,也别忽视。小单的密集程度,往往能反映散户的情绪。如果小单持续买入,但价格不涨,说明有人在卖给他们——那这个人很可能就是主力。
我一般用这个函数来实时监控大单:
def detect_large_order(volume, avg_volume, multiplier=3):
threshold = avg_volume * multiplier
if volume >= threshold:
return "大单"
elif volume >= threshold * 0.5:
return "中单"
else:
return "小单"
# 假设过去20天平均每笔成交60手
avg = 60
print(detect_large_order(200, avg)) # 输出: 大单
print(detect_large_order(80, avg)) # 输出: 中单
print(detect_large_order(30, avg)) # 输出: 小单
核心要点:订单簿分析不是看单个数据点,而是看“变化”。价格没动但深度变了,或者深度没变但价差变了,这些才是真正的信号。
我的小技巧:实盘时,我会把盘口数据做成热力图。买盘用绿色,卖盘用红色,颜色深浅代表挂单量大小。一眼就能看出资金动向,比看数字快多了。
注意:盘口数据更新极快,尤其是高频行情下。如果你的代码处理速度跟不上,建议用Cython或者Numba加速。我曾经因为Python的GIL问题,导致订单簿更新延迟了200毫秒,结果策略完全失效。
最后,我画了一张图,把订单簿分析的整体逻辑串起来。你看完应该能有个全局感:
这张图把整个流程串起来了:从交易所原始数据,到数据结构化存储,再到动态更新,最后落到三个核心分析维度上。你写代码的时候,就按这个思路来,不会乱。
好了,订单簿和盘口分析这块,今天就聊到这儿。记住一句话:盘口是市场的脉搏,读懂了它,你就读懂了资金的意图。