数据预处理与特征工程:金融数据清洗、缺失值处理、标准化与归一化、技术指标特征构建
各位同学,咱们今天聊点实在的。做量化交易,很多人一上来就想着怎么调参数、怎么选模型。但我跟你说,真正决定你策略生死的,其实是数据预处理和特征工程这一关。我见过太多人,模型跑得飞起,结果一上实盘就崩,为什么?数据没洗干净。
说白了,金融数据就是一堆「脏活累活」。你想想看,行情数据里可能有停牌、有除权除息、有数据源断流,甚至还有交易所的乌龙指。这些不处理干净,模型学到的全是噪音。嗯,咱们今天就把这块硬骨头啃下来。
1. 金融数据清洗:把垃圾倒掉
数据清洗,我个人的习惯是「先看后洗」。拿到数据别急着写代码,先打开看一眼。用 df.info() 和 df.describe() 扫一遍,心里有个数。
常见的脏数据有几种:
- 重复数据:同一个时间戳出现两次,可能是数据源推送重复了。
- 异常值:比如某只股票突然涨了1000%,大概率是除权没复权。
- 时间戳错乱:非交易时间出现数据,或者时间顺序颠倒。
重点:金融数据的时间戳必须是严格递增的。我曾在项目中遇到过某数据源在收盘后还推送了「假数据」,导致策略在非交易时段开仓,亏了一笔冤枉钱。
清洗代码其实不复杂,但细节多。给你看个例子:
import pandas as pd
import numpy as np
# 假设 df 是原始行情数据
df = df.sort_values('timestamp') # 先按时间排序
df = df.drop_duplicates(subset=['timestamp', 'symbol']) # 去重
df = df[df['volume'] > 0] # 剔除成交量为0的无效数据
df = df[df['close'] > 0] # 剔除价格为0或负数的异常数据
小技巧:清洗时别用 inplace=True,我建议每次都生成新的 DataFrame。这样万一洗错了,还能回滚。
2. 缺失值处理:别让「空」坑了你
金融数据里缺失值太常见了。停牌、节假日、数据源断流,都会导致某段时间没有数据。怎么处理?
我一般分三种情况:
| 缺失比例 | 处理方法 | 适用场景 |
|---|---|---|
| < 5% | 前向填充(ffill)或插值 | 盘中短暂断流,用上一笔数据填充 |
| 5% - 20% | 线性插值或中位数填充 | 日内数据有少量缺失,用前后值平均 |
| > 20% | 直接剔除该时间段或该特征 | 停牌期间,数据缺失太多,填充反而引入偏差 |
举个例子,停牌期间的数据怎么处理?我曾经犯过一个错:用前一天的收盘价填充停牌期间的所有数据。结果模型学到的规律全是「价格不变」,复牌后直接被打脸。
避坑指南:停牌数据不要用 ffill 填充超过3天。我建议直接标记为 NaN,然后在训练时用 mask 忽略掉。或者干脆把停牌期间的数据行删掉,别让模型「学空气」。
# 前向填充,最多填充3个缺失值
df['close'] = df['close'].fillna(method='ffill', limit=3)
# 超过3个缺失值,直接置为 NaN 并后续删除
df['close'] = df['close'].where(df['close'].notna() | (df['close'].isna().cumsum() <= 3))
3. 标准化与归一化:让数据「站在同一起跑线」
你想想看,茅台股价1800,工商银行股价5块。如果你直接把价格扔进模型,茅台的特征权重会被放大几百倍。模型会以为「价格高=重要」,这显然不对。
所以我们需要做标准化或归一化。说白了,就是把不同量纲的数据拉到同一个尺度上。
常用的方法就两种:
- Min-Max 归一化:把数据缩放到 [0, 1] 区间。适合有明确上下界的特征,比如 RSI 指标。
- Z-score 标准化:减去均值除以标准差,让数据符合标准正态分布。适合没有明确边界的特征,比如收益率。
我个人习惯:做回归或分类时用 Z-score,做神经网络时用 Min-Max。为什么?因为神经网络的激活函数(比如 sigmoid)在 0 附近梯度最大,归一化后训练更快。
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# Z-score 标准化
scaler_std = StandardScaler()
df['return_z'] = scaler_std.fit_transform(df[['return']])
# Min-Max 归一化
scaler_mm = MinMaxScaler()
df['volume_norm'] = scaler_mm.fit_transform(df[['volume']])
注意:一定要先拆分训练集和测试集,再用训练集的参数去标准化测试集。千万别用全量数据 fit,否则会引入未来信息,造成「数据泄露」。我见过有人因为这个回测曲线漂亮得不像话,实盘直接崩了。
4. 技术指标特征构建:从价格中「挖」出规律
原始的价格和成交量,信息量其实很有限。真正有价值的是技术指标——它们是对原始数据的「二次加工」,能反映出趋势、动量、波动率等特征。
常用的技术指标特征,我列几个核心的:
| 指标类别 | 常用指标 | 构建逻辑 |
|---|---|---|
| 趋势类 | MA、EMA、MACD | 平滑价格,识别方向 |
| 动量类 | RSI、KDJ、ROC | 衡量涨跌速度,判断超买超卖 |
| 波动率类 | ATR、布林带宽度 | 衡量价格波动幅度 |
| 成交量类 | OBV、成交量加权均价 | 结合量价关系 |
构建特征时,我有个原则:不要堆砌指标。你想想看,把几十个指标全扔进去,模型会陷入「维度灾难」。我一般先选5-8个核心指标,然后做相关性分析,剔除高度相关的(比如 MA5 和 MA10 相关性通常很高,留一个就行)。
import talib
# 构建技术指标特征
df['ma5'] = talib.SMA(df['close'], timeperiod=5)
df['ma20'] = talib.SMA(df['close'], timeperiod=20)
df['rsi'] = talib.RSI(df['close'], timeperiod=14)
df['atr'] = talib.ATR(df['high'], df['low'], df['close'], timeperiod=14)
# 计算 MACD
df['macd'], df['macd_signal'], df['macd_hist'] = talib.MACD(
df['close'], fastperiod=12, slowperiod=26, signalperiod=9
)
核心思路:特征构建不是越多越好,而是越「有效」越好。我习惯用「逻辑驱动」的方式:先想清楚我要捕捉什么市场规律(比如趋势跟随、均值回归),再针对性地构建特征。而不是先堆指标,再让模型自己找规律。
5. 知识体系总览
说了这么多,咱们用一张图把整个流程串起来。数据预处理和特征工程,本质上就是一条流水线:
这张图就是咱们今天讲的核心流程。从原始数据开始,经过清洗、缺失值处理、标准化,再到特征构建和筛选,最终得到一份干净可用的特征集。每一步都环环相扣,少一步都不行。
好了,数据预处理和特征工程这块,咱们就聊到这儿。记住一句话:好的特征胜过复杂的模型。把数据洗干净、把特征做扎实,你的策略就已经赢了一半。