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