第4章:特征工程入门:基础技术指标与截面标准化
各位同学,欢迎来到特征工程的第一课。
说实话,在量化投资里,模型选得再好,如果特征没做好,那基本就是白搭。我见过太多人一上来就堆几百个因子,结果过拟合得一塌糊涂。今天咱们就从最基础、最实用的技术指标讲起,一步步搭建你的Alpha特征库。
4.1 基础技术指标:均线、RSI、MACD
这些指标你可能早就听说过,但用在指数增强里,怎么用才有效?我个人的习惯是:不要直接用原始值,而是用衍生值。
4.1.1 移动平均线(MA)
均线是最简单的趋势指标。但直接拿收盘价减均线,其实噪音很大。
我一般这样处理:
- 价格位置:当前价格 / 均线 - 1,表示价格偏离均线的程度
- 均线斜率:当前均线值 / N天前均线值 - 1,表示趋势的加速度
- 多均线排列:短期均线在长期均线上方还是下方
核心经验:在A股市场,5日均线和20日均线的交叉信号,比单纯用一根均线有效得多。我曾在回测中发现,用价格偏离20日均线超过5%作为反转信号,胜率能提升近10%。
# 计算均线偏离度
import pandas as pd
import numpy as np
def ma_features(df, windows=[5, 10, 20, 60]):
"""计算均线相关特征"""
for w in windows:
ma = df['close'].rolling(w).mean()
df[f'ma_{w}'] = ma
df[f'price_to_ma_{w}'] = df['close'] / ma - 1
df[f'ma_slope_{w}'] = ma.pct_change(w)
return df
4.1.2 RSI(相对强弱指标)
RSI这个指标,说白了就是衡量最近涨跌的力度。默认用14天,但我建议你试试不同周期。
为什么?因为不同股票、不同市场环境下,RSI的有效周期是不一样的。我曾经在回测中发现,对于小盘股,6日RSI比14日RSI更灵敏,信号也更早。
避坑指南:我曾经直接用RSI的原始值(0-100)作为特征,结果模型学到的全是「超买超卖」的线性关系。后来改成RSI的排名分位数,效果好了很多。你想想看,在牛市中RSI长期在70以上,这时候70还能叫超买吗?
def rsi_features(df, periods=[6, 14, 21]):
"""计算RSI相关特征"""
for p in periods:
delta = df['close'].diff()
gain = delta.clip(lower=0)
loss = -delta.clip(upper=0)
avg_gain = gain.rolling(p).mean()
avg_loss = loss.rolling(p).mean()
rs = avg_gain / avg_loss
df[f'rsi_{p}'] = 100 - (100 / (1 + rs))
# 排名分位数
df[f'rsi_rank_{p}'] = df[f'rsi_{p}'].rank(pct=True)
return df
4.1.3 MACD(指数平滑异同移动平均线)
MACD其实是个趋势跟随指标,但很多人把它当震荡指标用,这就错了。
我个人习惯用MACD的柱状图变化率,而不是柱状图本身。因为柱状图的变化方向,往往比绝对值更有预测意义。
| MACD成分 | 计算公式 | 我常用的衍生特征 |
|---|---|---|
| DIF线 | EMA(12) - EMA(26) | DIF的5日变化率 |
| DEA线 | DIF的9日EMA | DIF与DEA的差值 |
| MACD柱 | (DIF - DEA) × 2 | 柱状图的斜率 |
4.2 滚动窗口统计量
技术指标说到底也是窗口统计量的一种。但除了均线、RSI这些,还有很多更基础的统计量值得挖掘。
我一般会计算以下几类:
- 波动类:滚动标准差、平均真实波幅(ATR)
- 动量类:N日收益率、N日最大回撤
- 分布类:滚动偏度、滚动峰度
- 相关性类:与大盘的滚动相关系数
注意:滚动窗口的大小选择很关键。窗口太小(比如3天),噪音太大;窗口太大(比如120天),信号太滞后。我一般会同时用多个窗口,然后让模型自己去选。比如同时用5、10、20、60天。
def rolling_stats(df, windows=[5, 10, 20]):
"""滚动窗口统计量"""
for w in windows:
df[f'ret_{w}d'] = df['close'].pct_change(w)
df[f'vol_{w}d'] = df['close'].pct_change().rolling(w).std()
df[f'max_drawdown_{w}d'] = (
df['close'].rolling(w).max() / df['close'] - 1
)
df[f'skew_{w}d'] = df['close'].pct_change().rolling(w).skew()
df[f'kurt_{w}d'] = df['close'].pct_change().rolling(w).kurt()
return df
4.3 截面标准化
嗯,这里要注意。前面讲的所有特征,都是针对单只股票的时间序列。但指数增强模型,需要的是横截面比较——也就是在同一时刻,哪只股票更强。
所以,截面标准化是必须的。我常用的方法有三种:
- Z-score标准化:减去截面均值,除以截面标准差。适合分布比较对称的特征。
- 排名标准化:把特征值转为排名,再映射到[-1, 1]区间。对异常值不敏感。
- 分位数标准化:按分位数分组,比如分成10组。适合非线性关系。
我的经验:对于技术指标这类特征,我强烈建议用排名标准化。因为技术指标本身就有很多异常值(比如某只股票突然涨停,RSI直接跳到100),如果用Z-score,整个截面都会被带偏。排名标准化就稳多了。
def cross_sectional_normalize(df, features, method='rank'):
"""截面标准化"""
for f in features:
if method == 'zscore':
mean = df.groupby('date')[f].transform('mean')
std = df.groupby('date')[f].transform('std')
df[f'{f}_zscore'] = (df[f] - mean) / std
elif method == 'rank':
df[f'{f}_rank'] = df.groupby('date')[f].rank(pct=True) * 2 - 1
return df
4.4 本章知识体系
说了这么多,咱们用一张图来总结一下本章的核心逻辑:
这张图把咱们今天讲的内容串起来了。从基础技术指标到滚动统计量,再到截面标准化,每一步都是为了让特征更干净、更有效。
最后说一句:特征工程没有银弹。我做了这么多年,最大的体会就是——多试、多对比、多思考。同样的指标,换一个窗口、换一种标准化方式,结果可能天差地别。别怕麻烦,这是量化投资中最值得花时间的地方。