第三章:特征工程基础——技术指标计算与统计特征
大家好,我是你们的量化交易工程师朋友。今天咱们聊聊特征工程里最基础、也最核心的一块——技术指标计算和统计特征提取。
说实话,我刚开始做量化那会儿,觉得技术指标就是金叉死叉那一套。后来踩了不少坑才明白:指标本身不赚钱,但指标背后的数学逻辑,才是我们和市场的对话语言。
3.1 技术指标计算:从均线说起
均线(MA)是最简单的,但也是最容易被低估的。我个人习惯把均线分成三组:
- 短期均线(5日、10日):捕捉快速波动
- 中期均线(20日、60日):识别趋势方向
- 长期均线(120日、250日):判断牛熊分界
代码实现其实很简单,但有个细节要注意——初始值的处理。我在项目中遇到过一个问题:直接用pandas的rolling函数,前N天会全是NaN。后来我改用min_periods参数,让数据量不足时也能计算。
import pandas as pd
import numpy as np
def calculate_ma(df, windows=[5, 10, 20, 60]):
"""计算多周期均线,处理初始缺失值"""
for w in windows:
df[f'ma_{w}'] = df['close'].rolling(window=w, min_periods=1).mean()
return df
3.2 RSI:超买超卖的真相
RSI(相对强弱指标)很多人用14天周期,但我觉得这太死板了。你想想看,不同品种的波动节奏完全不一样。
我曾经在期货市场里试过:把RSI周期从14改成9,信号提前了2-3天,但假信号也多了。后来我总结了一个经验:波动大的品种用短周期,趋势强的品种用长周期。
def calculate_rsi(df, period=14):
"""计算RSI,注意处理除零错误"""
delta = df['close'].diff()
gain = delta.where(delta > 0, 0)
loss = -delta.where(delta < 0, 0)
avg_gain = gain.rolling(window=period, min_periods=1).mean()
avg_loss = loss.rolling(window=period, min_periods=1).mean()
rs = avg_gain / avg_loss.replace(0, np.nan) # 避免除零
df['rsi'] = 100 - (100 / (1 + rs))
df['rsi'] = df['rsi'].fillna(50) # 初始值设为中性
return df
3.3 MACD:趋势与动量的结合
MACD说白了就是两条均线的差值,再加一条信号线。但它的魅力在于:把趋势和动量融合在一个指标里。
我记得有一次做股指期货的策略,发现MACD柱状线的斜率比金叉死叉更敏感。柱状线由负转正,往往比DIF上穿DEA早1-2根K线。
def calculate_macd(df, fast=12, slow=26, signal=9):
"""计算MACD,返回DIF、DEA和柱状线"""
ema_fast = df['close'].ewm(span=fast, adjust=False).mean()
ema_slow = df['close'].ewm(span=slow, adjust=False).mean()
df['macd_dif'] = ema_fast - ema_slow
df['macd_dea'] = df['macd_dif'].ewm(span=signal, adjust=False).mean()
df['macd_bar'] = 2 * (df['macd_dif'] - df['macd_dea'])
return df
3.4 滚动窗口统计特征
技术指标之外,我还喜欢用滚动窗口统计特征。说白了就是:在过去N天里,价格是怎么分布的。
常用的统计量包括:
| 特征名称 | 计算方式 | 我的用法 |
|---|---|---|
| 滚动均值 | 过去N天收盘价的平均值 | 判断当前价格相对于历史均值的位置 |
| 滚动标准差 | 过去N天收盘价的标准差 | 衡量波动剧烈程度 |
| 滚动偏度 | 过去N天收益率的偏度 | 判断涨跌不对称性 |
| 滚动峰度 | 过去N天收益率的峰度 | 识别极端行情出现的概率 |
| 滚动分位数 | 过去N天的20%、50%、80%分位 | 构建价格通道 |
def rolling_stats(df, window=20):
"""计算滚动窗口统计特征"""
close = df['close']
returns = close.pct_change()
df['roll_mean'] = close.rolling(window).mean()
df['roll_std'] = close.rolling(window).std()
df['roll_skew'] = returns.rolling(window).skew()
df['roll_kurt'] = returns.rolling(window).kurt()
df['roll_q20'] = close.rolling(window).quantile(0.2)
df['roll_q80'] = close.rolling(window).quantile(0.8)
return df
3.5 波动率特征:市场的情绪温度计
波动率是我最看重的特征之一。为什么?因为波动率会聚集——大涨大跌之后,往往还有大涨大跌。
我常用的波动率特征有三种:
- 历史波动率:过去N天收益率的标准差,年化处理
- 已实现波动率:日内高频数据的波动,用5分钟K线计算
- 波动率锥:不同周期波动率的对比,判断当前波动率处于历史什么位置
def volatility_features(df, windows=[5, 20, 60]):
"""计算多周期波动率特征"""
returns = df['close'].pct_change()
for w in windows:
# 年化波动率(假设一年252个交易日)
vol = returns.rolling(w).std() * np.sqrt(252)
df[f'vol_{w}d'] = vol
# 波动率变化率
df[f'vol_change_{w}d'] = vol.pct_change()
# 波动率相对于历史均值的位置
df[f'vol_zscore_{w}d'] = (vol - vol.rolling(252).mean()) / vol.rolling(252).std()
return df
3.6 特征组合的艺术
单个指标就像单个乐器,组合起来才能奏出交响乐。我常用的组合方式:
- 均线 + RSI:均线定方向,RSI定入场时机
- MACD + 波动率:MACD给信号,波动率给仓位大小
- 滚动统计 + 波动率锥:判断当前市场状态是正常还是异常
举个例子,我做过一个策略:当价格在20日均线上方(趋势向上),RSI低于30(超卖),同时波动率处于历史低位(即将爆发),这时候开多单。这个组合在震荡市里表现一般,但在趋势行情里效果很好。
3.7 本章知识体系
下面这张图是我自己画的,把本章的核心逻辑串起来了:
嗯,这张图把本章的内容串起来了。从原始价格数据出发,经过三大类特征提取,最终形成特征矩阵。这就是我们后续做聚类和自适应交易的基础。
公众号:蓝海资料掘金营,微信deep3321