第二章:数据准备与清洗——金融数据的“洗菜”功夫
做量化研究,有个段子说得好:80%的时间在搞数据,20%的时间在后悔没搞好数据。我个人做了这么多年因子挖掘,深以为然。数据准备与清洗,说白了就是给金融数据“洗菜切菜”的过程——菜不干净,再好的厨艺也白搭。
核心观点:金融数据质量直接决定因子有效性。垃圾进,垃圾出(Garbage In, Garbage Out),这是量化界的铁律。
2.1 金融数据获取:从哪里“进货”?
做A股研究,主流的数据源就两个:Tushare和Wind。我两个都用过,说说我的感受。
2.1.1 Tushare——开源党的最爱
Tushare是免费开源的Python接口,适合个人研究。你想想看,不用花钱就能拿到日线数据、财务数据、指数成分股,对学生党和小团队来说简直是福音。
import tushare as ts
# 设置token(去官网注册获取)
ts.set_token('你的token')
# 初始化接口
pro = ts.pro_api()
# 获取平安银行日线数据
df = pro.daily(ts_code='000001.SZ', start_date='20200101', end_date='20231231')
print(df.head())
我的经验:Tushare的积分制度要注意。基础数据免费,但高频数据、财务明细需要积分。我刚开始做研究时,积分不够,只能手动拼接数据,那叫一个痛苦。
2.1.2 Wind——机构标配
Wind(万得)是金融终端,数据全、质量高,但贵。一年几万块的费用,个人用户基本扛不住。不过如果你在机构工作,Wind是绕不开的。
from WindPy import w
# 启动Wind接口
w.start()
# 获取贵州茅台日线数据
data = w.wsd("600519.SH", "open,high,low,close,volume", "2020-01-01", "2023-12-31")
print(data.Data)
注意:Wind接口需要先启动Wind客户端,否则会报错。我曾经在服务器上跑脚本,忘了开客户端,排查了半天才发现问题。
2.1.3 数据获取的避坑指南
- 复权问题:股票有分红送股,不复权的价格是失真的。我个人习惯用后复权,这样价格序列连续可比较。
- 停牌处理:停牌期间没有交易数据,直接填充NaN或者前值?嗯,这里要注意,不同因子处理方法不同。
- 数据频率:日线、周线、月线,频率越高噪声越大,但信号也越及时。我建议先从日线开始。
2.2 数据清洗:去极值、缺失值处理
数据拿到手,别急着跑模型。先看看数据干不干净。我见过太多人,数据里一堆异常值,跑出来的因子回测曲线漂亮得不行,实盘一跑就崩。为什么?因为那些收益全是数据错误带来的假象。
2.2.1 去极值——把“疯子”揪出来
金融数据里经常出现极端值,比如某天涨了100倍(数据错误),或者某只小盘股突然暴涨(真实事件)。这些极端值会严重干扰因子计算。
常用的方法有两种:
| 方法 | 原理 | 适用场景 |
|---|---|---|
| MAD法(中位数绝对偏差) | 用中位数代替均值,更稳健 | 数据分布不对称时 |
| 百分位法 | 直接截断上下1%或5% | 简单粗暴,快速处理 |
import numpy as np
import pandas as pd
def winsorize_series(s, limits=[0.01, 0.01]):
"""
去极值:百分位截断法
limits: [下界百分位, 上界百分位]
"""
lower = s.quantile(limits[0])
upper = s.quantile(1 - limits[1])
return s.clip(lower, upper)
# 示例:对市盈率去极值
pe_series = pd.Series([10, 15, 20, 1000, 12, 18]) # 1000是异常值
cleaned_pe = winsorize_series(pe_series)
print(cleaned_pe)
我的习惯:我个人更推荐MAD法。为什么?因为百分位法太“硬”,直接把数据砍掉,可能会丢失真实信息。MAD法基于中位数,对异常值不敏感,处理起来更温和。
2.2.2 缺失值处理——别让“空洞”毁了你的因子
金融数据缺失是常态。新股上市第一天没有历史数据、停牌期间没有交易数据、财务数据发布有滞后……这些都会产生NaN。
处理缺失值,我总结了三板斧:
- 直接删除:如果缺失比例很小(比如<5%),直接删掉整行。简单有效。
- 向前填充:用上一个有效值填充。适合财务数据,因为财报数据是“直到下次更新前都有效”。
- 插值法:用前后数据线性插值。适合时间序列数据,比如日收益率。
# 缺失值处理示例
df = pd.DataFrame({
'date': ['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04'],
'return': [0.01, np.nan, 0.02, np.nan]
})
# 方法1:删除
df_drop = df.dropna()
# 方法2:向前填充
df_ffill = df.fillna(method='ffill')
# 方法3:线性插值
df_interp = df.interpolate()
print("原始数据:\n", df)
print("\n向前填充:\n", df_ffill)
我曾经踩过的坑:有一次做因子回测,用了向前填充处理缺失值,结果发现停牌期间的数据被错误地填充了前一天的收盘价。这导致因子在停牌期间“看起来”很稳定,但实际上全是假数据。后来我改成:停牌期间的数据单独标记,不参与因子计算。
2.3 数据标准化:让不同量纲的数据“对话”
你想想看,市盈率可能是10-100的量级,换手率是0.1%-5%的量级,市值是几十亿到几万亿的量级。如果不做标准化,直接把这些数据放进模型,市值大的因子会主导一切,其他因子全被淹没。
标准化的本质,就是让不同量纲的数据变得“可比较”。
2.3.1 Z-score标准化
最常用的方法。公式很简单:减去均值,除以标准差。处理后数据均值为0,标准差为1。
def zscore_standardize(s):
"""Z-score标准化"""
return (s - s.mean()) / s.std()
# 示例
returns = pd.Series([0.01, 0.02, -0.01, 0.03, 0.005])
standardized_returns = zscore_standardize(returns)
print(standardized_returns)
2.3.2 排序标准化(Rank标准化)
Z-score对异常值敏感。如果数据里有极端值,标准化后仍然会有极端值。这时候可以用排序标准化:把数据转换成0-1之间的均匀分布。
def rank_standardize(s):
"""排序标准化:将数据映射到[0,1]区间"""
return s.rank(pct=True)
# 示例
pe_ratios = pd.Series([10, 15, 20, 100, 12, 18])
ranked_pe = rank_standardize(pe_ratios)
print(ranked_pe)
我的建议:做截面因子(比如横截面多因子模型)时,我强烈推荐排序标准化。为什么?因为金融数据往往有厚尾分布,Z-score处理后的数据仍然可能有极端值,而排序标准化直接把所有股票拉到同一起跑线。
2.4 本章知识体系总览
说了这么多,我画了一张图帮你理清思路。数据准备与清洗,说白了就是三个步骤:获取→清洗→标准化。每一步都有坑,每一步都有技巧。
2.5 本章小结
数据准备与清洗,听起来枯燥,但这是量化研究的基石。我个人做了这么多年因子挖掘,见过太多人在这上面栽跟头。有的数据没复权,回测曲线漂亮得不行,实盘一跑就崩;有的缺失值处理不当,因子在停牌期间全是假信号;有的没做标准化,模型被市值因子主导……
嗯,这些坑我都踩过。所以这一章我讲得特别细,就是希望你能少走弯路。记住一句话:数据准备花的时间,会在后续的因子分析和回测中十倍百倍地回报你。
核心公式回顾:
Z-score标准化:\( z = \frac{x - \mu}{\sigma} \)
排序标准化:\( rank\_pct = \frac{rank(x)}{N} \)
MAD去极值:\( |x - median| > 3 \times MAD \) 视为异常值