3. 金融数据获取与清洗:用Tushare/Akshare搞定股票数据
做量化交易,第一步就是搞数据。没有干净的数据,再牛的策略也是白搭。我见过太多人花大把时间调参数,结果数据源就有问题——复权没做、缺失值没处理,回测结果全是幻觉。
今天咱们就聊聊怎么用Tushare和Akshare拿数据,以及怎么把数据洗干净。这两套库我都在生产环境里用过,各有千秋,我习惯把它们搭配着用。
3.1 数据获取:Tushare vs Akshare
先说说这两个库的区别。Tushare是老牌选手,数据质量高,但需要注册获取token,有些接口还有积分限制。Akshare呢,完全免费,数据源来自各大财经网站,覆盖面广,但偶尔会有格式变动。
我个人建议:做研究用Akshare,做生产用Tushare。为什么?因为Tushare的数据一致性更好,回测结果更可信。
3.1.1 Tushare 快速上手
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返回的数据默认是倒序的,最新的在最上面。我刚开始用的时候没注意,回测结果直接反了,亏大了。
3.1.2 Akshare 快速上手
import akshare as ak
# 获取A股日线数据
df = ak.stock_zh_a_hist(symbol="000001", period="daily",
start_date="20230101", end_date="20231231", adjust="")
print(df.head())
Akshare的接口命名比较直观,但参数名经常变。我曾经遇到过某次升级后,adjust参数从qfq改成了hfq,代码直接崩了。所以用Akshare的话,建议固定版本号。
3.2 数据清洗:去重、填充、对齐
拿到数据后,别急着跑策略。先看看数据长什么样。我一般会做三件事:去重、填充、对齐。
3.2.1 去重
数据重复的原因很多:网络重传、接口bug、手动追加等等。重复数据会让你的统计指标失真。
# 检查重复行
print(df.duplicated().sum())
# 删除重复行
df = df.drop_duplicates()
# 按日期去重(保留最新的一条)
df = df.sort_values('trade_date').drop_duplicates(subset=['ts_code', 'trade_date'], keep='last')
避坑指南:我曾经在合并多只股票数据时,忘了按股票代码和日期联合去重,结果某只股票的数据被另一只覆盖了。回测时发现收益异常高,查了半天才发现是数据串了。
3.2.2 缺失值填充
停牌、节假日、数据缺失都会导致NaN。怎么处理?看情况。
| 场景 | 处理方法 | 说明 |
|---|---|---|
| 停牌 | 前向填充 | 用上一个交易日的数据填充 |
| 节假日 | 删除或插值 | 非交易日数据直接删掉 |
| 数据源缺失 | 线性插值 | 适用于短期缺失 |
# 前向填充(停牌处理)
df = df.fillna(method='ffill')
# 线性插值
df = df.interpolate(method='linear')
# 删除缺失值(慎用)
df = df.dropna()
你想想看,如果某只股票停牌一周,你用前向填充,那这一周的收益率就是0。这合理吗?其实不太合理,但至少比用随机数强。
3.2.3 数据对齐
多只股票的数据,日期可能不一样。比如A股票有2023-01-01的数据,B股票没有。这时候就需要对齐。
# 假设我们有两只股票的数据
stock_a = df_a.set_index('trade_date')['close']
stock_b = df_b.set_index('trade_date')['close']
# 对齐到共同的交易日
combined = pd.concat([stock_a, stock_b], axis=1, join='inner')
combined.columns = ['stock_a', 'stock_b']
join='inner'只保留共同交易日,join='outer'保留所有日期。我一般用inner,因为outer会引入太多缺失值。
3.3 复权处理:别让分红影响你的策略
复权是个大坑。很多新手直接用不复权的数据跑回测,结果发现策略收益高得离谱——其实是因为分红除权导致的假象。
复权有三种方式:
- 不复权:真实价格,但会有跳空缺口
- 前复权:调整历史价格,让价格连续
- 后复权:调整当前价格,适合长期持有分析
我个人习惯用前复权。为什么?因为前复权能保证历史收益率计算正确,而且价格序列是连续的,画图好看。
# Tushare获取复权数据
df = pro.daily(ts_code='000001.SZ', start_date='20230101',
end_date='20231231', adj='qfq') # qfq=前复权, hfq=后复权
# Akshare获取复权数据
df = ak.stock_zh_a_hist(symbol="000001", period="daily",
start_date="20230101", end_date="20231231",
adjust="qfq") # qfq=前复权, hfq=后复权, 空=不复权
3.4 完整的数据清洗流程
说了这么多,咱们来个完整的例子。假设我们要获取5只股票2023年的数据,清洗后用于回测。
import pandas as pd
import tushare as ts
ts.set_token('你的token')
pro = ts.pro_api()
# 股票列表
stocks = ['000001.SZ', '000002.SZ', '000651.SZ', '600519.SH', '000858.SZ']
# 获取数据
all_data = []
for stock in stocks:
df = pro.daily(ts_code=stock, start_date='20230101',
end_date='20231231', adj='qfq')
df['stock'] = stock
all_data.append(df)
# 合并
full_df = pd.concat(all_data, ignore_index=True)
# 清洗步骤
# 1. 去重
full_df = full_df.drop_duplicates(subset=['stock', 'trade_date'])
# 2. 排序
full_df = full_df.sort_values(['stock', 'trade_date'])
# 3. 处理缺失值(按股票分组填充)
full_df['close'] = full_df.groupby('stock')['close'].transform(lambda x: x.fillna(method='ffill'))
# 4. 对齐(只保留所有股票都有数据的日期)
pivot_df = full_df.pivot(index='trade_date', columns='stock', values='close')
pivot_df = pivot_df.dropna()
print(f"清洗后数据量: {len(pivot_df)} 行")
print(pivot_df.head())
3.5 知识体系总览
下面这张图总结了本章的核心逻辑,从数据获取到清洗再到复权,每一步都环环相扣。
说白了,数据清洗就是个体力活,但也是最重要的体力活。你想想看,如果数据有问题,后面所有的分析、建模、回测都是白费功夫。我见过太多人在这上面栽跟头了。
嗯,今天就聊到这儿。记住一句话:垃圾进,垃圾出。数据质量决定了你的策略上限。
核心要点回顾:
- Tushare适合生产环境,Akshare适合快速研究
- 去重、填充、对齐是数据清洗三板斧
- 回测必须用复权数据,推荐前复权
- 清洗后的数据记得保存,别每次都重新拉