第三章:数据获取与清洗——金融数据源介绍、Pandas基础操作、数据清洗实战
做量化投资,有一句老话叫「垃圾进,垃圾出」。你模型再牛,策略再花哨,数据源出了问题,一切都是白搭。我刚开始做多因子模型那会儿,就吃过这个亏——从某数据商拿到的日频行情,居然有3%的股票代码是错的,回测曲线漂亮得不行,实盘一跑直接崩。从那以后,我养成了一个习惯:花70%的时间在数据清洗上,只留30%给建模。
这一章,咱们就聊聊数据获取与清洗。说白了,就是教你如何把「脏数据」变成「干净数据」,为后续的Alpha信号挖掘打好地基。
3.1 金融数据源介绍
金融数据源五花八门,我按自己的经验把它们分成三类:免费公开的、半商业的、以及专业付费的。
| 类型 | 代表数据源 | 适用场景 | 我的评价 |
|---|---|---|---|
| 免费公开 | Tushare、AKShare、Yahoo Finance | 学习、回测、小规模研究 | 够用,但数据质量参差不齐 |
| 半商业 | Wind(万得)、Choice(东方财富) | 机构研究、中大型策略 | 数据较全,但接口限制多 |
| 专业付费 | Bloomberg、Quandl、FactSet | 高频交易、全球市场 | 贵,但数据质量极高 |
我个人习惯用Tushare做快速原型验证。为什么呢?因为它免费、文档全、社区活跃。但注意,免费数据有个坑——后复权价格经常出现跳变。我曾经用Tushare的复权数据跑了一个动量因子,结果发现因子收益异常高,一查才发现是复权算法在除权日产生了「伪信号」。嗯,这里要提醒你:永远不要完全信任任何数据源,一定要做交叉验证。
3.2 Pandas基础操作——你每天都会用的工具
Pandas是量化分析的瑞士军刀。你想想看,从数据读取、清洗、到因子计算,几乎每一步都离不开它。我见过不少新手,一上来就学各种花哨的模型,结果连DataFrame的合并都搞不明白——这不行。
咱们快速过一遍最核心的操作:
3.2.1 数据读取
import pandas as pd
import tushare as ts
# 获取股票日线数据
df = ts.get_k_data('000001.SZ', start='2023-01-01', end='2023-12-31')
print(df.head())
这里有个细节:ts.get_k_data返回的是普通DataFrame,但金融数据通常需要设置时间索引。我建议你养成习惯,一拿到数据就做索引转换:
df['date'] = pd.to_datetime(df['date'])
df.set_index('date', inplace=True)
3.2.2 数据对齐——多因子模型的核心
做多因子模型时,你经常需要把不同频率的数据对齐。比如,日频的收益率和月频的财务数据。Pandas的reindex和align方法就是干这个的。
# 假设我们有日频价格数据和月频的市盈率数据
price_df = pd.DataFrame(...) # 日频
pe_df = pd.DataFrame(...) # 月频
# 将月频数据向前填充到日频
pe_daily = pe_df.reindex(price_df.index, method='ffill')
为什么要用ffill(向前填充)?因为财务数据发布有滞后性。比如3月份的财报,可能4月底才公布。如果你用3月底的市盈率去预测4月初的收益,那就犯了未来函数的错误。我早期做因子时,就因为没注意这个对齐问题,回测收益虚高了20%。
3.3 数据清洗实战——去重、填充、对齐
数据清洗,说白了就是三件事:去重、填充、对齐。咱们一个一个来。
3.3.1 去重
金融数据经常出现重复行。比如,某天交易所数据推送了两次,或者你从不同源拉到了同一时间段的数据。
# 检查重复行
print(df.duplicated().sum())
# 删除重复行
df.drop_duplicates(inplace=True)
但注意,不是所有重复都是错的。比如,某只股票在一天内发生了多次交易,那每一笔交易都是合法的。这时候,你需要根据业务逻辑来判断:是保留最后一条?还是取平均值?我个人的经验是:先按「股票+日期」分组,再决定去重策略。
3.3.2 填充缺失值
缺失值在金融数据里太常见了。停牌、节假日、数据源故障,都会导致NaN。常见的填充方法有:
- 向前填充(ffill):用上一个有效值填充。适用于价格数据,因为停牌期间价格不变。
- 向后填充(bfill):用下一个有效值填充。适用于某些财务指标,比如「最新公告日期」。
- 插值法:用前后值的线性插值。适用于连续变化的指标,比如成交量。
- 填充固定值:比如用0填充缺失的成交量,用行业均值填充缺失的市盈率。
# 向前填充
df['close'].fillna(method='ffill', inplace=True)
# 用行业均值填充市盈率
df['pe'].fillna(df.groupby('industry')['pe'].transform('mean'), inplace=True)
这里有个坑:不要盲目填充。如果某只股票连续停牌超过20天,它的价格数据可能已经失真了。我建议你设置一个阈值:如果缺失比例超过30%,直接删除该股票,而不是强行填充。
3.3.3 对齐——多因子模型的命脉
对齐不仅仅是时间对齐,还包括股票代码对齐和频率对齐。
举个例子:你有一个因子数据框,包含1000只股票的日频因子值;另一个是收益率数据框,包含500只股票的日频收益率。你需要把它们对齐到同一个股票池和时间轴上。
# 取两个数据框的交集
common_stocks = set(factor_df.columns) & set(return_df.columns)
common_dates = factor_df.index.intersection(return_df.index)
# 对齐
factor_aligned = factor_df.loc[common_dates, common_stocks]
return_aligned = return_df.loc[common_dates, common_stocks]
为什么要取交集?因为如果某只股票在因子数据里有,但在收益率数据里没有,那它就无法参与回测。强行保留只会引入偏差。
3.4 本章知识体系
下面这张图,是我自己总结的数据清洗流程。你可以把它当作一个检查清单,每次做数据清洗时,按这个流程走一遍,基本不会漏掉关键步骤。
这张图里,我特意加了一个「循环箭头」。为什么?因为数据清洗很少能一次搞定。你做完质量检查,发现异常值太多,就得回去重新调整填充策略或去重规则。这是一个迭代的过程,别指望一步到位。
好了,这一章的内容就到这里。数据清洗虽然枯燥,但它是整个多因子模型的基石。你花在清洗上的每一分钟,都会在后续的建模和回测中得到回报。
公众号:蓝海资料掘金营,微信deep3321