4、特征工程基础:技术指标计算(均线、RSI、MACD)、滚动窗口统计量、滞后特征构建
在量化回测中,原始行情数据(开盘价、收盘价、最高价、最低价、成交量)无法直接作为 LightGBM 模型的输入。特征工程的目标是将这些原始数据转化为能够反映市场状态、趋势、动量及波动性的有效特征。本章将系统讲解三类核心特征:技术指标、滚动窗口统计量和滞后特征,并给出可直接用于回测框架的 Python 实现。
4.1 技术指标计算
技术指标是量化特征工程中最经典的组成部分。它们通过数学变换提取价格和成交量的隐含信息。以下重点介绍均线、RSI 和 MACD 三个最常用的指标。
4.1.1 移动平均线(MA)
移动平均线用于平滑价格序列,识别趋势方向。常见类型包括简单移动平均(SMA)和指数移动平均(EMA)。
| 类型 | 计算公式 | 特点 |
|---|---|---|
| SMA | MAt = (Pt + Pt-1 + ... + Pt-n+1) / n | 等权平均,计算简单,对价格突变反应较慢 |
| EMA | EMAt = α × Pt + (1-α) × EMAt-1,α = 2/(n+1) | 指数加权,近期数据权重更大,反应更灵敏 |
实践建议:在 LightGBM 特征中,通常同时纳入短期(如 5 日、10 日)和长期(如 20 日、60 日)均线,并计算均线之间的差值或比值(如 5日/20日 均线偏离度),以捕捉短期与长期趋势的收敛与发散。
import pandas as pd
import numpy as np
def add_ma_features(df, windows=[5, 10, 20, 60]):
"""添加简单移动平均线特征"""
for w in windows:
df[f'ma_{w}'] = df['close'].rolling(window=w).mean()
# 价格与均线的偏离度
df[f'ma_{w}_pct'] = (df['close'] - df[f'ma_{w}']) / df[f'ma_{w}']
return df
4.1.2 相对强弱指标(RSI)
RSI 衡量价格变动的速度和幅度,用于判断超买或超卖状态。通常以 14 天为周期,RSI 值超过 70 视为超买,低于 30 视为超卖。
计算步骤:
- 计算每日价格变动 ΔP = Pt - Pt-1
- 分别计算周期内平均上涨幅度(AvgGain)和平均下跌幅度(AvgLoss)
- RS = AvgGain / AvgLoss
- RSI = 100 - (100 / (1 + RS))
def add_rsi_feature(df, period=14):
"""添加 RSI 特征"""
delta = df['close'].diff()
gain = delta.clip(lower=0)
loss = -delta.clip(upper=0)
avg_gain = gain.rolling(window=period).mean()
avg_loss = loss.rolling(window=period).mean()
rs = avg_gain / avg_loss
df['rsi_14'] = 100 - (100 / (1 + rs))
return df
4.1.3 指数平滑异同移动平均线(MACD)
MACD 由快线(DIF)、慢线(DEA)和柱状图(MACD Histogram)三部分组成,用于判断趋势的启动、延续和反转。
| 组件 | 计算方式 | 信号含义 |
|---|---|---|
| DIF(快线) | EMA12 - EMA26 | 反映短期与长期趋势的差值 |
| DEA(慢线) | DIF 的 9 日 EMA | 对 DIF 的平滑处理 |
| MACD Histogram | 2 × (DIF - DEA) | 柱状图由负转正为买入信号 |
def add_macd_features(df):
"""添加 MACD 特征"""
exp12 = df['close'].ewm(span=12, adjust=False).mean()
exp26 = df['close'].ewm(span=26, adjust=False).mean()
df['macd_dif'] = exp12 - exp26
df['macd_dea'] = df['macd_dif'].ewm(span=9, adjust=False).mean()
df['macd_hist'] = 2 * (df['macd_dif'] - df['macd_dea'])
return df
特征工程要点:技术指标本身是衍生特征,但在 LightGBM 中,我们还可以进一步提取指标的“形态特征”,例如:RSI 是否从低位上穿 30、MACD 柱状图是否由负转正、均线是否形成“金叉/死叉”等。这些布尔型或离散型特征对树模型的分裂非常友好。
4.2 滚动窗口统计量
滚动窗口统计量是量化特征工程中另一类强大的工具。它们通过滑动窗口计算价格或收益率的分布特征,帮助模型捕捉市场的波动状态和极端行为。
4.2.1 常用滚动统计量
| 统计量 | 含义 | 典型窗口 | 应用场景 |
|---|---|---|---|
| 滚动标准差 | 衡量价格波动率 | 20 日、60 日 | 波动率聚集效应、风险度量 |
| 滚动偏度 | 衡量收益率分布不对称性 | 20 日、30 日 | 识别趋势中的非对称风险 |
| 滚动峰度 | 衡量收益率分布尾部厚度 | 20 日、30 日 | 捕捉极端事件概率 |
| 滚动最大值/最小值 | 窗口内价格极值 | 10 日、20 日 | 支撑阻力位识别 |
| 滚动分位数 | 当前价格在窗口内的相对位置 | 20 日、60 日 | 判断价格处于历史区间的什么位置 |
def add_rolling_stats(df, windows=[5, 10, 20]):
"""添加滚动窗口统计量特征"""
for w in windows:
# 滚动标准差(波动率)
df[f'volatility_{w}'] = df['close'].pct_change().rolling(window=w).std()
# 滚动偏度
df[f'skew_{w}'] = df['close'].rolling(window=w).skew()
# 滚动峰度
df[f'kurt_{w}'] = df['close'].rolling(window=w).kurt()
# 当前价格在窗口内的百分位排名
df[f'percentile_rank_{w}'] = df['close'].rolling(window=w).apply(
lambda x: (x[-1] - x.min()) / (x.max() - x.min()) if x.max() != x.min() else 0.5
)
return df
注意:滚动窗口统计量在窗口初期会产生大量 NaN 值。在回测中,必须确保模型在训练和预测时不会使用这些无效值。建议在特征工程后使用
.dropna() 或设置最小有效样本数。
4.3 滞后特征构建
滞后特征(Lag Features)是将过去时刻的变量值作为当前时刻的特征。对于时间序列预测,滞后特征是捕捉序列自相关结构的最直接方式。
4.3.1 滞后特征的类型
- 价格滞后:过去 n 期的收盘价、开盘价等。
- 收益率滞后:过去 n 期的对数收益率或简单收益率。
- 成交量滞后:过去 n 期的成交量或成交额。
- 技术指标滞后:过去 n 期的 RSI、MACD 等指标值。
def add_lag_features(df, columns=['close', 'volume', 'rsi_14'], lags=[1, 2, 3, 5, 10]):
"""添加滞后特征"""
for col in columns:
if col not in df.columns:
continue
for lag in lags:
df[f'{col}_lag_{lag}'] = df[col].shift(lag)
return df
4.3.2 滞后特征与差分特征
对于非平稳时间序列,直接使用价格滞后可能导致模型学习到虚假关系。通常建议将滞后特征与差分特征(一阶差分或收益率)结合使用。
| 特征类型 | 示例 | 平稳性 | 适用模型 |
|---|---|---|---|
| 价格滞后 | close_lag_1 | 非平稳 | 需配合差分或树模型(对尺度不敏感) |
| 收益率滞后 | return_lag_1 | 近似平稳 | 通用,推荐作为主要滞后特征 |
| 差分滞后 | diff_close_lag_1 | 近似平稳 | 通用,适合捕捉短期动量 |
4.4 特征组合与高阶衍生
在完成基础特征计算后,可以通过特征组合进一步提升模型表现。LightGBM 对特征交互有自动学习能力,但人工构造有意义的组合特征仍能加速收敛。
- 比值特征:短期均线 / 长期均线、当前价格 / 滚动均值、成交量 / 滚动均值。
- 差值特征:快线 - 慢线、当前 RSI - 过去 RSI 均值。
- 交叉信号:均线金叉/死叉(布尔值)、MACD 柱状图由负转正(布尔值)。
- 时序聚合:过去 n 日收益率累加、过去 n 日上涨天数占比。
def add_advanced_features(df):
"""添加高阶组合特征"""
# 均线比值
df['ma_ratio_5_20'] = df['ma_5'] / df['ma_20']
# 成交量相对强度
df['volume_ratio'] = df['volume'] / df['volume'].rolling(20).mean()
# 过去5日上涨天数占比
df['up_days_5'] = (df['close'].diff() > 0).rolling(5).sum() / 5
# RSI 变化方向
df['rsi_direction'] = np.sign(df['rsi_14'].diff())
return df
特征工程黄金法则:在回测中,所有特征的计算必须严格基于历史数据,不得使用未来信息。建议在回测框架中实现一个统一的
compute_features(df) 函数,该函数接收截至当前时刻的数据切片,返回特征向量,确保时间序列的因果性。