4. 价差计算与特征工程:从原始数据到有效信号
好,我们进入第四章。这一章很关键,说白了就是解决一个问题:怎么把两个品种的价格数据,变成能喂给机器学习模型的特征。
我个人习惯把这一步叫做“信号提纯”。原始价格数据里噪声太多,直接扔给模型,效果往往很差。我见过不少新手,上来就把两个价格序列拼在一起当特征,结果模型学到的全是市场噪音,回测漂亮实盘就崩。
4.1 价差计算:三种主流方式
价差是套利策略的灵魂。但怎么算价差,其实有讲究。我常用的有三种方式,每种适用场景不同。
4.1.1 简单价差
最直观的方式:价差 = 品种A价格 - 品种B价格。
举个例子,螺纹钢和热卷,两者价格水平相近,用简单价差就挺好。
# 简单价差计算
import pandas as pd
def simple_spread(price_a, price_b):
return price_a - price_b
# 示例
spread = simple_spread(rb_close, hc_close)
4.1.2 对数价差
这是我个人最常用的方式。公式是:价差 = ln(品种A价格) - ln(品种B价格)。
为什么用对数?两个原因:
- 去量纲:把绝对价格差异变成相对比例关系
- 稳定方差:对数变换能让价格序列的波动更平稳
我在做股指期货跨期套利时,就吃过简单价差的亏。当时IF和IC价差波动剧烈,用简单价差根本找不到规律。换成对数价差后,信号清晰多了。
# 对数价差计算
import numpy as np
def log_spread(price_a, price_b):
return np.log(price_a) - np.log(price_b)
# 示例
log_sp = log_spread(if_close, ic_close)
4.1.3 标准化价差
也叫Z-score价差。公式:标准化价差 = (当前价差 - 均值) / 标准差。
这个方式的好处是:把价差映射到标准正态分布上。你一眼就能看出当前价差偏离均值几个标准差,方便设置开仓阈值。
# 标准化价差计算
def zscore_spread(spread, window=20):
mean = spread.rolling(window).mean()
std = spread.rolling(window).std()
return (spread - mean) / std
# 示例
z_spread = zscore_spread(log_sp, window=30)
4.2 滚动统计特征:捕捉价差的动态变化
价差本身只是基础。真正有价值的是价差的统计特征。我习惯用滚动窗口来计算这些特征。
4.2.1 滚动均值与标准差
这两个是最基础的。滚动均值反映价差的“中枢”,滚动标准差反映价差的“波动幅度”。
# 滚动统计特征
def rolling_stats(spread, window=20):
df = pd.DataFrame()
df['spread'] = spread
df['rolling_mean'] = spread.rolling(window).mean()
df['rolling_std'] = spread.rolling(window).std()
df['zscore'] = (spread - df['rolling_mean']) / df['rolling_std']
return df
4.2.2 滚动偏度与峰度
这两个特征很多人会忽略,但我觉得它们很有用。
- 偏度:衡量价差分布是否对称。正偏度意味着价差更容易出现正向极端值
- 峰度:衡量价差分布的“厚尾”程度。高峰度意味着极端价差出现概率更高
我曾经在原油跨期套利中,发现价差峰度突然飙升,随后就出现了极端行情。这个特征帮我提前预警了好几次。
# 滚动偏度与峰度
def rolling_skew_kurt(spread, window=60):
df = pd.DataFrame()
df['skew'] = spread.rolling(window).skew()
df['kurt'] = spread.rolling(window).kurt()
return df
4.2.3 滚动分位数
分位数比均值更稳健。我常用的是25%、50%、75%分位数。
# 滚动分位数
def rolling_quantiles(spread, window=100):
df = pd.DataFrame()
df['q25'] = spread.rolling(window).quantile(0.25)
df['q50'] = spread.rolling(window).quantile(0.50)
df['q75'] = spread.rolling(window).quantile(0.75)
df['iqr'] = df['q75'] - df['q25'] # 四分位距
return df
4.3 波动率特征:衡量价差的“脾气”
波动率特征,说白了就是衡量价差爱不爱动。价差波动率低的时候,套利机会少;波动率高的时候,机会多但风险也大。
4.3.1 历史波动率
最基础的方式:用滚动窗口计算价差收益率的标准差。
# 历史波动率
def historical_volatility(spread, window=20):
log_ret = np.log(spread / spread.shift(1))
hv = log_ret.rolling(window).std() * np.sqrt(252) # 年化
return hv
4.3.2 波动率锥
这个工具我特别喜欢。它把不同时间周期的波动率放在一起比较,能看出当前波动率处于什么水平。
# 波动率锥示例
def volatility_cone(spread, windows=[5, 10, 20, 60, 120]):
log_ret = np.log(spread / spread.shift(1))
cone = {}
for w in windows:
vol = log_ret.rolling(w).std() * np.sqrt(252)
cone[f'vol_{w}d'] = vol
return pd.DataFrame(cone)
4.3.3 波动率均值回归特征
价差波动率本身也有均值回归特性。我常用的是:当前波动率与历史均值的偏离度。
# 波动率偏离度
def vol_deviation(spread, vol_window=20, mean_window=60):
hv = historical_volatility(spread, vol_window)
vol_mean = hv.rolling(mean_window).mean()
vol_std = hv.rolling(mean_window).std()
vol_z = (hv - vol_mean) / vol_std
return vol_z
4.4 特征工程框架图
下面这张图,是我做特征工程时的核心框架。你可以把它当作一个检查清单。
4.5 实战中的特征选择建议
特征不是越多越好。我总结了几条经验:
- 先做减法:从对数价差和滚动Z-score开始,这两个特征已经能解决80%的问题
- 逐步添加:如果模型表现不够好,再考虑加入偏度、峰度、波动率特征
- 注意多重共线性:滚动均值和滚动中位数高度相关,不要同时使用
- 窗口大小要合理:我一般用20、60、120三个窗口,分别对应短期、中期、长期
好了,这一章的内容就到这里。价差计算和特征工程是套利策略的基石,花时间把这一步做好,后面的模型训练会事半功倍。
公众号:蓝海数据掘金营,微信deep3321