4、数据获取与清洗:用tushare/akshare搞定行情数据
做统计套利,第一步就是搞数据。这事儿听起来简单,但坑特别多。我刚开始做量化那会儿,光数据清洗就折腾了两周——拿到的日线数据里居然有未来数据,回测曲线漂亮得不像话,实盘直接翻车。嗯,从那以后我对数据环节就特别较真。
今天咱们就聊聊怎么用tushare和akshare拿数据,以及拿到手之后怎么处理。说白了,这两步做好了,后面的策略分析才有意义。
4.1 数据源的选择:tushare vs akshare
国内做量化,绕不开这两个库。我个人习惯是:tushare做主力,akshare做补充。为什么?
- tushare:数据质量高,字段规范,适合做精细分析。但需要积分,免费版有调用限制。
- akshare:数据源多,免费,适合快速验证。但偶尔会有数据缺失或格式不一致的问题。
你想想看,如果只是做策略原型,akshare完全够用。但真要跑实盘或者做严谨的回测,我建议用tushare。我在项目中遇到过akshare某只股票的复权数据突然断了一天,排查了半天才发现是源网站的问题。
4.2 用tushare获取行情数据
先装好库:
pip install tushare akshare pandas numpy
tushare需要token,去官网注册就能拿到。我个人习惯把token存在环境变量里,而不是硬编码在代码中。
import tushare as ts
import pandas as pd
# 初始化
pro = ts.pro_api('你的token')
# 获取日线数据
df = pro.daily(
ts_code='000001.SZ',
start_date='20230101',
end_date='20231231',
fields='ts_code,trade_date,open,high,low,close,vol'
)
print(df.head())
这里有个细节:fields参数可以指定字段,别一股脑全拉回来。我刚开始做的时候把几十个字段全要了,结果大部分用不上,还拖慢速度。
daily接口默认返回未复权数据。做统计套利时,建议用adj_factor接口获取复权因子,自己算后复权。
4.3 用akshare获取数据
akshare更直接,不用token,但接口名有点怪。我经常记不住,所以写了个小封装:
import akshare as ak
# 获取A股日线
df_ak = ak.stock_zh_a_hist(
symbol="000001",
period="daily",
start_date="20230101",
end_date="20231231",
adjust="qfq" # 前复权
)
print(df_ak.head())
注意akshare返回的列名是中文,比如"日期"、"开盘"、"收盘"。做数据处理前最好统一转成英文,不然容易乱。我吃过这个亏——某次回测脚本里用了中文列名,换了个环境就报错。
| 对比项 | tushare | akshare |
|---|---|---|
| 认证方式 | Token | 无需认证 |
| 数据质量 | 高,字段规范 | 中等,偶有异常 |
| 调用限制 | 积分制,免费版200次/分钟 | 无限制,但源网站可能反爬 |
| 复权数据 | 需手动计算 | 直接支持前/后复权 |
| 适用场景 | 严谨回测、实盘 | 快速验证、原型开发 |
4.4 数据清洗:处理缺失值和异常值
数据拿到手,别急着用。先看看有没有坑。我一般按这个流程走:
- 检查缺失值——停牌、节假日、数据源问题都会导致NaN
- 检查异常值——比如某天涨幅1000%,明显是数据错误
- 检查重复值——同一个交易日出现两条数据
- 检查时间序列连续性——有没有跳空的日子
4.4.1 缺失值处理
# 检查缺失
print(df.isnull().sum())
# 处理方式1:向前填充(适合停牌)
df['close'].fillna(method='ffill', inplace=True)
# 处理方式2:删除(适合少量缺失)
df.dropna(subset=['close'], inplace=True)
# 处理方式3:插值(适合短期缺失)
df['close'].interpolate(method='linear', inplace=True)
我个人习惯:停牌用前向填充,数据源问题用删除,短期缺失用插值。但要注意,插值会引入人为数据,做统计套利时要谨慎。我曾经用插值补了三天数据,结果那三天的协整关系全是假的。
4.4.2 异常值检测
异常值怎么找?我常用两种方法:
- 3σ原则:超过均值±3倍标准差的数据视为异常
- IQR方法:低于Q1-1.5*IQR或高于Q3+1.5*IQR的数据视为异常
# IQR方法示例
Q1 = df['close'].quantile(0.25)
Q3 = df['close'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 标记异常
df['is_outlier'] = (df['close'] < lower_bound) | (df['close'] > upper_bound)
# 查看异常数据
print(df[df['is_outlier']])
但要注意,金融数据本身就有厚尾特征,很多看似异常的值其实是真实波动。我一般会结合业务逻辑判断——比如某只股票突然涨了20%,如果是财报发布日,那可能是真的;如果是普通交易日,大概率是数据错误。
4.5 数据重采样与对齐
统计套利经常需要处理不同频率的数据。比如,你拿到的股票数据是日线,但指数数据是5分钟线。这时候就需要重采样和对齐。
4.5.1 重采样
把高频数据转成低频,或者反过来。我常用的是resample方法:
# 假设有5分钟数据,转成日线
df_5min = pd.read_csv('5min_data.csv', parse_dates=['time'])
df_5min.set_index('time', inplace=True)
# 重采样为日线
df_daily = df_5min.resample('D').agg({
'open': 'first',
'high': 'max',
'low': 'min',
'close': 'last',
'volume': 'sum'
})
这里有个坑:重采样时一定要指定聚合函数。如果不指定,pandas会报错或者用默认的mean,那开盘价就变成了一天中所有开盘价的均值,完全不对。
4.5.2 数据对齐
多只股票做配对交易时,必须保证时间戳对齐。我一般用merge或join:
# 假设有两只股票的数据
df_a = pd.read_csv('stock_a.csv', parse_dates=['date'])
df_b = pd.read_csv('stock_b.csv', parse_dates=['date'])
# 内连接:只保留共同交易日
df_aligned = pd.merge(
df_a[['date', 'close']],
df_b[['date', 'close']],
on='date',
how='inner',
suffixes=('_a', '_b')
)
print(f"对齐后数据量: {len(df_aligned)}")
为什么要用内连接?因为如果某只股票停牌,另一只正常交易,那天的价差数据就是假的。我刚开始做配对交易时用了外连接,结果回测里出现了大量"完美套利机会",实盘根本不存在。
4.6 本章知识体系
下面这张图总结了数据获取与清洗的核心流程。我画的时候特意把"清洗"环节放大了——因为在实际项目中,80%的时间都花在这上面。
4.7 实战小贴士
最后分享几个我踩过的坑:
- 数据缓存:每次跑策略都重新拉数据,既慢又浪费积分。我习惯把原始数据存成parquet格式,读取快,压缩率高。
- 版本管理:数据文件加上日期后缀,比如
stock_000001_20230101_20231231.parquet。这样回测结果可复现。 - 数据校验:清洗完后,随机抽几天和行情软件对比一下。我吃过一次亏——某次复权因子算错了,整个回测白跑。
数据这块就聊到这儿。记住一句话:垃圾进,垃圾出。数据质量决定了策略的天花板,别在这上面省功夫。