数据获取与清洗:用yfinance搞定股票数据

做量化分析,第一步就是搞到干净的数据。我见过太多人一上来就调模型、跑回测,结果数据本身就有问题——那后面的工作全白费了。今天咱们就聊聊怎么用yfinance拿数据,以及拿到手之后怎么处理。

核心要点:数据质量决定了分析的上限。模型再牛,也救不了脏数据。

1. yfinance 快速上手

yfinance这个库,说白了就是雅虎财经的非官方Python接口。我最早用的时候还叫yahoo-finance,后来雅虎把API关了,社区就搞出了yfinance。嗯,这玩意儿现在已经是量化圈的标配了。

安装很简单:

pip install yfinance

拿数据更简单。比如我要拿特斯拉的日线数据:

import yfinance as yf

# 下载特斯拉从2023年1月到2024年1月的数据
tsla = yf.download('TSLA', start='2023-01-01', end='2024-01-01')
print(tsla.head())

你会得到一个DataFrame,包含Open、High、Low、Close、Volume、Adj Close这几列。注意那个Adj Close——复权收盘价。我在项目中遇到过有人直接用Close做回测,结果分红除权后策略表现完全对不上,排查了半天才发现是数据没复权。

我的习惯:做回测分析时,永远用Adj Close。除非你明确知道自己在做什么。

2. 处理缺失值——别小看这个步骤

拿到的数据很少是完美的。停牌、节假日、数据源问题,都会导致缺失值。你想想看,如果某天数据是NaN,你的移动平均线、收益率计算全都会出问题。

常见的缺失值处理方法:

方法 适用场景 我的评价
向前填充(ffill) 非交易日的价格延续 最常用,适合股票数据
向后填充(bfill) 数据开头缺失 偶尔用,但要注意偏差
插值法 少量连续缺失 适合高频数据,日线慎用
直接删除 缺失比例极小 简单粗暴,但会破坏时间连续性

代码实现:

# 检查缺失值
print(tsla.isnull().sum())

# 向前填充——我最常用的方式
tsla_filled = tsla.ffill()

# 如果开头有缺失,用第一个有效值填充
tsla_filled = tsla_filled.bfill()

我曾经踩过的坑:有一次做多因子模型,用了插值法填充缺失的成交量数据。结果插出来的值完全不符合市场规律,导致因子失效。后来我改成ffill,问题就解决了。记住:金融数据不是连续函数,别乱插值。

3. 重采样——把数据变成你想要的频率

yfinance默认拿的是日线数据。但有时候你需要周线、月线,甚至小时线。重采样就是干这个的。

举个例子,把日线重采样成周线:

# 周线重采样
weekly = tsla_filled.resample('W').agg({
    'Open': 'first',
    'High': 'max',
    'Low': 'min',
    'Close': 'last',
    'Volume': 'sum',
    'Adj Close': 'last'
})

print(weekly.head())

这里要注意几点:

  • 开盘价用每周第一个交易日
  • 最高价用每周最高
  • 最低价用每周最低
  • 收盘价用每周最后一个交易日
  • 成交量用总和

我个人习惯把重采样后的数据单独存一份,不覆盖原始日线。因为不同频率的数据服务于不同的分析目的——日线做短线,周线看趋势,月线定方向。

4. 交易日历——别被非交易日坑了

这是新手最容易忽略的地方。股票市场不是每天都交易的。周末、法定节假日、特殊休市日,这些都会导致数据缺失。

但问题是——这些缺失是正常的,不是数据错误。你不能用ffill去填充一个周六的数据,因为周六本来就没有交易。

yfinance自带交易日历功能:

# 获取某只股票的交易日历
tsla_info = yf.Ticker('TSLA')
calendar = tsla_info.calendar
print(calendar)

不过说实话,yfinance的交易日历信息有限。我一般用pandas的pd.date_range配合自定义日历:

import pandas as pd

# 生成2023年所有交易日(排除周末)
all_days = pd.date_range(start='2023-01-01', end='2023-12-31', freq='B')
print(f'2023年交易日数量:{len(all_days)}')

这里freq='B'表示只取工作日。但注意,这还没排除法定节假日。如果你做的是A股,还需要额外处理春节、国庆等假期。

我的做法:对于美股,直接用yfinance下载的数据,它已经自动处理了非交易日。对于A股,我会用akshare或者tushare,它们内置了A股的交易日历。

5. 完整的数据清洗流程

把上面这些串起来,一个标准的数据清洗流程长这样:

def clean_stock_data(ticker, start, end):
    # 1. 下载数据
    df = yf.download(ticker, start=start, end=end)
    
    # 2. 只保留需要的列
    df = df[['Open', 'High', 'Low', 'Close', 'Volume', 'Adj Close']]
    
    # 3. 处理缺失值
    df = df.ffill().bfill()
    
    # 4. 检查是否有异常值(比如价格为负)
    assert (df[['Open', 'High', 'Low', 'Close', 'Adj Close']] > 0).all().all()
    
    # 5. 生成周线数据
    weekly = df.resample('W').agg({
        'Open': 'first',
        'High': 'max',
        'Low': 'min',
        'Close': 'last',
        'Volume': 'sum',
        'Adj Close': 'last'
    })
    
    return df, weekly

# 使用
daily, weekly = clean_stock_data('AAPL', '2023-01-01', '2024-01-01')
print(f'日线数据量:{len(daily)}')
print(f'周线数据量:{len(weekly)}')

这个函数我用了快两年了,基本没出过问题。当然,你还可以加更多检查——比如成交量不能为负、价格跳空超过20%要标记等等。具体加什么,取决于你的策略需求。

6. 本章知识体系

下面这张图把数据获取与清洗的核心逻辑串起来了:

数据获取与清洗流程 数据获取 yfinance.download() 缺失值处理 ffill / bfill / 删除 重采样 日线 → 周线/月线 交易日历 排除非交易日 异常值检测 价格/成交量合理性 干净的数据 可直接用于建模 数据清洗不是一次性工作,每次建模前都应该重新检查 核心原则:保留原始数据,在副本上做清洗

数据获取与清洗,说白了就是给模型喂饭。饭不干净,模型再聪明也白搭。我见过太多人在这步上栽跟头——要么数据频率搞错了,要么缺失值处理不当,要么没考虑交易日历。这些坑我都踩过,所以今天特意拿出来说。

记住一句话:花80%的时间处理数据,花20%的时间建模。这个比例在量化领域永远适用。

专注资料整理