第二章:数据获取与清洗——用tushare/akshare搞定A股日线数据
做量化交易,数据就是你的弹药。弹药不行,枪法再好也白搭。
这一章,咱们就聊聊怎么拿到干净、可用的A股日线数据。我会用tushare和akshare这两个库,手把手带你走一遍。我个人习惯用akshare多一些,因为它免费且数据源稳定。但tushare在某些场景下也有优势,比如复权因子的处理更直接。
2.1 数据获取:从零到一
先说说数据源的选择。你想想看,市面上能免费拿到A股数据的渠道其实不多。tushare是老牌选手,akshare是后起之秀。我的建议是:
- tushare:适合需要高频数据、复权因子、财务数据的场景。但需要积分,新手可能有点门槛。
- akshare:完全免费,接口丰富,覆盖股票、基金、期货、宏观数据。我最近的项目基本都用它。
嗯,这里要注意:akshare的数据源来自东方财富、新浪财经等,偶尔会有延迟或缺失。但做日线级别的分析,完全够用。
2.1.1 安装与初始化
# 安装
pip install akshare tushare
# 初始化
import akshare as ak
import tushare as ts
import pandas as pd
import numpy as np
# tushare需要token
ts.set_token('你的token')
pro = ts.pro_api()
我在项目中遇到过一个问题:tushare的token如果过期了,会直接报错。所以建议把token存在环境变量里,别硬编码在代码中。
2.1.2 获取日线数据
以贵州茅台(600519)为例,看看两个库的用法差异。
# akshare方式
df_ak = ak.stock_zh_a_hist(symbol="600519", period="daily",
start_date="20230101", end_date="20231231",
adjust="qfq") # qfq=前复权
# tushare方式
df_ts = pro.daily(ts_code="600519.SH", start_date="20230101",
end_date="20231231")
你看,akshare直接传参adjust="qfq"就能拿到前复权数据。tushare返回的是未复权数据,需要额外处理复权因子。这一点,akshare更省事。
2.2 复权因子处理:别让分红影响你的判断
为什么要处理复权?说白了,股票分红、送股会导致价格出现跳空缺口。如果你直接用原始价格计算收益率,结果会失真。
举个例子:某股票100元,10送10后变成50元。如果你用50元算历史收益率,会以为跌了50%。但实际上你的资产没变。复权就是把这个缺口补上。
2.2.1 前复权 vs 后复权
| 类型 | 含义 | 适用场景 |
|---|---|---|
| 前复权 | 调整历史价格,使当前价格不变 | 技术分析、回测(最常用) |
| 后复权 | 调整当前价格,使历史价格不变 | 看真实涨幅、长期投资分析 |
| 不复权 | 原始价格,有缺口 | 分红套利、事件驱动策略 |
我个人习惯用前复权做回测。为什么呢?因为前复权保证了当前价格是真实的市场价,回测结果更贴近实际交易。
2.2.2 手动处理复权因子
如果你用tushare,需要自己算复权价格。代码其实不复杂:
# 获取复权因子
adj_factor = pro.adj_factor(ts_code="600519.SH",
start_date="20230101",
end_date="20231231")
# 合并到日线数据
df = df_ts.merge(adj_factor[['trade_date', 'adj_factor']],
on='trade_date', how='left')
# 计算前复权价格
df['adj_close'] = df['close'] * df['adj_factor'] / df['adj_factor'].iloc[-1]
df['adj_open'] = df['open'] * df['adj_factor'] / df['adj_factor'].iloc[-1]
2.3 清洗异常值:数据质量决定策略上限
数据拿到手,别急着用。先看看有没有脏数据。我总结了一套清洗流程,分享给你:
2.3.1 常见异常类型
- 缺失值:停牌、节假日导致的数据空缺
- 极端值:涨跌停、数据录入错误(比如价格突然变成0或99999)
- 重复值:同一日期出现多条记录
- 逻辑错误:开盘价=收盘价、最高价<最低价等
2.3.2 清洗实战代码
def clean_stock_data(df):
"""
清洗股票日线数据
"""
# 1. 删除重复行
df = df.drop_duplicates(subset=['date'])
# 2. 处理缺失值
# 停牌日期的价格用前一个交易日填充
df = df.fillna(method='ffill')
# 3. 检查逻辑错误
# 最高价不能小于最低价
invalid = df[df['high'] < df['low']]
if len(invalid) > 0:
print(f"发现{len(invalid)}条逻辑错误数据,已修正")
# 交换最高价和最低价
df.loc[df['high'] < df['low'], ['high', 'low']] = \
df.loc[df['high'] < df['low'], ['low', 'high']].values
# 4. 处理极端值
# 涨跌幅超过20%的视为异常(A股普通股票涨跌幅限制10%)
df['pct_chg'] = df['close'].pct_change()
extreme = df[abs(df['pct_chg']) > 0.2]
if len(extreme) > 0:
print(f"发现{len(extreme)}条极端涨跌幅数据")
# 用前后均值替换
for idx in extreme.index:
df.loc[idx, 'close'] = (df.loc[idx-1, 'close'] +
df.loc[idx+1, 'close']) / 2
return df
你可能会问:为什么涨跌幅超过20%就算异常?因为A股主板涨跌幅限制是10%,科创板是20%。如果你做的是科创板股票,阈值要调到40%。这个细节,我在项目中吃过亏。
2.3.3 数据质量检查清单
每次清洗完数据,我都会跑一遍这个检查:
- 数据量是否完整?比如2023年应该有245个交易日左右
- 是否有空值?用
df.isnull().sum()检查 - 价格是否为正数?用
df.describe()看最小值 - 日期是否连续?用
df['date'].diff().value_counts()检查间隔
2.4 本章知识体系
下面这张图,帮你梳理了数据获取与清洗的完整流程:
这张图把整个流程串起来了。从数据源选择,到获取方式,再到复权处理和数据清洗,最后输出干净数据。每一步都环环相扣。
utils模块里。每次新项目直接调用,省时省力。你想想看,如果每次都要重写一遍,多浪费时间。
好了,数据获取和清洗就聊到这里。记住一句话:数据质量决定策略上限。花80%的时间在数据上,剩下20%做策略,这个比例是值得的。