第四章:特征工程(上)——技术指标构建、波动率特征、动量因子、均值回复特征
各位同学,欢迎来到特征工程部分。说实话,在量化交易这个行当里,我见过太多人把精力花在模型调参上,却忽略了最基础的东西——特征。你想想看,模型再牛,喂进去的是垃圾,出来的只能是更精致的垃圾。今天我们就来聊聊特征工程的上半场,重点讲四个核心模块:技术指标、波动率、动量因子和均值回复。
4.1 技术指标构建:别只会用TA-Lib
很多人一上来就import talib,然后噼里啪啦算一堆指标。我个人习惯是,先想清楚这个指标到底在表达什么市场行为。举个例子,RSI(相对强弱指标)——它本质上是在度量过去N天内上涨幅度与总波动的比值。你理解了这一点,就能自己手写,还能做定制化改造。
核心思路:技术指标不是越多越好,关键是每个指标都要有明确的交易逻辑支撑。
我在项目中遇到过这样一个场景:用TA-Lib算出来的布林带,在A股市场经常出现频繁的假突破。后来我改成了用EWMA(指数加权移动平均)替代SMA,效果好了不少。为什么?因为EWMA对近期价格更敏感,能更快捕捉趋势变化。
这里给出一段我常用的布林带自定义实现:
import numpy as np
import pandas as pd
def custom_bollinger(price, window=20, num_std=2, use_ewma=True):
if use_ewma:
mid = price.ewm(span=window, adjust=False).mean()
else:
mid = price.rolling(window).mean()
std = price.rolling(window).std()
upper = mid + num_std * std
lower = mid - num_std * std
return pd.DataFrame({'mid': mid, 'upper': upper, 'lower': lower})
嗯,这里要注意:用EWMA时,窗口参数的含义和SMA略有不同。EWMA的span参数相当于让权重衰减到约86.5%时的周期长度,不是简单的平均周期。
4.2 波动率特征:市场的"心跳"
波动率这东西,说白了就是市场的心跳。心跳太快(高波动)容易猝死,心跳太慢(低波动)可能休克。我个人认为,波动率特征是所有特征里最被低估的一个。
常见的波动率指标有几种:
- 历史波动率(HV):过去N天收益率的标准差,最基础但最稳定
- 已实现波动率(RV):日内高频数据的波动率,需要tick级数据
- Parkinson波动率:利用最高价和最低价估算,比HV更高效
- GARCH波动率:带自回归的波动率模型,适合有波动聚集的市场
我曾经在商品期货上踩过一个坑:直接用日线算HV,结果发现夜盘跳空导致波动率被严重高估。后来改用"隔夜调整"后的收益率,才得到真实的日内波动率。具体做法是:
def adjusted_returns(close, open_price):
# 隔夜收益率 = (开盘 - 前收盘) / 前收盘
overnight_ret = (open_price - close.shift(1)) / close.shift(1)
# 日内收益率 = (收盘 - 开盘) / 开盘
intraday_ret = (close - open_price) / open_price
# 总收益率 = (1 + 隔夜) * (1 + 日内) - 1
total_ret = (1 + overnight_ret) * (1 + intraday_ret) - 1
return total_ret
小技巧:波动率特征最好做标准化处理,比如除以近期中位数。这样在不同品种、不同时间区间之间才有可比性。
4.3 动量因子:趋势是你的朋友
动量因子,量化圈里最经典的因子之一。你想想看,如果一只股票连续涨了5天,它明天继续涨的概率大不大?答案是:不一定。动量因子有很多种变体,每种都有不同的适用场景。
我习惯把动量因子分为三类:
| 类型 | 计算方式 | 适用场景 |
|---|---|---|
| 价格动量 | (close - close_lag_N) / close_lag_N | 趋势明显的市场 |
| 成交量动量 | volume / volume_ma_N | 确认趋势强度 |
| 复合动量 | 价格动量 × 成交量动量 | 过滤假突破 |
这里有个坑,我曾经吃过亏:直接用收益率作为动量因子,结果在震荡市里被来回打脸。后来我加入了"动量持续性"的判断——只有当动量连续N期保持同向时,才认为是有效信号。
def momentum_signal(price, lookback=20, min_consistency=3):
ret = price.pct_change(lookback)
# 计算每日方向
daily_dir = np.sign(price.diff())
# 连续同向天数
consistency = daily_dir.rolling(lookback).apply(
lambda x: np.sum(x == x.iloc[-1]) if len(x) > 0 else 0
)
# 只有动量方向一致且持续性足够时才产生信号
signal = np.where(
(np.abs(ret) > 0.02) & (consistency >= min_consistency),
np.sign(ret), 0
)
return signal
警告:动量因子在极端行情下会失效。比如2020年3月的美股熔断,动量策略几乎全部崩溃。记得加上风控条件。
4.4 均值回复特征:物极必反
均值回复,说白了就是"涨多了会跌,跌多了会涨"。这个逻辑在震荡市里特别好用,但在单边趋势里就是灾难。我建议你把均值回复和动量因子结合起来用——用动量判断趋势方向,用均值回复寻找入场时机。
常用的均值回复特征包括:
- Z-score:(当前价格 - 均值) / 标准差,衡量偏离程度
- 乖离率:(价格 - 均线) / 均线,简单直观
- RSI:超买超卖指标,30以下超卖,70以上超买
- 布林带宽度:(上轨 - 下轨) / 中轨,衡量波动区间
我记得有一次做ETF套利,发现沪深300和中证500的价差经常在±2%之间波动。我构建了一个简单的均值回复策略:当价差超过1.5%时做空价差,低于-1.5%时做多价差。回测效果不错,但实盘时发现一个问题——价差可能长时间不回均值,导致浮亏很大。
后来我加了一个"时间衰减"因子:如果价差偏离超过N天还没回复,就强制平仓。这个改进让夏普比率从0.8提升到了1.4。
def mean_reversion_score(price, window=20, decay_days=10):
zscore = (price - price.rolling(window).mean()) / price.rolling(window).std()
# 时间衰减:偏离越久,信心越低
days_since_cross = np.where(
np.abs(zscore) > 1.5,
np.arange(len(zscore)) - np.maximum.accumulate(
np.where(np.abs(zscore) <= 1.5, np.arange(len(zscore)), 0)
),
0
)
decay = np.exp(-days_since_cross / decay_days)
return zscore * decay
4.5 知识体系总览
说了这么多,我们来画一张图,把今天讲的内容串起来。这张图展示了特征工程的四个核心模块以及它们之间的关系:
这张图里,四个模块不是孤立的。技术指标是基础,波动率特征提供风险度量,动量因子判断方向,均值回复寻找拐点。我个人习惯是把这四个维度的特征组合成一个特征向量,然后输入到模型中。你想想看,一个同时包含动量方向和均值回复程度的特征,是不是比单一指标更有信息量?
实战建议:刚开始做特征工程时,不要追求数量。先选3-5个核心特征,把它们的计算逻辑、参数敏感性、市场适应性都吃透。等跑通了基础模型,再逐步添加新特征。
好了,特征工程的上半场就到这里。记住一句话:好的特征能让简单的模型跑出惊人的效果,烂的特征能让复杂的模型变成废物。下一节我们会继续聊特征工程的下半场——时序特征、截面特征和特征选择。各位先把今天的内容消化一下,动手写写代码,有问题随时交流。