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())
我的经验:Tushare的pro接口返回的数据,字段命名很规范。但注意,它默认返回的是前复权数据。如果你需要后复权,得单独调接口。

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())
注意:AKShare的接口名经常变。我遇到过几次,升级版本后代码就报错了。建议锁定版本号,别追新。

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)]
避坑指南:我曾经用ffill处理停牌数据,结果停牌一个月的数据全是同一个价格。回测时发现策略在停牌期间"赚了钱",其实是假信号。后来我加了停牌标记,停牌期间的数据直接剔除。

对齐

多因子回测,最头疼的就是对齐。不同股票的交易日期不一样。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')
我的习惯:HDF5适合存中间结果。比如清洗后的日线数据。但注意,HDF5文件不能并发写入,多进程时会报错。

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 AKShare Yahoo Finance 去重 缺失值处理 对齐 HDF5 Parquet 核心原则 数据质量决定回测可信度 清洗步骤不可跳过,存储格式影响效率

这一章的内容,说白了就是三个字:稳、准、快。数据源要稳,清洗要准,存储要快。做到这三点,你的回测框架就成功了一半。

总结一下:
  • 数据源:Tushare做A股,AKShare做全品种,Yahoo做美股
  • 清洗:去重→补缺失→对齐,三步缺一不可
  • 存储:HDF5存中间数据,Parquet存因子矩阵

嗯,数据这块就聊到这儿。记住,别在数据上偷懒。你省下的时间,最后都会变成bug还回来。

专注资料整理