2. 数据获取与清洗
做量化的人都知道一句话:垃圾进,垃圾出。模型再漂亮,数据一塌糊涂,结果就是自欺欺人。这一章,咱们就聊聊怎么把股票数据搞到手,再把它收拾得服服帖帖。
我个人习惯把数据获取和清洗看作一个整体流程。你想想看,从 API 拉下来的数据,哪有一次就能直接用的?缺失值、极端值、量纲不统一……这些问题不解决,后面的因子计算全是白搭。
核心流程:数据获取 → 缺失值处理 → 去极值 → 标准化。每一步都有坑,咱们一个一个踩过去。
2.1 用 pandas-datareader 拉数据
pandas-datareader 这个库,说白了就是帮你从各种金融数据源拿数据的工具。我最早用的时候还是 0.6 版本,现在都更新好几轮了。它支持 Yahoo Finance、FRED、World Bank 等等,咱们做 A 股的话,常用的是 Yahoo 接口(虽然偶尔会抽风)。
先装好库:
pip install pandas-datareader yfinance
嗯,这里要注意。Yahoo 的接口近几年不太稳定,我建议配合 yfinance 一起用,作为备选数据源。下面是一个典型的获取代码:
import pandas as pd
import pandas_datareader.data as web
import datetime
start = datetime.datetime(2020, 1, 1)
end = datetime.datetime(2023, 12, 31)
# 获取贵州茅台的历史数据
df = web.DataReader('600519.SS', 'yahoo', start, end)
print(df.head())
跑完之后你会得到一个 DataFrame,列名包括 Open、High、Low、Close、Volume、Adj Close。这里我提醒一句:记得用复权价格。Adj Close 就是后复权收盘价,剔除了分红送股的影响。我在项目中遇到过有人直接用 Close 算收益率,结果回测曲线漂亮得不行,实盘一跑就崩——就是因为没复权。
小技巧:如果 Yahoo 数据拉不下来,试试把代码里的 'yahoo' 换成 'stooq',或者直接用 yfinance 的 download 方法。多准备几个数据源,别在一棵树上吊死。
2.2 处理缺失值:别让空值毁了你的因子
数据拉下来,第一件事就是检查缺失值。股票数据里缺失值很常见——停牌、节假日、数据源本身的问题,都会导致 NaN。
我一般用这个套路:
# 查看缺失值情况
print(df.isnull().sum())
# 前向填充(用上一个交易日的数据补)
df.fillna(method='ffill', inplace=True)
# 如果开头还有缺失,用后向填充
df.fillna(method='bfill', inplace=True)
为什么用前向填充?因为金融数据有连续性。今天停牌了,用昨天的价格来补,逻辑上说得通。但千万别用均值填充——你想想看,某天茅台突然跌停,你用过去 30 天的均价去填,那因子值就失真了。
我曾经在一个多因子项目里,因为没处理好停牌股的缺失值,导致某个因子的 IC(信息系数)直接变成负的。排查了两天才发现,原来是停牌期间用均值填充把因子值拉偏了。从那以后,我对缺失值处理就格外小心。
避坑指南:如果某只股票连续缺失超过 5 个交易日,我建议直接剔除该股票。连续停牌超过一周,复牌后大概率补跌或补涨,这种极端情况会严重干扰因子计算。
2.3 去极值:把那些离谱的值拉回来
极值,就是那些明显不合理的数据点。比如某只股票一天涨了 50%,或者市盈率突然变成 10000 倍。这些值如果不处理,会严重扭曲后续的标准化和模型训练。
常用的去极值方法有三种:
| 方法 | 原理 | 适用场景 |
|---|---|---|
| MAD 法 | 基于中位数绝对偏差,稳健性强 | 因子值分布不对称时 |
| 百分位法 | 直接截断上下 1% 或 5% 的数据 | 快速清洗,简单粗暴 |
| 标准差法 | 超过均值 ±3σ 的值被截断 | 数据近似正态分布时 |
我个人最常用的是 MAD 法。为什么?因为它不受极端值本身的影响。标准差法有个问题——如果数据里已经有一个超级大的极值,它会拉高标准差,导致阈值变宽,反而漏掉一些异常点。
MAD 法的代码实现:
def winsorize_mad(series, n=5):
median = series.median()
mad = (series - median).abs().median()
upper = median + n * mad
lower = median - n * mad
return series.clip(lower, upper)
# 对市盈率因子去极值
df['pe_winsorized'] = winsorize_mad(df['pe_ratio'])
这里的 n 值我一般取 5。取小了,会把正常值也截掉;取大了,极值去不干净。你可以根据因子分布情况微调,但 5 是个不错的起点。
关键点:去极值不是删除数据,而是把极端值拉到阈值边界。这样既保留了样本量,又避免了极端值对后续计算的干扰。
2.4 标准化:让不同因子站在同一起跑线
标准化,说白了就是把不同量纲的因子值统一到同一个尺度上。比如市盈率可能是 10-50,换手率可能是 0.1%-5%,如果不标准化,模型会天然地给数值大的因子更高权重——这显然不合理。
常用的标准化方法:
- Z-score 标准化:减去均值,除以标准差。结果服从均值为 0、标准差为 1 的分布。
- Min-Max 标准化:缩放到 [0, 1] 区间。对极值敏感,一般先去极值再用。
- Rank 标准化:把因子值转为排名,再映射到 [0, 1]。稳健性最强,但丢失了数值间的距离信息。
我建议在因子选股场景下优先用 Z-score 标准化。因为它保留了因子值的相对大小关系,而且很多统计模型(比如线性回归)天然假设输入是标准正态分布。
from scipy import stats
def zscore_standardize(series):
return (series - series.mean()) / series.std()
# 对去极值后的因子做标准化
df['pe_standardized'] = zscore_standardize(df['pe_winsorized'])
# 也可以直接用 scipy 的 zscore
df['pe_zscore'] = stats.zscore(df['pe_winsorized'])
这里有个细节:标准化要在截面(cross-section)上做,也就是每个时间点单独做。你不能把 2020 年和 2023 年的数据混在一起标准化——不同时期的市场环境不同,均值方差都会变。我见过有人把整个时间序列拉平做标准化,结果因子值在不同年份之间不可比,回测结果一塌糊涂。
我的习惯:先分组(按日期),再对每个组内的因子做标准化。用 groupby 加 transform 可以一行搞定:df['factor_z'] = df.groupby('date')['factor'].transform(lambda x: stats.zscore(x))
2.5 完整流程:一张图看懂
说了这么多,咱们用一张图把整个数据获取与清洗的流程串起来。这张图我每次做新项目都会贴在白板上,提醒自己别漏了哪一步。
这张图把四个核心步骤串起来了。每一步都有多种方法可选,我标注了推荐方案和注意事项。你可以在实际项目中根据数据情况灵活调整,但流程顺序不要乱——先处理缺失值,再去极值,最后标准化。顺序错了,效果会大打折扣。
2.6 实战小贴士:批量处理多只股票
实际做多因子模型,不可能只处理一只股票。我一般会维护一个股票池,比如沪深 300 的成分股,然后批量获取和清洗数据。
# 批量获取多只股票数据
tickers = ['600519.SS', '000858.SZ', '600036.SS'] # 示例股票池
data_dict = {}
for ticker in tickers:
df = web.DataReader(ticker, 'yahoo', start, end)
df['ticker'] = ticker
data_dict[ticker] = df
# 合并成一个 DataFrame
all_data = pd.concat(data_dict.values())
all_data.reset_index(inplace=True)
# 按日期分组,对每个因子做截面标准化
factor_cols = ['pe_ratio', 'pb_ratio', 'turnover']
for col in factor_cols:
all_data[col + '_z'] = all_data.groupby('Date')[col].transform(
lambda x: stats.zscore(x.clip(lower=x.quantile(0.01), upper=x.quantile(0.99)))
)
这段代码里我顺便做了去极值(百分位法)和标准化,一步到位。当然,你也可以拆开写,更清晰一些。我个人习惯在批量处理时用这种链式操作,代码短,跑起来也快。
注意:批量处理时一定要关注内存。沪深 300 的日线数据,三年下来大概几十万行,还好。但如果做全市场 4000 多只股票,数据量就上来了。我建议分批次处理,或者用数据库来存中间结果。
好了,数据获取与清洗这部分就聊到这儿。数据干净了,下一步才能安心地计算因子。记住一句话:花 80% 的时间在数据上,剩下 20% 的时间建模,这才是量化投资的正确姿势。