第二章:数据获取与清洗——打好量化研究的地基
做量化选股,说白了就是和数据打交道。我见过太多人花大把时间调模型、写策略,结果数据源就有问题——那真是白忙活一场。今天咱们就把数据获取和清洗这块地基打扎实。
核心观点:数据质量决定了策略的上限。模型再牛,也救不了垃圾数据。
2.1 数据获取:选对工具,事半功倍
目前国内做A股数据获取,主流就两个库:akshare 和 tushare。我个人习惯两个都装,因为各有各的脾气。
2.1.1 akshare——免费、轻量、上手快
akshare 的好处是纯免费,不需要token,适合快速验证想法。我在做回测原型时经常用它。
import akshare as ak
# 获取A股日线数据
df = ak.stock_zh_a_hist(symbol="000001",
period="daily",
start_date="20200101",
end_date="20231231",
adjust="qfq") # 前复权
print(df.head())
嗯,这里要注意:akshare 返回的字段名是中文,比如“开盘”、“收盘”。做量化时我习惯统一转成英文,方便后续处理。
2.1.2 tushare——专业、稳定、数据全
tushare 需要注册获取 token,但数据质量更高。我在正式项目中都用它。
import tushare as ts
# 设置token(第一次运行需要)
ts.set_token('your_token_here')
pro = ts.pro_api()
# 获取日线数据
df = pro.daily(ts_code='000001.SZ',
start_date='20200101',
end_date='20231231')
print(df.head())
我的小技巧:写个函数把两个库封装一下,一个挂了自动切另一个。我在项目中遇到过akshare某天接口调整,幸好有tushare兜底。
2.2 数据清洗:脏数据是量化的大敌
数据拿到手,别急着用。先过三关:去极值、处理缺失值、复权处理。
2.2.1 去极值——别让异常值带偏你的模型
股票数据里经常出现极端值,比如某天涨了20%(新股上市),或者跌停。这些会严重干扰因子计算。
我常用的方法是MAD法(中位数绝对偏差),比3σ更稳健。
import numpy as np
import pandas as pd
def winsorize_mad(series, n=5):
"""
MAD法去极值
n: 阈值,默认5倍MAD
"""
median = series.median()
mad = np.median(np.abs(series - median))
upper = median + n * mad
lower = median - n * mad
return series.clip(lower, upper)
# 示例:对市盈率去极值
df['pe_ratio'] = winsorize_mad(df['pe_ratio'])
避坑指南:我曾经直接用3σ去极值,结果遇到某只股票连续涨停,标准差被拉得巨大,极值根本没去掉。后来改用MAD法,效果好了很多。
2.2.2 缺失值处理——别让NaN毁了你的回测
缺失值处理没有银弹。我的经验是:
| 缺失比例 | 处理方法 | 适用场景 |
|---|---|---|
| < 5% | 向前填充(ffill) | 日线数据、财务数据 |
| 5% - 20% | 中位数填充 | 因子数据 |
| > 20% | 直接删除该特征 | 质量太差,留着也没用 |
# 向前填充(最常用)
df.fillna(method='ffill', inplace=True)
# 中位数填充
df['factor_a'].fillna(df['factor_a'].median(), inplace=True)
# 删除缺失过多的列
df.dropna(thresh=len(df)*0.8, axis=1, inplace=True)
2.2.3 复权处理——还原真实收益率
复权是个大坑。很多新手直接用不复权数据算收益率,结果分红送股后收益率曲线完全走样。
我个人建议:做因子分析用前复权,做回测用后复权。
- 前复权:调整历史价格,让价格连续。适合计算因子。
- 后复权:调整当前价格,真实反映收益。适合回测。
# akshare直接支持复权参数
df_qfq = ak.stock_zh_a_hist(symbol="000001", adjust="qfq") # 前复权
df_hfq = ak.stock_zh_a_hist(symbol="000001", adjust="hfq") # 后复权
# tushare需要手动处理
# 获取复权因子
adj_factor = pro.adj_factor(ts_code='000001.SZ')
# 然后自己算复权价格
我的经验:曾经有个策略回测年化30%,实盘直接腰斩。查了半天,原来是复权方式搞错了。从那以后,我每次都会用复权后的数据算一遍收益率,和原始数据对比验证。
2.3 数据存储:选对格式,效率翻倍
数据清洗完了,得存起来。我推荐两种格式:HDF5 和 Parquet。
2.3.1 HDF5——适合高频读写
HDF5 是二进制格式,读写速度快,支持压缩。我在做分钟级别数据时常用。
# 存储
df.to_hdf('stock_data.h5', key='daily', mode='w')
# 读取
df = pd.read_hdf('stock_data.h5', key='daily')
2.3.2 Parquet——列式存储,查询快
Parquet 是列式存储,适合按列查询。如果你经常只取某几个因子,Parquet 比 HDF5 快很多。
# 存储
df.to_parquet('stock_data.parquet')
# 读取
df = pd.read_parquet('stock_data.parquet')
| 对比项 | HDF5 | Parquet |
|---|---|---|
| 读写速度 | 整体快 | 列查询快 |
| 压缩率 | 中等 | 高 |
| 跨语言支持 | 一般 | 好 |
| 我的推荐 | 分钟数据、高频场景 | 因子数据、分析场景 |
总结一下:数据获取用akshare/tushare,清洗过三关(去极值、缺失值、复权),存储选HDF5或Parquet。这套流程我用了三年,基本没出过问题。