4、市场深度分析:Level 2 订单簿构建、深度图绘制、加权深度计算
各位同学,今天我们来聊聊市场深度分析。说实话,这个主题在量化交易里属于「硬核」部分。很多人看行情只看价格和成交量,但真正的流动性细节,藏在 Level 2 数据里。
我个人习惯把市场深度比作「水池的深浅」。价格只是水面,而订单簿告诉你水下到底有多少鱼。嗯,咱们一步步来拆解。
4.1 订单簿的构建:从原始数据到实时快照
Level 2 数据,说白了就是交易所推送的逐笔委托。每一笔都包含价格、数量、方向(买/卖)。我们要做的,就是把这些数据拼成一张「实时挂单表」。
我在项目中遇到过一个问题:交易所的数据是增量推送的,不是全量。比如第一笔告诉你「有人在 10.00 元挂了 100 手买单」,第二笔告诉你「10.00 元的买单被撤了 50 手」。如果你不维护一个状态机,数据就会乱套。
正确的做法是维护两个字典:bids(买单)和 asks(卖单)。每次收到增量,就更新对应价格档位的数量。数量归零时,删除该档位。
核心逻辑:订单簿 = 价格 → 数量的映射,按价格排序。
# 伪代码:增量更新订单簿
bids = {} # 买单字典 {价格: 数量}
asks = {} # 卖单字典 {价格: 数量}
def update_orderbook(side, price, size):
if side == 'buy':
if size == 0:
bids.pop(price, None) # 撤单
else:
bids[price] = size # 新单或修改
elif side == 'sell':
if size == 0:
asks.pop(price, None)
else:
asks[price] = size
# 排序后得到深度快照
sorted_bids = sorted(bids.items(), reverse=True) # 从高到低
sorted_asks = sorted(asks.items()) # 从低到高
你想想看,如果不用增量更新,每次全量拉取,网络开销和延迟都受不了。交易所也不会给你全量推送,太费带宽了。
我的经验:订单簿的更新频率极高,尤其在快市时。建议用 dict 或 OrderedDict,别用列表遍历,否则性能会崩。我曾经用列表写过一版,回测时跑得飞快,一上实盘就卡死……嗯,血的教训。
4.2 深度图绘制:把数字变成视觉
有了订单簿,下一步就是画深度图。深度图本质上是一个累计曲线:从最优价格开始,往两边累加挂单量。
举个例子:买一价 10.00 元挂了 100 手,买二价 9.99 元挂了 200 手。那么累计到 9.99 元时,总买单量是 300 手。卖单同理。
画图时,X 轴是价格,Y 轴是累计量。买单曲线从左到右递增,卖单曲线从右到左递增。两条曲线中间的空隙,就是「价差区间」。
import matplotlib.pyplot as plt
def plot_depth(bids, asks):
# bids: [(price, size), ...] 已排序
# asks: [(price, size), ...] 已排序
bid_prices = [p for p, s in bids]
bid_cum = []
total = 0
for p, s in bids:
total += s
bid_cum.append(total)
ask_prices = [p for p, s in asks]
ask_cum = []
total = 0
for p, s in asks:
total += s
ask_cum.append(total)
plt.step(bid_prices, bid_cum, where='post', label='Bids')
plt.step(ask_prices, ask_cum, where='post', label='Asks')
plt.xlabel('Price')
plt.ylabel('Cumulative Volume')
plt.legend()
plt.show()
这里要注意:where='post' 表示阶梯线在价格点之后跳变,更符合订单簿的「挂单」逻辑。我之前用默认的 where='pre',画出来的图总是差一个档位,找了半天 bug……
避坑指南:深度图的 Y 轴刻度可能非常大(比如比特币深度动辄几千个 BTC)。建议使用对数刻度或归一化处理,否则图形会「压扁」在底部。我曾经画过一个深度图,买单曲线几乎成了一条水平线,因为卖单量是买单的 100 倍……
4.3 加权深度计算:量化流动性
深度图是直观的,但我们需要一个数值来量化「深度」。加权深度就是干这个的。
最简单的加权深度公式:
加权深度 = Σ (价格 × 数量) / Σ 数量
但实际中,我们更关心「在某个价格范围内,吃掉多少量才能把价格推到目标位置」。比如,我想把价格从 10.00 推到 10.05,需要吃掉多少卖单?
这个计算其实就是对卖单做累计,直到累计量对应的价格达到目标价。
def weighted_depth(asks, target_price):
# asks: [(price, size), ...] 已排序
total_volume = 0
total_value = 0
for price, size in asks:
if price > target_price:
break
total_volume += size
total_value += price * size
return total_value / total_volume if total_volume > 0 else 0
这个值有什么用?我举个例子:假设加权深度是 10.02 元,而当前卖一价是 10.00 元。说明平均下来,你要花 10.02 元才能买到足够的量。这个「溢价」就是市场深度的直接体现。
关键指标:加权深度与当前价的差值,可以衡量「滑点成本」。差值越大,流动性越差。
我个人习惯同时计算「买侧加权深度」和「卖侧加权深度」,然后取两者的平均值作为整体深度指标。如果两侧差距很大,说明市场方向性偏斜——比如卖侧深度远大于买侧,可能有大户在压盘。
4.4 知识体系总览
下面这张图,是我自己总结的 Level 2 深度分析流程。你可以把它当作一个检查清单:
这张图的核心逻辑是:原始数据 → 订单簿 → 两个分支(深度图 + 加权深度)→ 最终决策。你写代码的时候,也可以按这个顺序来组织模块。
4.5 实战中的几个坑
最后,分享几个我在实盘里踩过的坑:
- 数据延迟:Level 2 数据量很大,如果处理速度跟不上,订单簿会「滞后」。我建议用异步 I/O 或多线程,别在主线程里做计算。
- 价格精度:有些交易所的价格是浮点数,但累计计算时浮点误差会放大。我习惯把所有价格乘以 10000 转成整数再算。
- 快市时的数据风暴:行情剧烈波动时,每秒可能有上千笔增量。如果逐笔更新,CPU 会爆。可以改成「定时快照」+「增量合并」的方式。
一个小技巧:加权深度不要只看当前时刻,可以计算过去 N 秒的平均值。比如 10 秒平均加权深度,能过滤掉瞬间的大单干扰。我自己的策略里,这个参数设为 5 秒效果最好。
好了,关于市场深度分析的核心内容就这些。记住:订单簿是基础,深度图是视觉,加权深度是量化。三者结合,你才能看清市场的真实流动性。