第三章 数据获取与清洗:金融数据API与数据预处理实战
做量化投研的朋友都知道,数据是策略的命根子。我见过太多人花几个月写策略,结果数据源一换,回测结果直接翻车。今天我们就聊聊数据获取与清洗这档子事。
3.1 金融数据API的选择:Tushare vs AKShare
国内金融数据API,目前最主流的就两个:Tushare和AKShare。我个人两个都用过,各有千秋。
核心观点:Tushare适合专业机构,AKShare适合快速原型开发。但别急着选,先看你的需求。
Tushare Pro
Tushare是老牌数据平台了。我2018年刚开始做量化时就在用。它的特点是数据质量高,字段规范,但需要积分才能获取高级数据。
# 安装与初始化
pip install tushare
import tushare as ts
# 设置token(需要注册获取)
ts.set_token('your_token_here')
pro = ts.pro_api()
# 获取日线数据
df = pro.daily(ts_code='000001.SZ', start_date='20230101', end_date='20231231')
print(df.head())
我的经验:Tushare的token一定要放在配置文件里,别硬编码。我曾经把token直接写在代码里,结果上传到GitHub被公开了...嗯,那之后我养成了用环境变量的习惯。
AKShare
AKShare是后来崛起的开源项目。它最大的优势是免费、接口丰富。但数据格式偶尔会变,需要留意更新。
# 安装
pip install akshare
import akshare as ak
# 获取A股日线数据
df = ak.stock_zh_a_hist(symbol="000001", period="daily",
start_date="20230101", end_date="20231231", adjust="qfq")
print(df.head())
| 对比维度 | Tushare | AKShare |
|---|---|---|
| 数据质量 | 高,字段规范 | 中等,偶有变动 |
| 费用 | 积分制,部分收费 | 完全免费 |
| 接口稳定性 | 稳定 | 偶尔更新 |
| 适合场景 | 生产环境 | 研究原型 |
3.2 数据清洗:从原始数据到可用数据
数据拿到手,别急着用。你想想看,原始数据里什么妖魔鬼怪都有。我遇到过停牌数据、复权数据没对齐、甚至还有日期格式不统一的问题。
3.2.1 数据对齐
做行业轮动,最怕的就是时间戳对不上。比如你拿到的银行板块数据和地产板块数据,交易日可能不一样。
import pandas as pd
# 假设有两个板块的数据
bank_data = pd.DataFrame({
'date': ['2023-01-03', '2023-01-04', '2023-01-05'],
'bank_return': [0.01, -0.02, 0.03]
})
estate_data = pd.DataFrame({
'date': ['2023-01-03', '2023-01-04', '2023-01-05', '2023-01-06'],
'estate_return': [0.02, -0.01, 0.01, -0.005]
})
# 对齐到共同交易日
merged = pd.merge(bank_data, estate_data, on='date', how='inner')
print(merged)
避坑指南:我曾经用'outer'合并,结果回测时发现有些日期只有单边数据,策略信号直接乱了。记住,行业轮动必须用'inner'对齐。
3.2.2 缺失值处理
缺失值在金融数据里太常见了。停牌、节假日、数据源问题都会导致缺失。处理方式有三种:
- 向前填充(ffill):用上一个交易日的数据填充。适合价格数据。
- 向后填充(bfill):用下一个交易日的数据填充。适合某些财务指标。
- 插值法:线性插值,适合连续型指标。
# 模拟有缺失的数据
import numpy as np
data = pd.DataFrame({
'date': pd.date_range('2023-01-01', periods=10, freq='D'),
'price': [100, np.nan, 102, 103, np.nan, 105, 106, np.nan, 108, 109]
})
# 向前填充
data['price_ffill'] = data['price'].ffill()
# 线性插值
data['price_interp'] = data['price'].interpolate()
print(data)
我的建议:对于收益率数据,千万别用插值。收益率是离散事件,插值会引入未来信息。我一般用ffill,然后标记哪些是填充的,回测时单独分析。
3.3 数据清洗的完整流程
好了,我们把上面这些串起来。一个完整的数据清洗流程应该是这样的:
def clean_market_data(raw_data):
"""
清洗市场数据
"""
# 1. 去除重复行
data = raw_data.drop_duplicates()
# 2. 排序
data = data.sort_values('date')
# 3. 处理缺失值
data['return'] = data['return'].ffill()
# 4. 去除异常值(比如涨跌幅超过20%)
data = data[data['return'].between(-0.2, 0.2)]
# 5. 重置索引
data = data.reset_index(drop=True)
return data
核心原则:清洗数据时,宁可少用,不可错用。一条错误数据可能毁掉整个回测。
3.4 知识体系总览
下面这张图,是我做数据清洗时脑子里始终绷着的一根弦。它把整个流程串起来了:
这张图把整个流程分成了三层:数据源层、清洗层、缺失值处理层。每一层都有对应的处理策略。我个人习惯在代码里用函数封装每一层,这样调试起来方便。
3.5 实战中的坑与技巧
最后,分享几个我踩过的坑:
- 数据源切换:别在策略运行中切换数据源。我试过,结果两个API的复权方式不一样,回测结果差了10%。
- 缓存机制:每次请求都去拉数据太慢了。我一般用本地SQLite缓存,每天更新一次。
- 数据校验:清洗完后,一定要做一次校验。比如检查收益率是否在合理范围内,日期是否连续。
重要提醒:千万别相信任何数据源是100%正确的。我见过Tushare和AKShare对同一只股票的同一天数据有差异。所以,交叉验证是必须的。
好了,数据获取与清洗就聊到这里。记住,数据质量决定了策略的上限。花80%的时间在数据上,剩下20%写策略,这个比例我觉得很合理。