第三节:特征工程基础——技术指标计算与数据预处理

各位同学,欢迎来到特征工程这一章。说实话,在量化交易里,模型选得再好,特征不行也是白搭。我见过太多人一上来就堆几百个特征,结果过拟合得一塌糊涂。今天咱们就聊聊,怎么从原始行情数据里,提炼出真正有用的特征。

3.1 技术指标计算:经典指标的实战用法

技术指标这东西,很多人觉得老掉牙。但我个人习惯是,先用经典指标打个底,再考虑上复杂特征。毕竟,这些指标经过了市场几十年的检验,不是没道理的。

3.1.1 移动平均线(MA)

MA 说白了就是过去 N 天的平均价格。它帮我们平滑掉短期的噪音,看清趋势方向。

import pandas as pd
import numpy as np

# 假设 df 包含 'close' 列
df['MA5'] = df['close'].rolling(window=5).mean()
df['MA20'] = df['close'].rolling(window=20).mean()
df['MA60'] = df['close'].rolling(window=60).mean()
我的经验: 别只算一根均线。我习惯把短、中、长三根均线放一起,看它们之间的排列关系。金叉死叉虽然老套,但在趋势行情里确实管用。

3.1.2 MACD 指标

MACD 是均线的升级版。它通过快慢线的交叉和柱状图的变化,帮我们判断动能的强弱。

def compute_macd(df, fast=12, slow=26, signal=9):
    df['EMA_fast'] = df['close'].ewm(span=fast, adjust=False).mean()
    df['EMA_slow'] = df['close'].ewm(span=slow, adjust=False).mean()
    df['DIF'] = df['EMA_fast'] - df['EMA_slow']
    df['DEA'] = df['DIF'].ewm(span=signal, adjust=False).mean()
    df['MACD'] = 2 * (df['DIF'] - df['DEA'])
    return df
避坑指南: 我曾经在回测时发现,MACD 在震荡行情里频繁发出假信号。后来我加了一个条件——只有当价格突破布林带中轨时,才采纳 MACD 的信号。效果好了不少。

3.1.3 RSI 相对强弱指标

RSI 衡量的是过去一段时间内,价格上涨和下跌的力度对比。说白了,就是看市场是「买过头」了还是「卖过头」了。

def compute_rsi(df, period=14):
    delta = df['close'].diff()
    gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
    loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
    rs = gain / loss
    df['RSI'] = 100 - (100 / (1 + rs))
    return df

嗯,这里要注意:RSI 超过 70 算超买,低于 30 算超卖。但别死板,在强趋势行情里,RSI 可以长时间维持在超买或超卖区。我一般会结合趋势指标一起用。

3.1.4 布林带(Bollinger Bands)

布林带由中轨(MA)、上轨(中轨+2倍标准差)和下轨(中轨-2倍标准差)组成。它帮我们判断价格的「相对高低」。

def compute_bollinger(df, period=20, num_std=2):
    df['BB_middle'] = df['close'].rolling(window=period).mean()
    df['BB_std'] = df['close'].rolling(window=period).std()
    df['BB_upper'] = df['BB_middle'] + num_std * df['BB_std']
    df['BB_lower'] = df['BB_middle'] - num_std * df['BB_std']
    df['BB_width'] = (df['BB_upper'] - df['BB_lower']) / df['BB_middle']
    return df
关键点: 布林带的宽度变化很有价值。宽度收窄往往预示着即将出现大行情。我在做螺纹钢期货时,就靠这个特征捕捉到过几次不错的突破行情。

3.2 滚动统计特征:捕捉局部统计规律

技术指标之外,滚动统计特征是我最常用的「万金油」。它们能帮我们捕捉价格序列的局部统计特性。

3.2.1 滚动均值与标准差

滚动均值反映局部趋势,滚动标准差反映波动率。这两个是基础中的基础。

# 滚动统计特征
df['roll_mean_10'] = df['close'].rolling(window=10).mean()
df['roll_std_10'] = df['close'].rolling(window=10).std()
df['roll_max_20'] = df['high'].rolling(window=20).max()
df['roll_min_20'] = df['low'].rolling(window=20).min()

你想想看,为什么用最大值和最小值?因为它们能告诉我们,当前价格在最近 N 天的区间里处于什么位置。这个信息对判断支撑阻力很有帮助。

3.2.2 滚动相关系数

有时候,两个品种之间的相关性变化比价格本身更有预测价值。比如螺纹钢和铁矿石,它们的价格走势通常高度相关,但一旦背离,往往意味着机会。

# 计算滚动相关系数
df['corr_20'] = df['close_steel'].rolling(20).corr(df['close_iron'])
我的习惯: 滚动窗口大小的选择很关键。太短了噪音多,太长了反应迟钝。我一般会同时算 10、20、60 三个窗口,让模型自己去选。

3.3 时间序列滞后特征:让模型「看到」过去

期货价格是有记忆的。今天的价格往往和昨天、前天甚至一周前的价格有关。滞后特征就是把这些历史信息显式地喂给模型。

# 生成滞后特征
for lag in [1, 2, 3, 5, 10, 20]:
    df[f'close_lag_{lag}'] = df['close'].shift(lag)
    df[f'volume_lag_{lag}'] = df['volume'].shift(lag)
避坑指南: 我曾经犯过一个低级错误——在训练时用了未来的数据。比如用 t 时刻的特征去预测 t 时刻的涨跌,结果回测曲线漂亮得不像话,实盘却一塌糊涂。记住:滞后特征一定要用 shift() 把数据往后挪,确保不会泄露未来信息。

3.4 特征标准化与归一化:让模型「公平」对待每个特征

不同特征的量纲差异很大。比如价格是几千块,成交量是几万手,RSI 是 0-100 的数值。如果不做处理,模型会天然地偏向数值大的特征。

3.4.1 标准化(Z-score)

标准化把数据变成均值为 0、标准差为 1 的分布。适合数据本身近似正态分布的情况。

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
df['close_scaled'] = scaler.fit_transform(df[['close']])

3.4.2 归一化(Min-Max)

归一化把数据压缩到 [0, 1] 区间。适合数据有明确上下界的情况,比如 RSI 天然就在 0-100 之间。

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler(feature_range=(0, 1))
df['rsi_norm'] = scaler.fit_transform(df[['RSI']])
我的建议: 对于树模型(如 XGBoost、LightGBM),标准化不是必须的,因为它们对尺度不敏感。但对于神经网络和 SVM,标准化是必须的。我一般会先做标准化,反正没坏处。

3.5 本章知识体系总览

下面这张图,是我自己梳理的特征工程知识框架。你可以把它当作一个检查清单,看看自己有没有遗漏什么。

特征工程知识体系 原始行情数据 技术指标计算 MA / MACD / RSI 布林带 / 其他指标 滚动统计特征 均值 / 标准差 最大值 / 最小值 相关系数 时间滞后特征 价格滞后 成交量滞后 多阶滞后 特征标准化 Z-score 标准化 Min-Max 归一化 特征工程的核心目标: 从原始数据中提取对预测有用的信息,同时避免过拟合

好了,这一节的内容就到这里。特征工程是个细活,需要反复尝试和验证。我建议你从最简单的 MA 和滚动均值开始,慢慢往上加特征。记住:少即是多,好的特征比多的特征更重要。


公众号:蓝海资料掘金营,微信deep3321