数据获取与清洗:用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. 本章知识体系
下面这张图把数据获取与清洗的核心逻辑串起来了:
数据获取与清洗,说白了就是给模型喂饭。饭不干净,模型再聪明也白搭。我见过太多人在这步上栽跟头——要么数据频率搞错了,要么缺失值处理不当,要么没考虑交易日历。这些坑我都踩过,所以今天特意拿出来说。
记住一句话:花80%的时间处理数据,花20%的时间建模。这个比例在量化领域永远适用。