数据获取与清洗:数据源选择与质量把控
做量化回测,第一步就是搞数据。这事听起来简单,但我在这个坑里摔过不止一次。数据不对,后面所有策略分析都是白搭——垃圾进,垃圾出,这话在量化领域尤其扎心。
今天咱们就聊聊数据获取和清洗那些事。我会把常用的数据源、清洗套路、质量检查方法都过一遍,顺便分享一些我踩过的坑。
数据源选择:三大主流渠道
国内做量化,常用的数据源就三个:Yahoo Finance、东方财富、Tushare。各有各的脾气,我一个个说。
| 数据源 | 覆盖范围 | 更新频率 | 免费程度 | 稳定性 |
|---|---|---|---|---|
| Yahoo Finance | 全球股票、ETF、指数 | 实时/日线 | 免费 | 中等(偶尔断连) |
| 东方财富 | A股、港股、基金 | 实时/分钟/日线 | 免费 | 较高 |
| Tushare | A股、期货、期权、基金 | 日线/分钟/实时 | 部分免费,高级需积分 | 高 |
Yahoo Finance:全球数据的好选择
我个人习惯用 yfinance 库来拉 Yahoo 的数据。代码简单,几行就能拿到历史行情。
import yfinance as yf
# 获取苹果股票数据
aapl = yf.download('AAPL', start='2020-01-01', end='2023-12-31')
print(aapl.head())
但这里有个坑——Yahoo 的数据偶尔会缺字段,尤其是调整收盘价。我记得有一次做回测,发现策略收益异常高,查了半天才发现是 Yahoo 的调整因子没更新,导致复权数据全是错的。
东方财富:A股数据最全
东方财富的数据接口,说白了就是 akshare 库。它把东方财富的网页接口封装好了,用起来挺顺手。
import akshare as ak
# 获取贵州茅台日线数据
stock_zh_a_hist_df = ak.stock_zh_a_hist(symbol="600519", period="daily",
start_date="20200101", end_date="20231231")
print(stock_zh_a_hist_df.head())
东方财富的数据更新快,基本收盘后几分钟就能拿到。但有个问题——它的字段命名比较随意,比如「日期」有时候叫 date,有时候叫 trade_date。你想想看,如果代码里写死了字段名,换一只股票可能就崩了。
Tushare:专业玩家的首选
Tushare 是我现在的主力数据源。它数据质量高,字段规范,而且支持分钟级数据。不过需要注册获取 token,高级接口要积分。
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 的数据结构很清晰,每个字段都有文档说明。但它的免费额度有限,一天只能调 200 次。如果你要拉全 A 股的历史数据,积分不够的话得等好几天。
数据清洗:去重、填充、对齐
数据拉回来,别急着用。原始数据通常有各种问题——重复行、缺失值、时间戳不对齐。我一般按三步走:去重、填充、对齐。
1. 去重
数据重复的原因很多:接口重试、网络波动、手动追加。重复数据会导致回测结果失真,尤其是计算收益率的时候。
# 按日期去重,保留第一条
df = df.drop_duplicates(subset=['trade_date'], keep='first')
# 重置索引
df = df.reset_index(drop=True)
我曾经遇到过一个问题:某只股票的数据重复了三天,导致移动平均线计算完全走样。嗯,从那以后我每次拉数据都会先跑一遍去重。
2. 填充缺失值
缺失值处理要分情况。如果是非交易日导致的缺失,直接向前填充就行。如果是数据本身缺失,那就得小心了。
# 向前填充(非交易日)
df = df.fillna(method='ffill')
# 如果开盘价缺失,用前一天的收盘价代替
df['open'] = df['open'].fillna(df['close'].shift(1))
3. 时间对齐
多只股票一起回测时,时间戳必须对齐。A 股有涨跌停、停牌、退市等情况,不同股票的交易日期可能不一样。
# 生成完整交易日历
trade_cal = ak.tool_trade_date_hist_sina()
# 对齐到交易日历
df_aligned = df.set_index('trade_date').reindex(trade_cal['trade_date'])
df_aligned = df_aligned.fillna(method='ffill')
说白了,对齐就是让所有股票站在同一条时间线上。不然你算出来的组合收益,时间点都对不上。
数据质量检查:别让脏数据毁了回测
数据清洗完,还得检查质量。我一般做这几项检查:
- 边界值检查:价格不能为负,成交量不能为 0(停牌除外)
- 跳空检查:单日涨跌幅超过 20% 的,大概率是数据错误
- 复权一致性:前复权和后复权的价格序列应该逻辑自洽
- 时间连续性:交易日之间不能有超过 5 天的间隔(长假除外)
# 简单质量检查函数
def check_data_quality(df):
issues = []
# 检查负价格
if (df['close'] < 0).any():
issues.append('存在负价格')
# 检查跳空
returns = df['close'].pct_change()
if (returns.abs() > 0.2).any():
issues.append('存在超过20%的跳空')
# 检查缺失
if df.isnull().sum().sum() > 0:
issues.append('存在缺失值')
return issues
数据获取与清洗流程图
下面这张图概括了从数据获取到清洗完成的完整流程。你可以把它贴在工位旁边,每次做数据准备时对照着走一遍。
避坑指南:我踩过的那些坑
做数据准备这些年,我踩过的坑能写一本书。挑几个典型的说说:
- 复权数据不一致:我曾经用 Yahoo 的前复权数据和东方财富的后复权数据做对比,结果发现价格差了好几个点。后来才意识到,不同数据源的复权算法不一样,不能混用。
- 停牌数据没处理:某只股票停牌一个月,复牌后直接跌停。我的回测模型没处理停牌,结果把停牌期间的缺失值填充成了前一天的收盘价,导致策略信号完全错位。
- 节假日对齐问题:A 股和港股的节假日不一样。我做跨市场回测时,没对齐交易日历,结果两个市场的日期对不上,组合收益算出来是乱的。