3. 数据获取与清洗:数据源、清洗、存储
做量化回测,最怕什么?
不是策略亏钱,而是数据是错的。
我见过太多人,花几个月写策略,最后发现是数据没对齐,白忙一场。所以这一章,咱们把数据这块地基打牢。说白了,数据搞不定,后面全是空中楼阁。
3.1 数据源:三个主流选择
我个人习惯,根据场景选数据源。做A股,首选Tushare和AKShare。做美股,Yahoo Finance最方便。你想想看,每个工具都有自己的脾气,摸透了就好用。
Tushare
国内老牌金融数据接口。数据全,质量高。但有个门槛——需要注册获取token。我记得刚开始用的时候,被token折腾了半天。
import tushare as ts
# 设置token(需要去官网注册)
ts.set_token('你的token')
pro = ts.pro_api()
# 获取日线数据
df = pro.daily(ts_code='000001.SZ', start_date='20230101', end_date='20231231')
print(df.head())
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())
Yahoo Finance
做美股回测,绕不开它。用yfinance库,一行代码就能拉数据。
import yfinance as yf
# 获取苹果股票数据
df = yf.download('AAPL', start='2023-01-01', end='2023-12-31')
print(df.head())
嗯,这里要注意。Yahoo Finance的数据有时会缺失。特别是小盘股,历史数据可能不完整。我曾经拉过一只美股,发现2018年的数据全是NaN,后来换了数据源才解决。
3.2 数据清洗:去重、缺失值、对齐
数据拿到手,别急着用。先洗一遍。我总结了三步走:去重、补缺失、对齐。
去重
同一个交易日,同一个股票,出现两条数据。这种情况很常见。原因可能是网络重试、接口重复调用。
# 去重:按股票代码和交易日期
df = df.drop_duplicates(subset=['ts_code', 'trade_date'])
# 排序
df = df.sort_values(['ts_code', 'trade_date']).reset_index(drop=True)
缺失值处理
停牌、节假日、数据源问题,都会导致缺失。怎么处理?看情况。
| 缺失比例 | 处理方法 | 适用场景 |
|---|---|---|
| < 5% | 前向填充(ffill) | 价格数据,用前一天收盘价 |
| 5% - 20% | 插值法 | 成交量、财务指标 |
| > 20% | 直接删除该股票 | 数据质量太差,留着也是噪音 |
# 前向填充
df['close'] = df.groupby('ts_code')['close'].ffill()
# 线性插值
df['volume'] = df.groupby('ts_code')['volume'].apply(lambda x: x.interpolate())
# 删除缺失过多的股票
valid_stocks = df.groupby('ts_code')['close'].apply(lambda x: x.isna().sum() / len(x) < 0.2)
df = df[df['ts_code'].isin(valid_stocks[valid_stocks].index)]
对齐
多因子回测,最头疼的就是对齐。不同股票的交易日期不一样。A股有节假日,美股也有。你想想看,如果A股在国庆节休市,美股还在交易,那你的因子矩阵就缺了一行。
# 生成统一交易日历
all_dates = pd.date_range(start='2023-01-01', end='2023-12-31', freq='B')
# 对齐所有股票
aligned_data = {}
for stock in stock_list:
stock_data = df[df['ts_code'] == stock].set_index('trade_date')
stock_data = stock_data.reindex(all_dates, method='ffill')
aligned_data[stock] = stock_data['close']
# 合并成面板数据
panel = pd.DataFrame(aligned_data)
print(panel.head())
说白了,对齐就是让所有股票站在同一条时间线上。这样你算因子、做回归,才不会出现时间错位。
3.3 数据存储:HDF5 vs Parquet
数据清洗完了,存起来。别每次回测都重新拉数据,太慢了。我推荐两种格式:HDF5和Parquet。
HDF5
适合存储层次化数据。比如按股票代码分组,每个组存一个DataFrame。
import pandas as pd
# 存储到HDF5
with pd.HDFStore('market_data.h5') as store:
for stock, data in grouped_data.items():
store.put(f'stock/{stock}', data, format='table')
# 读取
with pd.HDFStore('market_data.h5') as store:
df = store.get('stock/000001.SZ')
Parquet
列式存储,压缩率高,读写快。特别适合存因子矩阵这种宽表数据。
import pyarrow.parquet as pq
import pyarrow as pa
# 存储为Parquet
table = pa.Table.from_pandas(panel)
pq.write_table(table, 'factor_panel.parquet')
# 读取
table = pq.read_table('factor_panel.parquet')
df = table.to_pandas()
我做过测试,同样一份因子数据,Parquet比CSV小70%,读取速度快10倍。做回测时,这个差距就是几分钟和几秒钟的区别。
3.4 知识体系总览
下面这张图,概括了本章的核心逻辑。数据从源到清洗再到存储,每一步都有坑,每一步都有技巧。
这一章的内容,说白了就是三个字:稳、准、快。数据源要稳,清洗要准,存储要快。做到这三点,你的回测框架就成功了一半。
- 数据源:Tushare做A股,AKShare做全品种,Yahoo做美股
- 清洗:去重→补缺失→对齐,三步缺一不可
- 存储:HDF5存中间数据,Parquet存因子矩阵
嗯,数据这块就聊到这儿。记住,别在数据上偷懒。你省下的时间,最后都会变成bug还回来。