3、订单簿特征构建:订单簿不平衡、订单簿斜率、加权买卖价差、订单簿压力指标
好,咱们进入正题。订单簿特征,说白了就是把交易所那一堆挂单数据,变成能用的量化信号。我刚开始做高频策略的时候,也犯过傻——直接把十档行情扔进模型,结果过拟合得一塌糊涂。后来才明白,关键不在于数据多,而在于特征精。
这一节,我带你手撸四个核心特征:订单簿不平衡、订单簿斜率、加权买卖价差、订单簿压力指标。每个我都会讲清楚数学逻辑,再给Python实现。嗯,代码都是我在实盘环境里打磨过的,你直接拿去用问题不大。
3.1 订单簿不平衡(Order Book Imbalance)
这个特征最简单,也最实用。它衡量的是买方和卖方的力量对比。你想想看,如果买一档挂了1000手,卖一档只有100手,那价格往上走的概率是不是更大?
公式其实很直观:
imbalance = (bid_volume - ask_volume) / (bid_volume + ask_volume)
取值范围在-1到1之间。正数表示买方占优,负数表示卖方占优。绝对值越大,单边力量越强。
我在项目中遇到过一个问题:只用第一档算出来的imbalance太毛躁了。后来我改成用前五档加权,效果好了不少。权重怎么给?我个人习惯用指数衰减,离当前价格越近的档位权重越大。
代码实现如下:
def compute_imbalance(bids, asks, levels=5, decay=0.8):
"""
bids: [(price, volume), ...] 买盘列表,已按价格降序排列
asks: [(price, volume), ...] 卖盘列表,已按价格升序排列
"""
bid_vol = 0
ask_vol = 0
weight = 1.0
for i in range(min(levels, len(bids))):
bid_vol += bids[i][1] * weight
weight *= decay
weight = 1.0
for i in range(min(levels, len(asks))):
ask_vol += asks[i][1] * weight
weight *= decay
if bid_vol + ask_vol == 0:
return 0.0
return (bid_vol - ask_vol) / (bid_vol + ask_vol)
3.2 订单簿斜率(Order Book Slope)
斜率这个概念,其实是从经济学里的需求曲线借过来的。它描述的是:价格每变动一个单位,挂单量会变化多少。说白了,就是市场的「弹性」。
计算方式也不复杂。我们把买盘和卖盘分别拟合一条直线,斜率就是这条线的系数。具体来说:
- 对买盘:价格从低到高,累计挂单量从大到小。拟合价格与累计量的关系。
- 对卖盘:价格从低到高,累计挂单量从小到大。同样拟合。
- 斜率绝对值越大,说明这个价位附近的流动性越集中。
我曾经犯过一个错:直接用原始挂单量去拟合,结果噪声特别大。后来改成用累计挂单量,曲线平滑多了。你想想看,原始挂单量可能某一档突然冒出一个大单,但累计量就不会被这种单点异常带偏。
import numpy as np
def compute_slope(bids, asks, levels=10):
# 买盘:价格升序,累计量递减
bid_prices = np.array([b[0] for b in bids[:levels]])
bid_cumvol = np.cumsum([b[1] for b in bids[:levels]][::-1])[::-1]
# 卖盘:价格升序,累计量递增
ask_prices = np.array([a[0] for a in asks[:levels]])
ask_cumvol = np.cumsum([a[1] for a in asks[:levels]])
# 线性拟合
bid_slope = np.polyfit(bid_prices, bid_cumvol, 1)[0]
ask_slope = np.polyfit(ask_prices, ask_cumvol, 1)[0]
return bid_slope, ask_slope
3.3 加权买卖价差(Weighted Spread)
传统的买卖价差就是卖一价减买一价。但这里有个问题:如果买一只有1手,卖一有1000手,这个价差能真实反映交易成本吗?显然不能。
加权买卖价差就是来解决这个问题的。它把每一档的挂单量作为权重,算出一个「等效」的价差。公式如下:
weighted_spread = (sum(ask_price_i * ask_vol_i) / sum(ask_vol_i))
- (sum(bid_price_i * bid_vol_i) / sum(bid_vol_i))
说白了,就是买卖双方的加权平均价格之差。这个指标比简单价差更稳健。我记得有一次做回测,简单价差策略亏得一塌糊涂,换成加权价差后立马扭亏为盈。原因就是加权价差过滤掉了那些「虚挂」的单子。
def weighted_spread(bids, asks, levels=5):
bid_wavg_price = sum(b[0] * b[1] for b in bids[:levels]) / sum(b[1] for b in bids[:levels])
ask_wavg_price = sum(a[0] * a[1] for a in asks[:levels]) / sum(a[1] for a in asks[:levels])
return ask_wavg_price - bid_wavg_price
3.4 订单簿压力指标(Order Book Pressure)
这个指标是我个人比较偏爱的。它综合了价格和量两个维度,算出一个「压力值」。具体做法是:
- 把买盘和卖盘分别按价格分成几个区间(比如每0.1%一个区间)。
- 每个区间内,计算挂单总量。
- 用距离当前价格的远近做权重,越近权重越大。
- 最终压力 = 买方加权总量 - 卖方加权总量。
为什么要这么做?因为单纯看imbalance,它只考虑了总量,没考虑分布。而压力指标能告诉你:买方力量是集中在眼前,还是分散在远处。这两者的市场含义完全不同。
举个例子:如果买方压力集中在买一买二,说明多头很急,想立刻成交。如果买方压力分散在买五买六,说明多头只是「挂个单试试」,并不着急。这两种情况,对价格的预测意义截然不同。
def order_book_pressure(bids, asks, price_bins=10, bin_size=0.001):
"""
price_bins: 分多少个区间
bin_size: 每个区间的价格宽度(比例)
"""
mid_price = (bids[0][0] + asks[0][0]) / 2
bid_pressure = 0
for price, vol in bids:
dist = (mid_price - price) / mid_price
bin_idx = int(dist / bin_size)
if bin_idx < price_bins:
weight = 1.0 / (bin_idx + 1) # 距离越近权重越大
bid_pressure += vol * weight
ask_pressure = 0
for price, vol in asks:
dist = (price - mid_price) / mid_price
bin_idx = int(dist / bin_size)
if bin_idx < price_bins:
weight = 1.0 / (bin_idx + 1)
ask_pressure += vol * weight
return bid_pressure - ask_pressure
3.5 四个特征的综合运用
单独用任何一个特征,效果都有限。真正的价值在于组合。我一般会把这四个特征加上它们的滚动均值、滚动标准差,一起喂给模型。比如:
| 特征名称 | 原始值 | 滚动均值(20笔) | 滚动标准差(20笔) |
|---|---|---|---|
| 订单簿不平衡 | 0.32 | 0.15 | 0.28 |
| 订单簿斜率(买) | -1.2e4 | -1.1e4 | 3.2e3 |
| 加权买卖价差 | 0.021 | 0.019 | 0.005 |
| 订单簿压力 | 450 | 320 | 210 |
你看,光看原始值可能看不出什么,但结合滚动统计量,就能发现一些规律。比如不平衡的滚动标准差突然变大,说明市场分歧在加剧——这时候往往是大行情的前兆。
好了,这四个特征就讲完了。代码你直接拿去用,但记得根据你的交易品种和频率调整参数。嗯,实盘之前一定要回测,别偷懒。
公众号:蓝海资料掘金营,微信deep3321