金融数据获取:使用yfinance、Tushare、AkShare等库获取股票历史行情数据,处理复权、缺失值
量化分析的第一步永远是数据。无论你的策略逻辑多么精妙、回测框架多么高效,如果底层行情数据存在复权错误、缺失值未处理或字段口径不一致,最终的回测结果都将失真,甚至得出完全相反的结论。本章将聚焦于Python生态中最常用的三个金融数据库——yfinance、Tushare和AkShare,深入讲解如何获取股票历史行情数据,并重点解决复权因子计算与缺失值处理这两个核心工程问题。我们将从数据源特性对比、API调用规范、数据清洗流程到实战案例拆解,构建一套完整、可复用的数据获取与预处理流水线。
核心原理:三大数据源与复权机制的本质
在开始编写代码之前,理解数据源的本质差异和复权机制的原理至关重要。yfinance、Tushare和AkShare虽然都能返回OHLCV(开盘、最高、最低、收盘、成交量)数据,但它们的底层数据来源、更新频率、字段定义和复权处理方式存在显著差异。
yfinance 非官方地抓取Yahoo Finance的数据,无需注册,适合快速原型验证和海外市场数据获取。其返回的DataFrame索引为DatetimeIndex,包含Adj Close列(前复权收盘价)。但Yahoo的复权因子计算逻辑不透明,且在中国大陆网络环境下访问不稳定。
Tushare Pro 是国内最专业的量化数据平台之一,需要注册并获取,积分制访问不同权限的数据。其pro_bar接口支持复权因子(qfq、hfq)的直接计算,数据质量高,字段规范,但积分门槛限制了高频或全量数据的获取。
AkShare 是一个开源爬虫库,聚合了东方财富、新浪财经等公开数据源。接口丰富、免费无限制,但数据格式因源而异,需要较多的清洗工作,且接口变动频繁。
复权机制的核心在于处理上市公司分红、送股、配股等事件对股价造成的非交易性跳空。前复权(qfq)以当前价格为基准,调整历史价格,保证价格连续性,适合技术分析;后复权(hfq)以上市首日价格为基准,适合计算真实收益率。不复权(raw)数据则保留原始价格,但会因除权除息产生价格缺口,直接用于回测会产生虚假的收益信号。
| 数据源 | 认证方式 | 复权支持 | 数据质量 | 适用场景 |
|---|---|---|---|---|
| yfinance | 无需Key | 仅前复权(Adj Close) | 中等,偶有缺失 | 海外市场、快速验证 |
| Tushare Pro | +积分 | qfq/hfq/raw | 高,字段规范 | 国内A股、专业回测 |
| AkShare | 无需Key | 需自行计算 | 中等,格式多样 | 免费爬取、多源对比 |
操作流程:从API调用到数据清洗的标准流水线
构建一个健壮的数据获取流程,需要遵循“配置-获取-校验-清洗-存储”的五步方法论。以下以获取贵州茅台(600519.SH)近一年的日线数据为例,展示三种库的标准调用方式。
# 1. yfinance 调用示例
import yfinance as yf
df_yf = yf.download("600519.SS", start="2023-01-01", end="2024-01-01", auto_adjust=True)
# auto_adjust=True 自动返回前复权数据,但会丢失原始OHLC
# 2. Tushare Pro 调用示例
import tushare as ts
ts.set_('你的')
pro = ts.pro_api()
df_ts = pro.daily(ts_code='600519.SH', start_date='20230101', end_date='20240101')
# 获取复权因子
adj_factor = pro.adj_factor(ts_code='600519.SH', start_date='20230101', end_date='20240101')
# 合并后自行计算前复权价格:qfq_close = close * (adj_factor / latest_adj_factor)
# 3. AkShare 调用示例
import akshare as ak
df_ak = ak.stock_zh_a_hist(symbol="600519", period="daily", start_date="20230101", end_date="20240101", adjust="qfq")
# adjust参数支持qfq/hfq/"",但返回的列名为中文
获取数据后,必须进行统一的格式标准化。首先将日期列转换为统一的DatetimeIndex,并按照时间升序排序。其次,统一列名为小写英文(open, high, low, close, volume)。最后,检查索引的连续性——A股市场有法定节假日和周末休市,需要与交易日历对齐,识别出因停牌或数据缺失导致的空值。
关键操作:复权因子的正确应用
Tushare的adj_factor接口返回的是累积复权因子。计算前复权价格时,不能简单地将所有历史价格乘以同一个因子,而应使用factor / factor_latest的比例。例如,某股票当前复权因子为10,一年前为8,则一年前的价格应乘以8/10=0.8。后复权则乘以factor / factor_first。yfinance的auto_adjust虽然方便,但无法获取原始价格,对于需要同时使用复权和未复权数据的策略(如计算股息率)存在局限。
案例拆解:构建一个完整的A股数据管道
假设我们需要为沪深300成分股构建一个日线级别的回测数据库,要求包含前复权价格、成交量,并处理停牌导致的缺失值。我们选择Tushare Pro作为主数据源,因为其数据质量最稳定。
背景与约束: 本地服务器定时任务每日18:00运行,需要增量更新数据。网络带宽有限,单次请求限制5000行。策略回测需要至少3年的历史数据。
初始现象与目标: 直接使用pro.daily()获取的数据存在两个问题:一是停牌日数据缺失,导致DataFrame行数少于实际交易日;二是未复权价格在除权日出现跳空,直接计算收益率会产生虚假的负收益。
分析判断过程: 我们决定采用“先获取不复权数据+复权因子,再自行计算前复权价格”的策略。这样既保留了原始价格信息,又能灵活切换复权方式。对于缺失值,我们使用交易日历(pro.trade_cal())生成完整的日期序列,然后对停牌日进行标记。
实际操作步骤:
- 获取交易日历,生成2021-2024年的完整交易日列表。
- 循环获取每只股票的daily数据和adj_factor数据,合并后计算前复权价格。
- 使用
df.reindex(full_trade_days)将数据对齐到完整交易日,缺失的OHLC填充为NaN,volume填充为0。 - 对于停牌超过5个交易日的股票,在数据库中标记
is_suspended=True,回测时跳过这些时段。 - 将清洗后的数据追加写入Parquet文件,按股票代码分区存储。
结果对比与复盘: 处理前,直接使用原始数据回测一个简单的均线策略,年化收益率为-15%(因除权跳空导致虚假卖出信号)。处理后,同一策略的年化收益率为+8.5%,与Wind终端的数据核对一致。关键经验是:复权因子必须与价格数据同步更新,如果某日公司进行了大比例分红,而我们的因子表未更新,会导致后续所有价格计算错误。
风险提示: 不要盲目信任API返回的“复权”数据。yfinance的auto_adjust在遇到拆股时会自动调整,但遇到分红时调整逻辑不透明。AkShare的adjust参数在不同接口间行为不一致。最稳妥的方式是获取原始价格+复权因子,自行计算。
常见误区与总结复盘
误区一:忽略复权因子的时效性。 复权因子是随时间变化的。今天获取的前复权数据,在明天公司分红后,历史价格会整体变化。因此,回测数据必须一次性生成并固化,不能每次回测时实时获取,否则结果不可复现。
误区二:将所有缺失值都填充为前值。 停牌和缺失是两回事。停牌期间不应有交易,填充前值会引入未来信息(look-ahead bias)。对于停牌,应保留NaN并标记;对于日内短暂缺失(如数据源返回空值),可以使用前值填充,但需在日志中记录。
误区三:忽视成交量与价格的匹配。 在除权日,成交量也会进行相应调整(如送股后成交量放大)。如果只复权价格不复权成交量,会导致量价关系失真。Tushare的daily接口返回的vol是手数,在复权时无需调整,但需注意单位一致性。
误区四:数据源切换导致的口径不一致。 不同数据源的OHLC定义可能不同(如是否包含集合竞价)。在构建多源备份时,必须定义统一的数据字典,并在写入数据库前进行字段映射和校验。
经验总结: 一个生产级的数据管道应包含三层校验:第一层是完整性校验(行数是否匹配交易日历);第二层是逻辑校验(high >= max(open, close),low <= min(open, close));第三层是统计校验(收益率分布是否在合理范围内,如单日涨跌幅不超过20%)。建议将数据获取与策略回测解耦,数据管道独立运行并输出质量报告。