3. 特征工程(上):价格衍生特征与成交量特征
大家好,欢迎来到特征工程部分。
说实话,在订单簿预测这个领域,模型选得再好,特征不行也是白搭。我见过太多人一上来就堆LSTM、Transformer,结果效果还不如一个简单的XGBoost加几个手工特征。为什么?因为原始订单簿数据太「原始」了,你得帮模型把信息提炼出来。
今天我们就来聊聊价格和成交量这两大类特征。我会把我在实盘项目中踩过的坑、总结的经验都抖出来。
核心观点:特征工程不是越多越好,而是越「有效」越好。一个高质量的特征,往往比十个垃圾特征更有价值。
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