12、订单簿特征工程:从订单簿提取特征、归一化、时序特征、截面特征

做量化交易这几年,我越来越觉得一句话是真理:特征决定上限,模型只是逼近这个上限。订单簿数据,说白了就是市场最原始的博弈记录——每一笔挂单、每一次撤单,都是真金白银投票的结果。但原始数据太糙了,没法直接用。你得把它「洗」成模型能看懂的特征。

这一章,我就带你走一遍订单簿特征工程的完整流程。从原始数据到归一化,从时序特征到截面特征,咱们一步步来。

核心思路: 原始订单簿 → 提取基础特征 → 归一化处理 → 构建时序特征 → 构建截面特征 → 输入模型
原始订单簿 买一卖一 · 深度 · 价差 基础特征提取 WAP · 失衡 · 斜率 归一化处理 Z-score · Min-Max 时序特征 滚动均值 · 差分 · 波动率 截面特征 多档位 · 斜率 · 聚类 特征矩阵 → 模型 特征工程流程

12.1 从原始订单簿提取基础特征

原始订单簿长什么样?说白了就是一张大表,记录着每个价格档位的挂单量和挂单方向。但直接拿这些数字喂给模型,效果很差。为什么?因为价格本身不包含信息,真正有价值的是相对关系

我个人习惯,第一步先算这几个基础特征:

  • 加权平均价格(WAP):买一价和卖一价的加权平均,反映当前公允价格。公式很简单:WAP = (bid_price * ask_size + ask_price * bid_size) / (bid_size + ask_size)
  • 订单簿失衡(Order Imbalance):衡量买卖双方的力量对比。Imbalance = (bid_volume - ask_volume) / (bid_volume + ask_volume)。正值表示买方强势,负值表示卖方强势。
  • 价差(Spread):卖一价减去买一价。价差越小,流动性越好。
  • 深度(Depth):前N档的总挂单量。我一般取前5档,太深了噪声大。
小技巧: 我在项目中遇到过,直接用原始价格做特征,模型很容易过拟合。后来我把所有价格都转换成相对于WAP的偏移量,效果好了很多。你想想看,价格涨跌是相对的,不是绝对的。
# 基础特征提取示例(Python伪代码)
def extract_basic_features(orderbook):
    bid_prices = orderbook['bid_prices']
    ask_prices = orderbook['ask_prices']
    bid_sizes = orderbook['bid_sizes']
    ask_sizes = orderbook['ask_sizes']
    
    # 加权平均价格
    wap = (bid_prices[0] * ask_sizes[0] + ask_prices[0] * bid_sizes[0]) / (bid_sizes[0] + ask_sizes[0])
    
    # 订单簿失衡
    total_bid = sum(bid_sizes[:5])
    total_ask = sum(ask_sizes[:5])
    imbalance = (total_bid - total_ask) / (total_bid + total_ask)
    
    # 价差
    spread = ask_prices[0] - bid_prices[0]
    
    # 深度
    depth_bid = sum(bid_sizes[:5])
    depth_ask = sum(ask_sizes[:5])
    
    return {'wap': wap, 'imbalance': imbalance, 'spread': spread, 'depth_bid': depth_bid, 'depth_ask': depth_ask}

12.2 归一化:让特征站在同一起跑线

特征提取完了,你会发现一个问题:价差可能是0.01,而深度可能是10000。量纲差了好几个数量级。如果不做归一化,模型会天然地认为「深度」比「价差」重要——这显然是错的。

我常用的归一化方法有两种:

方法 公式 适用场景
Z-score标准化 x' = (x - μ) / σ 特征分布近似正态,或存在异常值
Min-Max归一化 x' = (x - min) / (max - min) 特征有明确边界,如比例类特征
注意: 我曾经犯过一个错误——对整个数据集做归一化,导致数据泄露。正确的做法是:用训练集的统计量(μ、σ、min、max)去归一化训练集和测试集。千万别用测试集的信息去算归一化参数!

对于订单簿特征,我个人更推荐Z-score。因为订单簿数据经常有极端行情(比如闪崩),Min-Max会被这些极端值拉偏。Z-score对异常值相对鲁棒一些。

12.3 时序特征:捕捉市场动态

单点的特征只能告诉你「现在怎么样」,但交易决策更需要知道「趋势怎么样」。这就是时序特征的价值。

我常用的时序特征包括:

  • 滚动均值(Rolling Mean):过去N个时间步的WAP均值。比如5步均值、20步均值。反映短期和中期趋势。
  • 差分(Difference):当前值减去前一个值。说白了就是变化量。WAP的差分就是价格变动。
  • 波动率(Volatility):过去N步的WAP标准差。波动率越大,市场越不稳定。
  • 动量(Momentum):当前WAP与N步前WAP的比值。大于1表示上涨,小于1表示下跌。
# 时序特征构建示例
def build_time_series_features(df, window=10):
    df['wap_ma'] = df['wap'].rolling(window=window).mean()
    df['wap_diff'] = df['wap'].diff()
    df['wap_volatility'] = df['wap'].rolling(window=window).std()
    df['momentum'] = df['wap'] / df['wap'].shift(window)
    return df

嗯,这里要注意:滚动窗口的大小怎么选?没有标准答案。我个人习惯用5、10、20、50这几个窗口,分别对应秒级、分钟级、5分钟级和15分钟级的交易节奏。具体用哪个,得看你的交易频率。

12.4 截面特征:多档位信息融合

时序特征看的是「时间轴」,截面特征看的是「价格轴」。订单簿有多个档位,每个档位都藏着信息。截面特征就是把这些信息融合起来。

我常用的截面特征:

  • 价格斜率(Price Slope):对前N档的价格做线性回归,斜率反映了价格随档位变化的趋势。斜率越大,说明深度越浅。
  • 量能分布(Volume Distribution):前N档挂单量的分布特征,比如集中度、偏度。集中度高的订单簿,容易被大单击穿。
  • 累积深度曲线(Cumulative Depth Curve):从买一/卖一开始,累积挂单量随价格偏移的变化。这个曲线能反映市场的「承压能力」。
  • 多档位失衡(Multi-level Imbalance):不只是看买一卖一,而是看前N档的累积失衡。比如前3档失衡、前5档失衡。
实战经验: 我记得有一次做高频策略,发现只用买一卖一的失衡特征,策略经常被「假突破」骗进去。后来加入了前5档的累积失衡特征,过滤掉了大量虚假信号。说白了,大资金的动作往往会在多个档位留下痕迹,只看表面容易被骗。
# 截面特征构建示例
def build_cross_sectional_features(orderbook, levels=5):
    bid_prices = orderbook['bid_prices'][:levels]
    ask_prices = orderbook['ask_prices'][:levels]
    bid_sizes = orderbook['bid_sizes'][:levels]
    ask_sizes = orderbook['ask_sizes'][:levels]
    
    # 价格斜率(对档位做线性回归)
    x = np.arange(levels)
    slope_bid = np.polyfit(x, bid_prices, 1)[0]
    slope_ask = np.polyfit(x, ask_prices, 1)[0]
    
    # 累积深度
    cum_bid = np.cumsum(bid_sizes)
    cum_ask = np.cumsum(ask_sizes)
    
    # 多档位失衡
    imbalance_3 = (sum(bid_sizes[:3]) - sum(ask_sizes[:3])) / (sum(bid_sizes[:3]) + sum(ask_sizes[:3]))
    imbalance_5 = (sum(bid_sizes[:5]) - sum(ask_sizes[:5])) / (sum(bid_sizes[:5]) + sum(ask_sizes[:5]))
    
    return {'slope_bid': slope_bid, 'slope_ask': slope_ask, 
            'cum_bid': cum_bid, 'cum_ask': cum_ask,
            'imbalance_3': imbalance_3, 'imbalance_5': imbalance_5}

12.5 特征组合与筛选

特征造了一大堆,但并不是越多越好。我见过有人一口气造了200多个特征,结果模型训练时间翻了好几倍,效果反而下降了。这就是典型的「维度灾难」。

我的做法是:

  1. 先粗筛:计算每个特征与目标变量的相关性,去掉相关性低于0.05的特征。
  2. 再精筛:用随机森林或XGBoost的特征重要性排序,保留Top 30-50个特征。
  3. 最后做PCA:如果特征之间相关性太高(比如多个滚动均值),用PCA降维到20维左右。
避坑指南: 我曾经一股脑把所有时序特征和截面特征拼在一起,结果模型在回测里表现完美,实盘却一塌糊涂。后来发现,有些时序特征和截面特征高度相关(比如WAP的滚动均值和价格斜率),造成了严重的多重共线性。所以,特征组合后一定要检查相关性矩阵。

好了,特征工程这块就聊到这儿。记住一句话:好的特征工程,胜过十个调参大师。下一章咱们会把这些特征喂进模型里,看看实际效果如何。


交易系统化学习资料 微信Strategy888888