第二章:数据准备与预处理——金融数据的“洗菜”功夫

做量化分析,有个老生常谈的道理:垃圾进,垃圾出。你模型再牛,策略再花哨,数据一塌糊涂,结果就是白忙活。我刚开始做量化那会儿,就吃过这个亏——拿了一堆没清洗的数据跑回测,收益曲线漂亮得不行,结果实盘一跑,直接傻眼。后来才发现,是数据里藏着几个异常值,把整个策略都给骗了。

所以,今天咱们就来聊聊金融数据预处理这档子事。说白了,就是怎么把“带泥的菜”洗干净,让它能下锅。

核心要点:金融数据预处理 = 获取 + 清洗 + 填充 + 对齐。一步不到位,后面全白费。

2.1 数据获取:从Yahoo Finance“捞”数据

我个人习惯用 yfinance 这个库,它是对 Yahoo Finance API 的 Python 封装,用起来特别顺手。你想想看,一行代码就能把股票历史数据拉下来,多方便。

import yfinance as yf
import pandas as pd
import numpy as np

# 下载苹果公司股票数据
aapl = yf.download('AAPL', start='2020-01-01', end='2023-12-31')
print(aapl.head())

嗯,这里要注意:yfinance 返回的是 pandas DataFrame,列名包括 OpenHighLowCloseVolumeAdj Close。其中 Adj Close 是复权收盘价,做回测时我建议用这个,因为它考虑了分红、拆股等因素。

小技巧:如果你要下载多只股票,可以传一个列表进去:yf.download(['AAPL', 'MSFT', 'GOOGL'], start='...')。返回的 DataFrame 会带有多层索引,处理起来稍微麻烦点,但习惯就好。

2.2 数据清洗:把“脏东西”筛出去

数据拉下来之后,第一件事就是看看有没有脏数据。我在项目中遇到过好几次,明明看着好好的数据,一检查发现全是坑。

常见的脏数据包括:

  • 重复行:同一个交易日出现两次
  • 零值或负值:价格不可能为0或负数(除非是某些衍生品)
  • 跳空或异常波动:比如某天突然涨了1000%,多半是数据错误
# 检查重复行
print(f"重复行数量: {aapl.duplicated().sum()}")

# 删除重复行
aapl = aapl.drop_duplicates()

# 检查零值或负值
print(f"零值数量: {(aapl['Close'] == 0).sum()}")
print(f"负值数量: {(aapl['Close'] < 0).sum()}")

# 过滤掉异常值(比如单日涨跌幅超过20%)
daily_returns = aapl['Close'].pct_change()
aapl = aapl[abs(daily_returns) < 0.2]
避坑指南:我曾经因为没检查重复行,导致回测时同一个交易日的信号被重复计算,收益率直接翻倍。嗯,从那以后,我每次拿到数据第一件事就是 duplicated()

2.3 缺失值处理:别让“空洞”毁了你的模型

金融数据里,缺失值太常见了。节假日、停牌、数据源故障,都会导致某天没有数据。你想想看,如果模型遇到缺失值直接报错,那还怎么玩?

处理缺失值,我一般分三步走:

  1. 先看看缺失情况:用 isnull().sum() 统计每列的缺失数量
  2. 判断缺失原因:是随机缺失,还是结构性缺失(比如某只股票退市了)
  3. 选择填充方法:常用的有向前填充、插值法、或者直接用均值/中位数填充
# 查看缺失值
print(aapl.isnull().sum())

# 向前填充(用上一个交易日的数据填充)
aapl.fillna(method='ffill', inplace=True)

# 或者用线性插值
aapl.interpolate(method='linear', inplace=True)
个人经验:对于金融时间序列,我强烈推荐用 ffill(向前填充)。为什么呢?因为金融数据有很强的连续性,今天的缺失值用昨天的数据来补,比用均值更合理。比如某天停牌,复牌后的价格通常和停牌前有延续性。

2.4 重采样与对齐:让不同频率的数据“步调一致”

做量化分析,经常会遇到不同频率的数据。比如股票是日频数据,宏观经济指标是月频数据,你要把它们放在一起分析,就得先对齐。

重采样,说白了就是把数据从一种频率转换成另一种频率。比如把日频数据转成周频:

# 将日频数据重采样为周频(取每周最后一个交易日)
weekly_data = aapl.resample('W').last()

# 或者取每周的平均值
weekly_mean = aapl.resample('W').mean()

对齐就更简单了,直接用 pd.concat 或者 merge 就行。但要注意,不同数据源的日期格式可能不一样,得先统一成 datetime 类型。

# 假设我们有两只股票的数据
aapl = yf.download('AAPL', start='2020-01-01', end='2023-12-31')
msft = yf.download('MSFT', start='2020-01-01', end='2023-12-31')

# 只保留两者都有的交易日
combined = pd.concat({'AAPL': aapl['Close'], 'MSFT': msft['Close']}, axis=1)
combined.dropna(inplace=True)  # 去掉缺失值,实现对齐
注意:对齐时千万别用 fillna 硬填。如果某只股票在某个时间段根本没有数据(比如还没上市),硬填只会引入噪声。我建议直接 dropna,或者用 reindex 指定一个共同的日期范围。

2.5 本章知识体系图

下面这张图,把数据预处理的整个流程串起来了。你可以把它当作一个检查清单,每次做数据预处理时对照着来。

金融时间序列数据预处理流程 数据获取 Yahoo Finance API 数据清洗 去重、过滤异常值 缺失值处理 ffill / 插值 重采样 日频 → 周频 / 月频 数据对齐 多源数据合并 干净、对齐的数据集

2.6 本章小结

数据预处理,听起来枯燥,但它是整个量化分析的地基。地基不稳,楼盖得再高也得塌。我个人觉得,花在数据清洗上的时间,至少应该占整个项目时间的30%。别嫌多,这钱花得值。

最后送大家一句话:好的数据,是成功策略的一半。 下次咱们聊特征工程,看看怎么从干净的数据里挖出有用的信号。

蓝海数据掘金营 | 专注资料整理