4、特征工程基础:价格衍生特征、成交量特征与时间序列特征

各位同学好,我是你们的讲师。今天咱们聊聊特征工程里最基础、也最核心的一块——价格和成交量怎么变成模型能吃的“粮食”。

说实话,我见过不少新手一上来就扔一堆原始数据给模型,结果跑出来一团糟。为什么?因为原始价格序列本身噪声太大,模型根本抓不住规律。你想想看,直接拿收盘价去预测,跟拿一堆随机数去预测,区别真不大。

所以,特征工程的第一步,就是把原始数据“翻译”成有意义的信号。今天咱们就拆开揉碎了讲。

核心观点:好的特征 = 对市场微观结构的理解 + 对噪声的合理过滤。别指望模型自己会“悟”出规律,你得帮它一把。

特征工程三大支柱 价格衍生特征 收益率 · 波动率 · 价格比率 成交量特征 VWAP · 成交量分布 · 资金流 时间序列特征 滞后项 · 滚动统计量 · 差分 简单收益率 对数收益率 历史波动率 VWAP 成交量分布 滞后项 滚动统计量 差分/趋势 目标:将原始Tick/Bar数据转化为稳定、可预测的模型输入

4.1 价格衍生特征:收益率与波动率

价格本身是非平稳的。什么意思?就是今天的100块和明天的100块,意义完全不同。所以咱们得先做差分,把价格变成收益率。

4.1.1 收益率计算

收益率分两种:简单收益率和对数收益率。我个人习惯用对数收益率,尤其是在高频场景下。

为什么?因为对数收益率具有可加性,而且分布更接近正态。你想想看,连续复利假设下,对数收益率天然就是正态的,这对很多模型来说太友好了。

import numpy as np
import pandas as pd

# 假设我们有收盘价序列
prices = pd.Series([100.0, 101.5, 102.3, 101.8, 103.2])

# 简单收益率
simple_returns = prices.pct_change()
print("简单收益率:")
print(simple_returns)

# 对数收益率(我常用的)
log_returns = np.log(prices / prices.shift(1))
print("\n对数收益率:")
print(log_returns)

我的经验:在回测时,用对数收益率计算组合收益更准确。因为简单收益率在时间上不可加,你算多期收益时容易出错。我曾经因为这个坑,回测结果和实盘差了0.5%,排查了两天才发现是收益率计算方式的问题。

4.1.2 波动率特征

波动率是高频交易里最重要的风险指标之一。我一般会算三种:

  • 历史波动率:过去N期收益率的标准差。简单粗暴,但有效。
  • 已实现波动率:日内高频收益率的平方和。这个更精细,适合捕捉日内波动。
  • 波动率锥:不同时间尺度下的波动率对比。用来判断当前波动是“异常”还是“正常”。
# 计算滚动波动率(窗口20)
window = 20
rolling_vol = log_returns.rolling(window).std() * np.sqrt(252)  # 年化
print("年化波动率:")
print(rolling_vol)

注意:波动率计算时,窗口大小的选择很关键。窗口太小,噪声大;窗口太大,反应迟钝。我一般先用5分钟数据试,再根据策略调参。没有银弹,多试。

4.2 成交量特征:VWAP与成交量分布

价格告诉你“值多少钱”,成交量告诉你“有多少人参与”。两者结合,才能看清市场的真实意图。

4.2.1 VWAP(成交量加权平均价格)

VWAP是机构交易者最常用的基准之一。说白了,就是按成交量加权的平均价格。如果当前价格远高于VWAP,说明买方力量强;反之亦然。

# 假设我们有每笔交易数据
df = pd.DataFrame({
    'price': [100.1, 100.3, 100.2, 100.5, 100.4],
    'volume': [1000, 1500, 800, 2000, 1200]
})

# 计算VWAP
df['dollar_volume'] = df['price'] * df['volume']
vwap = df['dollar_volume'].sum() / df['volume'].sum()
print(f"VWAP: {vwap:.2f}")

实战技巧:我习惯把VWAP和价格放在一起看。如果价格突破VWAP且成交量放大,这是个强信号。如果价格在VWAP附近反复震荡,说明市场在犹豫,这时候别急着进场。

4.2.2 成交量分布

成交量分布,就是看成交量在价格区间上的分布情况。哪些价格区间成交最活跃?哪些是“真空区”?

我记得有一次做期货策略,发现某个品种在开盘后前30分钟成交量特别集中,但价格波动不大。后来分析成交量分布才发现,那是做市商在“铺单”,根本不是趋势信号。从那以后,我每次做特征工程都会先画成交量分布图。

# 简单的成交量分布计算
price_bins = pd.cut(df['price'], bins=5)
volume_profile = df.groupby(price_bins)['volume'].sum()
print("成交量分布:")
print(volume_profile)

4.3 时间序列特征:滞后项与滚动统计量

时间序列数据有个特点:过去会影响未来。所以我们需要把历史信息“喂”给模型。

4.3.1 滞后项(Lag Features)

滞后项就是把过去N期的值作为当前的特征。比如,用过去5分钟的收益率来预测下一分钟。

# 创建滞后特征
for lag in [1, 2, 3, 5, 10]:
    df[f'return_lag_{lag}'] = log_returns.shift(lag)

print("滞后特征示例:")
print(df.head())

避坑指南:我曾经一次性加了50个滞后项,结果模型过拟合得一塌糊涂。后来我学乖了:先用ACF/PACF图看相关性衰减速度,只选那些真正有相关性的滞后阶数。一般高频数据,滞后3-5期就够了,再多就是噪声。

4.3.2 滚动统计量

滚动统计量包括滚动均值、滚动标准差、滚动最大值/最小值等。它们能捕捉到局部趋势和波动变化。

# 滚动统计量
window = 10
df['rolling_mean'] = log_returns.rolling(window).mean()
df['rolling_std'] = log_returns.rolling(window).std()
df['rolling_max'] = log_returns.rolling(window).max()
df['rolling_min'] = log_returns.rolling(window).min()

print("滚动统计量:")
print(df[['rolling_mean', 'rolling_std']].head(15))

这里有个细节:滚动窗口的大小怎么选?我个人的经验是,先看数据的采样频率。如果是1分钟数据,窗口20(约20分钟)是个不错的起点。如果是Tick级数据,窗口可以设到100甚至更高。但记住,窗口越大,特征越平滑,但也越滞后。

4.3.3 差分与趋势特征

除了滞后项和滚动统计量,我还喜欢加一些趋势特征。比如:

  • 价格差分:当前价格与N期前价格的差值。
  • 趋势强度:用线性回归拟合最近N期的斜率,作为趋势方向的特征。
  • 加速度:收益率的二阶差分,反映趋势变化的速度。
# 趋势强度(用滚动回归计算斜率)
def calc_slope(series):
    x = np.arange(len(series))
    slope, _ = np.polyfit(x, series, 1)
    return slope

df['trend_slope'] = log_returns.rolling(window).apply(calc_slope, raw=False)
print("趋势斜率:")
print(df['trend_slope'].dropna().head())

总结一下:特征工程不是越多越好,而是越“对”越好。我见过有人堆了200个特征,结果模型还不如只用10个精心挑选的特征。记住三个原则:

  1. 相关性:特征和目标变量要有逻辑关联。
  2. 稳定性:特征的统计性质要随时间保持稳定。
  3. 可解释性:你至少得知道这个特征在描述什么市场行为。

好了,今天的内容就到这里。这些特征看起来简单,但用好它们,你的模型就已经赢在起跑线上了。下次咱们聊聊怎么把这些特征组合起来,构建一个完整的特征矩阵。

专注资料整理