3. 特征工程(上):价格衍生特征与成交量特征

大家好,欢迎来到特征工程部分。

说实话,在订单簿预测这个领域,模型选得再好,特征不行也是白搭。我见过太多人一上来就堆LSTM、Transformer,结果效果还不如一个简单的XGBoost加几个手工特征。为什么?因为原始订单簿数据太「原始」了,你得帮模型把信息提炼出来。

今天我们就来聊聊价格和成交量这两大类特征。我会把我在实盘项目中踩过的坑、总结的经验都抖出来。

核心观点:特征工程不是越多越好,而是越「有效」越好。一个高质量的特征,往往比十个垃圾特征更有价值。

订单簿特征工程 价格衍生特征 价差 (Spread) 加权价格 (VWAP) 价格梯度 成交量特征 成交量加权 成交量分布 订单不平衡 输入:原始订单簿 → 输出:结构化特征

3.1 价格衍生特征

价格是订单簿最直观的信息。但原始价格本身其实没什么预测能力——你得把它「加工」一下。

3.1.1 价差(Spread)

价差就是卖一价减去买一价。简单吧?但它的信息量可不小。

def calc_spread(best_ask, best_bid):
    """计算买卖价差"""
    return best_ask - best_bid

# 示例
best_ask = 100.05
best_bid = 100.00
spread = calc_spread(best_ask, best_bid)
print(f"当前价差: {spread:.4f}")  # 0.05

我个人习惯把价差分成几类:

  • 绝对价差:就是上面那个减法,单位是价格单位
  • 相对价差:绝对价差 / 中间价,无量纲,方便跨品种比较
  • 对数价差:log(卖一) - log(买一),在统计建模中更常用

我的经验:在实盘交易中,价差突然扩大往往意味着流动性枯竭或者有大事件发生。我曾经在回测时忽略了这个信号,结果模型在极端行情下亏得很惨。后来我把价差的Z-score作为一个风险控制特征加进去,效果立竿见影。

3.1.2 加权价格

加权价格有很多种,最常用的是成交量加权平均价格(VWAP)。

def calc_vwap(price_levels, volume_levels):
    """
    计算成交量加权平均价格
    price_levels: 价格列表 [p1, p2, ..., pn]
    volume_levels: 对应成交量 [v1, v2, ..., vn]
    """
    total_volume = sum(volume_levels)
    if total_volume == 0:
        return 0
    vwap = sum(p * v for p, v in zip(price_levels, volume_levels)) / total_volume
    return vwap

# 示例:计算买盘前5档的VWAP
bid_prices = [100.00, 99.98, 99.95, 99.92, 99.88]
bid_volumes = [1000, 800, 600, 400, 200]
vwap_bid = calc_vwap(bid_prices, bid_volumes)
print(f"买盘VWAP: {vwap_bid:.4f}")

除了VWAP,还有中间价(Mid Price)、微价(Micro Price)等。微价是我比较喜欢的一个特征,它把买卖双方的成交量都考虑进去了:

def calc_micro_price(best_bid, best_ask, bid_vol, ask_vol):
    """微价 = (bid_vol * ask_price + ask_vol * bid_price) / (bid_vol + ask_vol)"""
    if bid_vol + ask_vol == 0:
        return (best_bid + best_ask) / 2
    micro = (bid_vol * best_ask + ask_vol * best_bid) / (bid_vol + ask_vol)
    return micro

你想想看,微价其实反映了「如果现在成交,价格会偏向哪边」。当买盘力量强时,微价会靠近卖一价;反之则靠近买一价。这个特征在预测短期价格方向时特别有用。

3.1.3 价格梯度

价格梯度描述的是价格随档位变化的模式。说白了,就是看价格在深度上的「形状」。

def calc_price_gradient(prices, volumes):
    """
    计算价格梯度:相邻档位的价格变化率
    返回每个档位的梯度值
    """
    gradients = []
    for i in range(1, len(prices)):
        # 价格变化率
        grad = (prices[i] - prices[i-1]) / prices[i-1]
        # 可以用成交量加权
        # grad *= volumes[i] / sum(volumes)
        gradients.append(grad)
    return gradients

注意:价格梯度对数据质量要求很高。我曾经遇到过交易所的数据偶尔出现价格跳跃(比如某档价格突然偏离正常范围),如果不做预处理,梯度值会爆炸。建议先做中位数滤波或者截断处理。

我个人喜欢把价格梯度分成「买盘梯度」和「卖盘梯度」两个特征,然后计算它们的差值。这个差值能反映买卖双方的价格「陡峭程度」差异,某种程度上可以看作市场压力的一个代理指标。

3.2 成交量特征

成交量是订单簿的另一条腿。光看价格不看量,就像只看地图不看路况——容易翻车。

3.2.1 成交量加权

前面我们已经在VWAP里用到了成交量加权。但成交量加权本身也可以作为独立特征。

def calc_weighted_volume(volumes, weights='linear'):
    """
    计算加权成交量
    weights: 'linear' 线性加权, 'exponential' 指数加权
    """
    n = len(volumes)
    if weights == 'linear':
        w = list(range(n, 0, -1))  # 越近的档位权重越大
    elif weights == 'exponential':
        alpha = 0.8
        w = [alpha ** i for i in range(n)]
    else:
        w = [1] * n
    
    weighted_vol = sum(v * w[i] for i, v in enumerate(volumes)) / sum(w)
    return weighted_vol

为什么要加权?因为不同档位的成交量「含金量」不一样。第一档的成交量比第十档重要得多。线性加权是最简单的做法,但我建议试试指数加权——它更符合市场微观结构的直觉。

3.2.2 成交量分布

成交量分布描述的是成交量在不同价格档位上的分配情况。常用的指标有:

  • 集中度:前几档成交量占总成交量的比例。比如前3档占比超过80%,说明流动性高度集中。
  • 偏度:成交量分布是否偏向某一侧。买盘集中还是卖盘集中?
  • 峰度:分布是尖峰还是平坦?尖峰意味着某个价格档位有大量挂单。
def calc_volume_concentration(volumes, top_k=3):
    """计算前k档成交量集中度"""
    total = sum(volumes)
    if total == 0:
        return 0
    top_vol = sum(sorted(volumes, reverse=True)[:top_k])
    return top_vol / total

def calc_volume_skew(bid_volumes, ask_volumes):
    """计算成交量偏度:正数表示买盘集中,负数表示卖盘集中"""
    bid_total = sum(bid_volumes)
    ask_total = sum(ask_volumes)
    if bid_total + ask_total == 0:
        return 0
    return (bid_total - ask_total) / (bid_total + ask_total)

关键洞察:成交量分布的变化往往先于价格变化。我记得有一次在回测中发现,当买盘集中度突然下降而卖盘集中度上升时,接下来3-5个tick价格大概率会下跌。这个信号比单纯看价格变化早了大约2个tick——在高频交易中,2个tick可能就是真金白银。

3.2.3 订单不平衡

订单不平衡(Order Imbalance)是我最常用的成交量特征之一。它衡量的是买卖双方的力量对比。

def calc_order_imbalance(bid_volumes, ask_volumes, depth=5):
    """
    计算订单不平衡指标
    正值表示买方力量强,负值表示卖方力量强
    """
    bid_vol = sum(bid_volumes[:depth])
    ask_vol = sum(ask_volumes[:depth])
    
    if bid_vol + ask_vol == 0:
        return 0
    
    imbalance = (bid_vol - ask_vol) / (bid_vol + ask_vol)
    return imbalance

这个值在-1到1之间。0表示买卖均衡,接近1表示买方绝对主导,接近-1表示卖方绝对主导。

我建议不要只用一档的订单不平衡,而是计算多档的(比如前3档、前5档、前10档),然后观察它们的变化趋势。有时候前3档显示买方强,但前10档显示卖方强——这说明买方只是在浅层挂单,深层其实是卖方在埋伏。这种「表里不一」的情况往往预示着反转。

避坑指南:我曾经犯过一个错误——直接用原始成交量计算订单不平衡。后来发现,不同时间段的成交量量级差异很大(比如开盘和收盘时成交量明显更大),导致不平衡指标在不同时间段不可比。解决方案是:先对成交量做标准化(比如除以滚动均值),再计算不平衡。这样得到的特征更稳定。

3.3 特征组合与实战建议

单个特征的力量有限,真正厉害的是特征组合。我常用的几个组合:

组合名称 特征构成 预测目标 我的使用场景
价差-不平衡组合 价差 + 订单不平衡 短期价格方向 价差缩小 + 买方不平衡 → 上涨概率大
梯度-集中度组合 价格梯度 + 成交量集中度 流动性变化 梯度变陡 + 集中度上升 → 流动性恶化
VWAP-微价组合 VWAP + 微价 + 中间价 价格锚点 三者偏离度作为均值回归信号

最后说一句:特征工程没有银弹。同样的特征,在不同的市场、不同的品种、不同的时间尺度下,效果可能天差地别。我的建议是:先做一批基础特征,然后通过特征重要性分析(比如用随机森林或XGBoost的特征重要性排序)来筛选,再迭代优化。

嗯,今天就先聊到这里。这些特征足够你搭建一个不错的基线模型了。下一节我们会继续聊时间序列特征和统计特征,到时候再给大家分享更多实战技巧。


公众号:蓝海资料掘金营,微信deep3321