第三章:金融数据获取——用yfinance、akshare、Tushare搞定股票、ETF与指数历史数据

做量化投资,第一步就是搞数据。没有数据,再牛的策略也是空中楼阁。

我个人习惯把数据获取比作「做饭前的买菜」。菜买不好,厨艺再好也白搭。今天咱们就聊聊怎么用Python把A股、港股、美股的股票、ETF和指数历史数据给「买」回来。

3.1 三大数据源,各有什么本事?

市面上数据源很多,但我重点推荐三个:yfinance、akshare、Tushare。它们各有侧重,我一般搭配着用。

数据源 覆盖范围 是否需要注册 数据质量 我的使用场景
yfinance 美股、全球ETF 中等,偶尔有缺失 快速验证美股策略
akshare A股、港股、期货、基金 较好,接口丰富 日常A股数据采集
Tushare A股、指数、财务数据 是(需Token) 优秀,字段完整 正式回测、生产环境
我的小建议:刚开始学的时候,先用yfinance和akshare,零门槛。等你要做严肃回测了,再上Tushare。别一上来就注册一堆账号,容易劝退。

3.2 yfinance:三行代码拿下美股数据

yfinance是我用过最「无脑」的库。你想想看,连注册都不用,直接pip安装就能用。

import yfinance as yf

# 下载苹果公司近1年日线数据
aapl = yf.download('AAPL', start='2024-01-01', end='2025-01-01')
print(aapl.head())

就这么简单。但这里有个坑——数据频率。yfinance默认返回的是日线,如果你想拿分钟线,得加参数。

# 获取5分钟线数据
aapl_5min = yf.download('AAPL', period='5d', interval='5m')
我曾经踩过的坑:yfinance对A股代码支持很差。比如你想拿「贵州茅台」,直接写'600519.SS'有时候能行,但经常返回空数据。所以,我一般只用它拿美股和全球ETF。

ETF怎么拿?跟股票一样,直接写代码就行。

# 标普500 ETF
spy = yf.download('SPY', start='2024-01-01', end='2025-01-01')
# 纳斯达克100 ETF
qqq = yf.download('QQQ', start='2024-01-01', end='2025-01-01')

3.3 akshare:A股数据的一站式解决方案

做A股量化,akshare是我的首选。它把东方财富、新浪财经等网站的数据接口都封装好了,用起来很顺手。

import akshare as ak

# 获取贵州茅台历史数据
stock_600519_df = ak.stock_zh_a_hist(symbol="600519", period="daily", 
                                     start_date="20240101", end_date="20250101", 
                                     adjust="qfq")
print(stock_600519_df.head())

注意看参数里的 adjust="qfq",这是前复权。做回测时一定要用复权数据,否则分红送股会把你搞晕。

ETF数据怎么拿?akshare也支持。

# 获取沪深300 ETF(510300)数据
etf_510300_df = ak.fund_etf_hist_em(symbol="510300", period="daily", 
                                     start_date="20240101", end_date="20250101", 
                                     adjust="qfq")
核心要点:akshare的接口名经常变。我去年写的一个脚本,今年跑就报错了。解决方案是——每次用之前,先打印一下 ak.__doc__ 看看最新接口。

3.4 Tushare:专业级数据,但需要一点耐心

Tushare的数据质量是最好的,但门槛也最高。你得先注册、申请Token,而且有些接口还有积分限制。

import tushare as ts

# 设置Token(第一次使用需要)
ts.set_token('你的token')

# 初始化接口
pro = ts.pro_api()

# 获取贵州茅台日线数据
df = pro.daily(ts_code='600519.SH', start_date='20240101', end_date='20250101')
print(df.head())

Tushare返回的数据格式很规范,字段名都是英文的,比如 trade_dateopenhighlowclosevol。做回测时,我一般用Tushare的数据做最终验证。

指数数据怎么拿?

# 获取沪深300指数数据
index_df = pro.index_daily(ts_code='000300.SH', start_date='20240101', end_date='20250101')
我的经验:Tushare的积分制度有点烦人。基础积分只能调200次/分钟,高频策略根本不够用。我的做法是——用akshare做日常探索,用Tushare做最终回测。

3.5 数据获取的核心逻辑:一张图说清楚

说了这么多,咱们用一张SVG图把整个数据获取的流程串起来。这样你脑子里就有个清晰的框架了。

金融数据获取核心流程 yfinance 美股 / 全球ETF akshare A股 / 港股 / 期货 Tushare A股 / 指数 / 财务 数据清洗与预处理 处理缺失值 → 复权处理 → 对齐时间戳 → 去重 数据存储 CSV文件 → HDF5 → 数据库(MySQL / MongoDB) 选择建议:快速验证用yfinance,日常A股用akshare,正式回测用Tushare

3.6 实战:构建一个统一的数据获取函数

在实际项目中,我不会每次都写一堆重复代码。我习惯封装一个函数,根据参数自动选择数据源。

def get_history_data(symbol, source='akshare', start='20240101', end='20250101'):
    """
    统一数据获取接口
    :param symbol: 股票代码
    :param source: 数据源('yfinance', 'akshare', 'tushare')
    :param start: 开始日期
    :param end: 结束日期
    :return: DataFrame
    """
    if source == 'yfinance':
        import yfinance as yf
        data = yf.download(symbol, start=start, end=end)
        data.columns = ['open', 'high', 'low', 'close', 'adj_close', 'volume']
        return data
    
    elif source == 'akshare':
        import akshare as ak
        data = ak.stock_zh_a_hist(symbol=symbol, period='daily',
                                  start_date=start, end_date=end, adjust='qfq')
        data.rename(columns={
            '开盘': 'open', '最高': 'high', '最低': 'low',
            '收盘': 'close', '成交量': 'volume'
        }, inplace=True)
        return data[['open', 'high', 'low', 'close', 'volume']]
    
    elif source == 'tushare':
        import tushare as ts
        pro = ts.pro_api()
        data = pro.daily(ts_code=symbol, start_date=start, end_date=end)
        data.rename(columns={
            'open': 'open', 'high': 'high', 'low': 'low',
            'close': 'close', 'vol': 'volume'
        }, inplace=True)
        return data[['trade_date', 'open', 'high', 'low', 'close', 'volume']]
    
    else:
        raise ValueError("不支持的数据源,请选择 yfinance / akshare / tushare")

# 使用示例
df_600519 = get_history_data('600519', source='akshare', start='20240101', end='20250101')
print(df_600519.head())
为什么这么封装?因为在实际回测中,你可能会换数据源。比如一开始用akshare做原型,后来发现数据有缺失,换成Tushare。如果代码写死了,改起来很痛苦。封装一层,换个参数就行。

3.7 避坑指南:我踩过的那些坑

做数据获取这么多年,我总结了几条血泪教训:

  • 数据对齐问题:不同数据源的日期格式不一样。yfinance是datetime,akshare是字符串,Tushare是int。一定要统一成datetime格式再合并。
  • 复权问题:做回测必须用复权数据。我见过有人用不复权数据做回测,结果策略收益虚高,实盘直接亏哭。
  • 网络超时:数据获取时网络可能中断。我习惯加个重试机制,最多重试3次。
  • 数据缓存:别每次都重新下载。把数据存到本地文件,下次直接读取,能省不少时间。
我曾经犯过的错:有一次我用yfinance拿港股数据,代码写的是'0700.HK',结果返回的数据全是NaN。后来才发现,yfinance对港股的支持很差,得用akshare或者直接去港交所官网拿。从那以后,我拿港股只用akshare。

好了,数据获取这块就聊到这儿。记住一个原则:数据质量决定回测质量。别嫌麻烦,多花点时间把数据搞干净,后面做策略会省心很多。