3. 订单簿特征工程:买卖价差、订单簿深度、订单簿斜率
好,咱们进入第三章。这一章我打算聊聊订单簿的特征工程。说白了,就是把原始的订单簿数据,加工成能直接喂给模型的信号。
我个人习惯把订单簿特征分成三类:价差类、深度类、斜率类。这三类特征,几乎覆盖了90%的微观结构信号需求。你想想看,做高频交易也好,做算法交易也罢,最终看的无非就是三个东西:交易成本、市场厚度、价格弹性。正好对应这三个特征。
3.1 买卖价差:最直接的流动性成本
买卖价差,英文叫Bid-Ask Spread。这是最直观的流动性指标。价差越小,说明市场越活跃,流动性越好。
绝对价差:就是卖一价减去买一价。
spread = ask_price_1 - bid_price_1
相对价差:绝对价差除以中间价。这个更常用,因为不同价格的股票没法直接比。
mid_price = (ask_price_1 + bid_price_1) / 2
relative_spread = spread / mid_price
实战经验:我在做A股高频策略时发现,开盘前3分钟的价差波动特别大。这时候的价差信号,其实更多反映的是隔夜信息博弈,而不是真实的流动性状况。所以我一般会过滤掉开盘前5分钟的数据。
加权价差:考虑多档位。比如用买卖各5档的加权平均价来计算。
def weighted_spread(bids, asks, levels=5):
bid_wavg = sum(bids[i][0] * bids[i][1] for i in range(levels)) / sum(bids[i][1] for i in range(levels))
ask_wavg = sum(asks[i][0] * asks[i][1] for i in range(levels)) / sum(asks[i][1] for i in range(levels))
return ask_wavg - bid_wavg
小技巧:我习惯把价差序列做一阶差分。价差突然扩大,往往意味着有大单进场或者信息事件发生。这个信号比价差本身更有预测价值。
3.2 订单簿深度:市场能吃掉多少单
深度,说白了就是订单簿上各个价位的挂单量。深度越大,市场越能扛住大单冲击。
总深度:买一到买N档的总量,或者卖一到卖N档的总量。
total_bid_depth = sum(bid_volume for bid_volume in bid_volumes[:10])
total_ask_depth = sum(ask_volume for ask_volume in ask_volumes[:10])
深度不平衡:买卖深度的差值。这个指标能反映短期的供需失衡。
depth_imbalance = (total_bid_depth - total_ask_depth) / (total_bid_depth + total_ask_depth)
这个值在-1到1之间。正值说明买方力量强,负值说明卖方力量强。
避坑指南:我曾经犯过一个错误——直接用原始深度做特征。后来发现,不同股票的深度差异太大了。茅台的一档深度可能是几千万,而一些小盘股只有几十万。所以一定要做标准化处理。我一般用Z-score或者除以过去N分钟的均值。
深度衰减率:这个特征比较有意思。它衡量的是,随着档位远离中间价,挂单量下降的速度。
def depth_decay_rate(volumes):
# 用线性回归拟合log(volume) ~ level
levels = np.arange(len(volumes))
log_vol = np.log(volumes + 1)
slope, _ = np.polyfit(levels, log_vol, 1)
return slope
衰减率越陡,说明流动性越集中在最优价位。衰减率平缓,说明订单簿比较"厚实"。
3.3 订单簿斜率:价格与深度的关系
斜率这个概念,其实是从限价订单簿的"形状"衍生出来的。你想想看,如果把价格和对应的挂单量画成一条曲线,这条曲线的斜率能告诉我们什么?
简单斜率:用最优几档的价格和深度来拟合一条直线。
def order_book_slope(prices, volumes):
# 价格归一化,深度取对数
prices_norm = (prices - prices.mean()) / prices.std()
log_volumes = np.log(volumes + 1)
slope, _ = np.polyfit(prices_norm, log_volumes, 1)
return slope
买卖斜率差:买方斜率和卖方斜率的差值。这个指标能反映订单簿的"倾斜程度"。
bid_slope = order_book_slope(bid_prices, bid_volumes)
ask_slope = order_book_slope(ask_prices, ask_volumes)
slope_spread = bid_slope - ask_slope
斜率差为正,说明买方挂单更积极。为负则相反。
核心逻辑:订单簿斜率其实是在刻画"价格弹性"。斜率越大,说明价格变动一点点,挂单量就会大幅变化。这种市场通常流动性较好,但也很脆弱——一旦价格突破某个关键点位,订单簿可能会瞬间崩塌。
3.4 特征组合与实战框架
单独用某个特征,效果往往一般。我习惯把这三个特征组合起来用。
| 特征组合 | 含义 | 典型场景 |
|---|---|---|
| 价差小 + 深度大 + 斜率陡 | 高流动性,市场健康 | 适合做高频做市 |
| 价差大 + 深度小 + 斜率平 | 低流动性,风险较高 | 避免大单交易 |
| 价差扩大 + 深度减少 + 斜率变陡 | 流动性正在恶化 | 预警信号,考虑减仓 |
| 价差缩小 + 深度增加 + 斜率变平 | 流动性正在改善 | 可以逐步建仓 |
嗯,这里要注意一点。这些特征不是独立的。价差和深度之间就有很强的负相关。价差大的时候,深度通常也浅。所以做特征工程时,我一般会做PCA或者去相关性处理。
我的习惯:我会把这三个特征做成一个三维向量,然后计算它的模长和方向角。模长代表整体流动性水平,方向角代表流动性的结构特征。这个做法在CTA策略里效果不错。
3.5 代码实战:实时特征计算
最后,给一个完整的特征计算函数。这个函数我用了好几年,改了好几版,现在算是比较稳定的版本。
import numpy as np
from collections import deque
class OrderBookFeatures:
def __init__(self, window=100):
self.window = window
self.spread_history = deque(maxlen=window)
self.depth_history = deque(maxlen=window)
self.slope_history = deque(maxlen=window)
def compute_features(self, bids, asks):
# bids: [(price, volume), ...], asks: [(price, volume), ...]
# 1. 价差特征
best_bid = bids[0][0]
best_ask = asks[0][0]
mid_price = (best_bid + best_ask) / 2
spread = (best_ask - best_bid) / mid_price
# 2. 深度特征
bid_depth = sum(v for _, v in bids[:10])
ask_depth = sum(v for _, v in asks[:10])
depth_imbalance = (bid_depth - ask_depth) / (bid_depth + ask_depth + 1e-8)
# 3. 斜率特征
bid_prices = np.array([p for p, _ in bids[:5]])
bid_volumes = np.array([v for _, v in bids[:5]])
ask_prices = np.array([p for p, _ in asks[:5]])
ask_volumes = np.array([v for _, v in asks[:5]])
bid_slope = np.polyfit(bid_prices, np.log(bid_volumes + 1), 1)[0]
ask_slope = np.polyfit(ask_prices, np.log(ask_volumes + 1), 1)[0]
slope_diff = bid_slope - ask_slope
# 4. 更新历史
self.spread_history.append(spread)
self.depth_history.append(depth_imbalance)
self.slope_history.append(slope_diff)
# 5. 计算变化率
spread_change = spread / (np.mean(self.spread_history) + 1e-8) - 1
depth_change = depth_imbalance / (np.mean(self.depth_history) + 1e-8) - 1
return {
'spread': spread,
'depth_imbalance': depth_imbalance,
'slope_diff': slope_diff,
'spread_change': spread_change,
'depth_change': depth_change
}
这个函数返回5个特征。前三个是静态特征,后两个是动态特征。我个人觉得,动态特征比静态特征更有预测价值。因为市场参与者都在盯着静态特征,而动态特征往往能捕捉到别人没注意到的变化。
这张图把本章的知识体系串起来了。三个主特征,每个下面有4个子特征,最后组合成一个三维向量。做策略的时候,你可以根据这个框架来构建特征矩阵。
好了,这一章就到这里。特征工程这东西,说白了就是"垃圾进,垃圾出"。特征选得好,模型就成功了一半。下一章我们会聊怎么用这些特征来构建交易信号。
公众号:蓝海资料掘金营,微信deep3321