3、数据获取与清洗:金融数据源与预处理全流程
做因子投资,有一句老话叫「垃圾进,垃圾出」。你策略再牛,模型再花哨,数据一塌糊涂,结果就是白忙活。我刚开始做量化那会儿,就吃过这个亏——从某个数据源拉了一堆日频数据,回测曲线漂亮得不行,结果实盘一跑,发现复权价格算错了。嗯,从那以后,我对数据清洗这件事,再也不敢马虎。
这一章,咱们就聊聊数据从哪来,拿到手之后怎么「洗」干净。说白了,就是让原始数据变成你能放心用的样子。
3.1 主流金融数据源对比
国内做量化,常用的数据源就那么几个。我列个表,你一看就明白。
| 数据源 | 特点 | 适用场景 | 成本 |
|---|---|---|---|
| Wind | 机构级,数据全,接口稳定 | 私募、券商、专业机构 | 高(年费数万) |
| Tushare | 社区驱动,免费/积分制 | 个人研究、小团队 | 低(积分可免费获取) |
| Yahoo Finance | 全球数据,接口简单 | 跨境策略、美股研究 | 免费 |
我个人习惯是:生产环境用Wind,数据质量有保障;研究阶段用Tushare,灵活且免费;做全球配置时,Yahoo Finance 是个不错的补充。但要注意,Yahoo的数据偶尔会有缺失,尤其是分红除权日附近。
3.2 数据获取实战:以Tushare为例
咱们直接上代码。假设你要获取沪深300成分股的日线数据,并计算简单的收益率因子。
import tushare as ts
import pandas as pd
import numpy as np
# 设置token(你需要在tushare官网注册获取)
ts.set_token('你的token')
pro = ts.pro_api()
# 获取沪深300成分股列表
hs300 = pro.index_weight(index_code='000300.SH', start_date='20230101', end_date='20231231')
stocks = hs300['con_code'].tolist()
# 获取日线数据(只取前5只做演示)
df_list = []
for code in stocks[:5]:
df = pro.daily(ts_code=code, start_date='20230101', end_date='20231231')
df_list.append(df)
# 合并数据
all_data = pd.concat(df_list, ignore_index=True)
print(all_data.head())
这段代码跑起来很快。但我在项目中遇到过一个问题:Tushare的接口有频率限制,如果你一次性拉太多股票,会被限流。我的解决办法是加个time.sleep(0.5),每次请求间隔半秒。
3.3 数据清洗流程:从原始到可用
数据拿到手,别急着用。先走一遍清洗流程。我总结了一个四步法:
- 格式统一:日期格式、列名、数据类型
- 缺失值处理:识别、填充或删除
- 异常值处理:识别、修正或剔除
- 一致性检查:复权价格、涨跌幅计算
你想想看,如果日期列有的是字符串,有的是datetime,后面做时间序列分析时,光类型转换就够你头疼的。所以第一步,先把格式统一了。
# 格式统一示例
all_data['trade_date'] = pd.to_datetime(all_data['trade_date'])
all_data = all_data.sort_values(['ts_code', 'trade_date']).reset_index(drop=True)
3.4 缺失值处理:别让NaN毁了你的因子
缺失值在金融数据里太常见了。停牌、新股上市、数据源漏传,都会导致NaN。处理方式有三种:
- 直接删除:如果缺失比例很小(比如<1%),直接dropna()最省事
- 向前填充:用上一个交易日的数据填充,适合价格类数据
- 插值法:线性插值或时间加权插值,适合连续指标
我个人习惯是:价格类数据用向前填充,因为停牌期间价格不变;财务类数据用插值,因为财报是季度发布的,中间月份需要平滑处理。
# 向前填充价格缺失值
all_data['close'] = all_data.groupby('ts_code')['close'].ffill()
# 线性插值处理财务指标
all_data['pe_ttm'] = all_data.groupby('ts_code')['pe_ttm'].apply(lambda x: x.interpolate(method='linear'))
3.5 异常值处理:揪出那些离谱的数据
异常值,说白了就是那些「一看就不对」的数据。比如某只股票一天涨了1000%,或者市盈率突然变成负数几万倍。这些数据如果不处理,会严重扭曲你的因子计算结果。
常用的检测方法:
- 3σ原则:超过均值±3倍标准差的数据视为异常
- IQR方法:低于Q1-1.5*IQR或高于Q3+1.5*IQR的数据
- 业务规则:比如涨跌幅超过±20%的(A股涨停板限制)
我在项目中遇到过最典型的案例:某只股票因为数据源错误,把收盘价多打了一个0,导致当天的收益率计算为900%。用3σ方法轻松就揪出来了。
# 使用IQR方法处理异常值
def detect_outliers_iqr(series):
Q1 = series.quantile(0.25)
Q3 = series.quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
return (series < lower_bound) | (series > upper_bound)
# 按股票分组检测异常收益率
all_data['return'] = all_data.groupby('ts_code')['close'].pct_change()
all_data['outlier'] = all_data.groupby('ts_code')['return'].transform(detect_outliers_iqr)
# 将异常值替换为NaN,然后填充
all_data.loc[all_data['outlier'], 'return'] = np.nan
all_data['return'] = all_data.groupby('ts_code')['return'].ffill()
3.6 知识体系总览
为了让你更直观地理解本章的知识结构,我画了一张流程图。它展示了从数据源到清洗完成的完整链路。
这张图把整个流程串起来了。你从左边选择数据源,拉取数据后,经过四个清洗步骤,最终得到干净的数据。每一步都很关键,缺一不可。
3.7 一些实战中的小经验
最后,分享几个我在实际项目中踩过的坑,希望能帮你少走弯路:
- 复权价格一定要用后复权。前复权会导致历史价格不断变化,影响因子回测的稳定性。我见过有人用前复权做回测,结果每次跑出来的结果都不一样。
- 财务数据要注意财报发布日期。不要用未来数据。比如2023年的一季报,实际发布日期可能是2023年4月底,你在回测时不能提前使用。
- 数据清洗要写日志。每次清洗了多少缺失值、多少异常值,都记录下来。这样出了问题,你能快速定位。
好了,数据获取与清洗这部分就聊到这儿。记住一句话:数据干净,因子才可靠。下一章,咱们会基于清洗好的数据,开始构建真正的因子。
公众号:蓝海资料掘金营,微信deep3321