3、数据获取与清洗:金融数据源与预处理全流程

做因子投资,有一句老话叫「垃圾进,垃圾出」。你策略再牛,模型再花哨,数据一塌糊涂,结果就是白忙活。我刚开始做量化那会儿,就吃过这个亏——从某个数据源拉了一堆日频数据,回测曲线漂亮得不行,结果实盘一跑,发现复权价格算错了。嗯,从那以后,我对数据清洗这件事,再也不敢马虎。

这一章,咱们就聊聊数据从哪来,拿到手之后怎么「洗」干净。说白了,就是让原始数据变成你能放心用的样子。

3.1 主流金融数据源对比

国内做量化,常用的数据源就那么几个。我列个表,你一看就明白。

数据源特点适用场景成本
Wind机构级,数据全,接口稳定私募、券商、专业机构高(年费数万)
Tushare社区驱动,免费/积分制个人研究、小团队低(积分可免费获取)
Yahoo Finance全球数据,接口简单跨境策略、美股研究免费

我个人习惯是:生产环境用Wind,数据质量有保障;研究阶段用Tushare,灵活且免费;做全球配置时,Yahoo Finance 是个不错的补充。但要注意,Yahoo的数据偶尔会有缺失,尤其是分红除权日附近。

小提示: 如果你刚开始做因子研究,我建议先用Tushare。它提供了从股票日线到财务数据的全套接口,而且社区活跃,遇到问题容易找到答案。

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),每次请求间隔半秒。

避坑指南: 我曾经一次性拉取全A股5000只股票的数据,结果被Tushare封了IP两个小时。后来学乖了,分批拉取,每次不超过100只。

3.3 数据清洗流程:从原始到可用

数据拿到手,别急着用。先走一遍清洗流程。我总结了一个四步法:

  1. 格式统一:日期格式、列名、数据类型
  2. 缺失值处理:识别、填充或删除
  3. 异常值处理:识别、修正或剔除
  4. 一致性检查:复权价格、涨跌幅计算

你想想看,如果日期列有的是字符串,有的是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'))
重点: 千万不要对整个数据集统一填充。一定要按股票分组(groupby),否则会把不同股票的数值混在一起,造成严重的未来函数问题。

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 知识体系总览

为了让你更直观地理解本章的知识结构,我画了一张流程图。它展示了从数据源到清洗完成的完整链路。

数据获取与清洗流程 金融数据源 Wind Tushare Yahoo Finance 数据获取(API调用、批量拉取) 数据清洗流程 格式统一 缺失值处理 异常值处理 一致性检查 清洗完成,可用于因子计算

这张图把整个流程串起来了。你从左边选择数据源,拉取数据后,经过四个清洗步骤,最终得到干净的数据。每一步都很关键,缺一不可。

3.7 一些实战中的小经验

最后,分享几个我在实际项目中踩过的坑,希望能帮你少走弯路:

  • 复权价格一定要用后复权。前复权会导致历史价格不断变化,影响因子回测的稳定性。我见过有人用前复权做回测,结果每次跑出来的结果都不一样。
  • 财务数据要注意财报发布日期。不要用未来数据。比如2023年的一季报,实际发布日期可能是2023年4月底,你在回测时不能提前使用。
  • 数据清洗要写日志。每次清洗了多少缺失值、多少异常值,都记录下来。这样出了问题,你能快速定位。
我的习惯: 每次清洗完数据,我都会做一个简单的统计报告,包括缺失率、异常率、数据量等。这样后续分析时,心里有底。

好了,数据获取与清洗这部分就聊到这儿。记住一句话:数据干净,因子才可靠。下一章,咱们会基于清洗好的数据,开始构建真正的因子。


公众号:蓝海资料掘金营,微信deep3321