第三章 数据获取与预处理:用yfinance搞定股票数据

做配对交易,第一步就是拿数据。这一步看似简单,但坑不少。我见过太多人花大量时间建模,结果数据本身就有问题——那真是白忙活。

今天咱们就聊聊,怎么用Python把数据拿对、洗干净、对齐好。

3.1 为什么选yfinance?

市面上能拿股票数据的库不少,比如pandas-datareader、tushare、baostock。但我个人最常用的是yfinance。原因有三:

  • 免费且稳定:雅虎财经的数据源,不需要API Key
  • 覆盖全球:美股、港股、A股都能拿(A股代码加后缀.SS或.SZ)
  • 接口简洁:几行代码就能拿到历史数据
小提示: 如果你拿A股数据,记得把代码写成"600519.SS"这种格式。我刚开始用的时候忘了加后缀,结果拿了一堆空数据回来,排查了半天。

3.2 安装与基本用法

先装库,这个不用多说:

pip install yfinance

然后咱们写个最简单的例子,拿茅台和五粮液的数据:

import yfinance as yf
import pandas as pd

# 定义股票代码
tickers = ["600519.SS", "000858.SZ"]

# 下载数据
data = yf.download(tickers, start="2020-01-01", end="2023-12-31")

# 看看数据结构
print(data.head())
print(data.columns)

运行后你会发现,返回的是一个多层列索引的DataFrame。列名是(Open, 600519.SS)、(Close, 600519.SS)这种形式。嗯,这里要注意,后续处理时需要把列拍平。

3.3 数据清洗——别让脏数据毁了你的模型

拿到的数据很少是完美的。我遇到过的情况包括:

  • 停牌日数据缺失(NaN)
  • 节假日不同步(美股和A股休市时间不一样)
  • 除权除息导致价格跳空
  • 数据源偶尔返回错误值

咱们一步步处理。

3.3.1 处理缺失值

最简单的做法是直接删除缺失行。但配对交易要求两只股票的时间戳完全对齐,所以不能简单dropna——你得先对齐。

# 只保留收盘价
close_prices = data['Close'].copy()

# 删除所有包含NaN的行(两只股票必须同时有数据)
clean_prices = close_prices.dropna()

print(f"原始数据行数: {len(close_prices)}")
print(f"清洗后行数: {len(clean_prices)}")
注意: 千万别用fillna(method='ffill')来填充缺失的交易日数据。我曾经这么干过,结果把停牌期间的价格硬填进去了,导致协整检验结果完全失真。停牌就是停牌,数据不存在就是不存在。

3.3.2 检查异常值

有时候数据源会返回离谱的值,比如某天价格突然涨了1000%。这种情况多半是数据错误。

# 计算日收益率
returns = clean_prices.pct_change()

# 找出收益率绝对值超过20%的日期
outliers = returns[returns.abs() > 0.2].dropna()
print(f"异常值数量: {len(outliers)}")

# 如果发现异常值,建议人工核实
if len(outliers) > 0:
    print("异常日期:")
    print(outliers)

3.4 数据对齐——配对交易的核心步骤

配对交易的本质是看两只股票的价格差是否稳定。如果时间戳不对齐,价差计算就是错的。

对齐其实就一句话:取两只股票都交易的日子。上面用dropna已经做到了。但这里有个细节——你用的是收盘价还是调整收盘价

3.4.1 价格的选择:Adj Close vs Close

yfinance默认提供两列:Close(收盘价)和Adj Close(调整收盘价)。

我个人习惯用Adj Close。为什么?因为调整收盘价已经考虑了分红、拆股、送股等因素。如果你用原始收盘价,遇到除权除息日,价格会突然跳空,价差序列就会出现假信号。

# 推荐使用调整收盘价
adj_close = data['Adj Close'].copy()
clean_adj = adj_close.dropna()

# 对比一下
print("原始收盘价最后5行:")
print(data['Close'].tail())
print("\n调整收盘价最后5行:")
print(data['Adj Close'].tail())
核心原则: 做配对交易,永远用调整收盘价。除非你明确知道自己在做什么,并且愿意手动处理除权除息。

3.5 收益率 vs 价格——到底用哪个?

这个问题在配对交易里经常被问到。我的答案是:看情况

指标 适用场景 优点 缺点
价格 协整检验、价差交易 直观,直接反映价差 非平稳,需要检验
收益率 相关性分析、风险度量 平稳,统计性质好 丢失了价格水平信息

说白了,如果你做协整检验,必须用价格序列(因为协整本身就是研究非平稳序列的线性组合是否平稳)。如果你只是看两只股票的相关性,用收益率更合适。

我自己的流程是:

  1. 先用价格做协整检验,找到配对
  2. 再用收益率计算相关系数,确认相关性够高
  3. 最后用价格计算价差,制定交易信号

3.6 完整的数据预处理流程

把上面所有步骤串起来,写成一个函数:

def prepare_pair_data(ticker1, ticker2, start, end):
    """
    获取并清洗配对交易数据
    """
    # 1. 下载数据
    data = yf.download([ticker1, ticker2], start=start, end=end)
    
    # 2. 提取调整收盘价
    prices = data['Adj Close'].copy()
    
    # 3. 对齐时间戳(删除缺失值)
    prices = prices.dropna()
    
    # 4. 重命名列
    prices.columns = [ticker1, ticker2]
    
    # 5. 计算收益率
    returns = prices.pct_change().dropna()
    
    return prices, returns

# 使用示例
prices, returns = prepare_pair_data("600519.SS", "000858.SZ", 
                                    "2020-01-01", "2023-12-31")
print(prices.head())
print(returns.head())
避坑指南: 我曾经在回测时发现策略表现特别好,结果一查,是因为数据里包含了未来信息——我用了当天的收盘价来计算当天的交易信号。正确的做法是:用前一天的收盘价计算价差,决定今天的交易。这个叫"前向无偏",后面章节会细讲。

3.7 本章知识体系

下面这张图总结了数据获取与预处理的完整流程:

数据获取与预处理流程 1. 数据获取 yfinance.download() 2. 数据清洗 处理缺失值、异常值 3. 数据对齐 dropna()对齐时间戳 价格序列(协整检验) Adj Close 收益率序列(相关性) pct_change() 输出:对齐后的数据 用于后续建模与回测

这张图把整个流程串起来了。你从yfinance拿数据,然后清洗、对齐,最后根据你的目的选择价格还是收益率。记住:对齐是前提,价格是核心


好了,数据准备这块就聊到这儿。数据干净了,后面做协整检验才有意义。下一章咱们正式进入协整的世界——看看怎么用统计方法找到真正能配对的股票。

专注资料整理