第二章 数据清洗与预处理:金融时间序列数据获取、缺失值处理、异常值检测、收益率计算与标准化
做波动率预测,第一步不是建模,而是跟数据死磕。
我见过太多人,拿到数据就急着跑模型,结果出来的结果一塌糊涂。说白了,金融数据天生就是脏的。缺失值、异常点、非同步交易……哪个都能让你的模型翻车。这一章,咱们就把这些坑一个个填上。
核心观点:数据清洗的质量,直接决定波动率预测的成败。花80%的时间做清洗,只花20%的时间建模,这是量化金融的常态。
2.1 金融时间序列数据获取
数据源的选择,我个人的习惯是:优先用公开的、可复现的数据源。比如雅虎财经、Quandl、Wind这些。为什么?因为你的策略最终要能回测,别人也能验证。
获取数据时,有几个关键点要注意:
- 调整因子:股票有分红、拆股,价格必须做后复权处理。我遇到过有人直接用原始价格算收益率,结果回测曲线漂亮得不行,一查是没复权……
- 交易时间对齐:不同市场开盘时间不同。比如A股和港股,你直接拼接日线数据,会引入非同步交易偏差。
- 数据频率:波动率预测常用日线、5分钟线、Tick级数据。频率越高,噪声越大,清洗越要小心。
# Python示例:从雅虎财经获取数据
import yfinance as yf
import pandas as pd
# 获取沪深300指数数据
data = yf.download('000300.SS', start='2020-01-01', end='2023-12-31')
# 自动调整价格(后复权)
data = data[['Adj Close']].rename(columns={'Adj Close': 'price'})
print(data.head())
小技巧:我习惯在获取数据后,先检查一下数据的时间跨度是否连续。用 data.index.is_monotonic_increasing 看一眼,能省很多事。
2.2 缺失值处理
金融数据缺失,太常见了。停牌、节假日、数据源故障……你想想看,如果直接扔掉缺失的行,你的时间序列就断了,波动率计算会出大问题。
我的处理原则是:
- 连续缺失不超过3个交易日:用前向填充(ffill)或线性插值。我个人偏爱线性插值,因为它更平滑,不会引入跳跃。
- 连续缺失超过5个交易日:建议标记为缺失,或者用行业指数填充。我曾经处理过一只停牌半年的股票,直接填充会严重扭曲波动率估计。
- 开盘缺失但收盘存在:用收盘价代替开盘价,但要在备注里标记。
# 缺失值处理示例
# 前向填充
data['price_ffill'] = data['price'].fillna(method='ffill')
# 线性插值
data['price_interp'] = data['price'].interpolate(method='linear')
避坑指南:我曾经用前向填充处理过一段数据,结果波动率模型跑出来异常低。后来发现,是因为停牌期间价格不变,导致收益率出现大量0值,严重低估了真实波动。所以,停牌期间的数据,最好标记为缺失,而不是简单填充。
2.3 异常值检测
异常值,说白了就是那些「离谱」的数据点。比如某股票一天涨了50%,或者价格突然跳空10%。这些点如果不处理,会严重扭曲波动率估计。
我常用的方法有三种:
- 3-sigma法则:假设收益率服从正态分布,超过均值±3倍标准差的值视为异常。简单粗暴,但对厚尾分布效果不好。
- MAD(中位数绝对偏差):比3-sigma更稳健。用中位数代替均值,用MAD代替标准差。我个人的首选。
- 分位数法:取1%和99%分位数作为阈值。适合处理极端值,但需要根据数据特点调整分位数。
# MAD异常值检测示例
import numpy as np
def detect_outliers_mad(series, threshold=3.5):
median = np.median(series)
mad = np.median(np.abs(series - median))
modified_z_scores = 0.6745 * (series - median) / mad
return np.abs(modified_z_scores) > threshold
# 标记异常值
data['is_outlier'] = detect_outliers_mad(data['returns'])
经验之谈:对于高频数据(比如5分钟线),异常值往往来自数据记录错误,比如价格突然变成0或者负数。这种直接删除就好。但对于日线数据,异常值可能是真实的市场事件(比如财报发布),需要谨慎处理。
2.4 收益率计算与标准化
波动率预测的核心输入是收益率。怎么算?有两种主流方式:
- 简单收益率: (P_t - P_{t-1}) / P_{t-1}。直观,但不可加。
- 对数收益率: log(P_t / P_{t-1})。可加性更好,且近似正态分布。波动率建模的首选。
我个人强烈建议用对数收益率。为什么?因为波动率模型(比如GARCH)假设收益率是正态分布的,对数收益率更接近这个假设。
# 对数收益率计算
data['log_return'] = np.log(data['price'] / data['price'].shift(1))
# 去掉第一个NaN值
data = data.dropna()
标准化这一步,很多人会忽略。但你想,不同资产的波动率水平不一样,直接比较没有意义。标准化后的收益率,均值为0,方差为1,这样模型才能公平对待每个资产。
# 标准化
mean = data['log_return'].mean()
std = data['log_return'].std()
data['return_standardized'] = (data['log_return'] - mean) / std
注意:标准化时,均值和标准差要用训练集的数据计算,不能用到未来数据。否则就是数据泄露,回测结果会虚高。我见过有人犯这个错,回测曲线漂亮得不像话,实盘直接崩了。
2.5 本章知识体系
下面这张图,概括了数据清洗与预处理的完整流程。你可以把它当作一个检查清单,每次处理数据时对照着来。
2.6 小结
数据清洗,说白了就是跟「脏数据」斗智斗勇。这一章我们讲了:
- 数据获取时要注意复权和时间对齐
- 缺失值处理要区分连续缺失长度
- 异常值检测推荐用MAD方法
- 收益率计算首选对数收益率
- 标准化要避免数据泄露
嗯,这些基础打牢了,后面建模才能稳。下一章,咱们开始真正进入波动率模型的世界。