第三章:金融数据获取——用yfinance搞定历史数据
做量化回测,第一步就是搞数据。没有数据,策略就是空中楼阁。
我个人习惯用 yfinance 这个库。它免费、简单,而且直接对接雅虎财经。虽然数据精度比不上专业数据商,但做策略验证和教学完全够用。我在项目中遇到过好几次,用付费数据跑出来的结果,跟 yfinance 的差距其实不大——当然,高频交易除外。
3.1 安装与基础用法
先装库。一行命令搞定:
pip install yfinance
然后拉数据。比如拿茅台(600519.SS)试试:
import yfinance as yf
# 下载茅台历史数据
maotai = yf.download('600519.SS', start='2020-01-01', end='2023-12-31')
print(maotai.head())
输出长这样:
Open High Low Close Volume
Date
2020-01-02 1110.0000 1133.0000 1105.0100 1130.0000 2910000
2020-01-03 1130.0000 1138.0000 1120.0000 1128.9900 2260000
嗯,这里要注意:A股代码后面要加 .SS(上海)或 .SZ(深圳)。港股加 .HK。美股直接写代码就行,比如 AAPL。
3.2 批量下载多只股票
做回测很少只盯一只票。我习惯一次性拉几十只:
tickers = ['600519.SS', '000858.SZ', 'AAPL', 'TSLA']
data = yf.download(tickers, start='2020-01-01', end='2023-12-31')
# 提取收盘价
close_prices = data['Close']
print(close_prices.head())
输出:
000858.SZ 600519.SS AAPL TSLA
Date
2020-01-02 85.500000 1130.0000 74.956001 28.684000
2020-01-03 85.099998 1128.9900 74.357498 28.752001
你看,数据自动对齐了日期。缺失的日期(比如节假日)会被跳过。这其实是个坑——不同市场的交易日不一样,合并后可能出现空值。
3.3 数据清洗——别让脏数据毁了你的策略
数据到手了,但别急着用。先看看有没有问题。
3.3.1 检查缺失值
# 检查缺失值
print(close_prices.isnull().sum())
# 看看哪些日期有缺失
missing_dates = close_prices[close_prices.isnull().any(axis=1)]
print(missing_dates.head())
输出示例:
000858.SZ 5
600519.SS 3
AAPL 0
TSLA 0
为什么会缺失?原因很多:停牌、数据源问题、不同市场节假日不同。我遇到过最离谱的一次,某只股票因为公司改名,雅虎把新旧代码当成两只不同的股票,导致中间有半年数据是空的。
3.3.2 处理缺失值
处理方式取决于你的策略。我个人常用这几种:
| 方法 | 适用场景 | 代码 |
|---|---|---|
| 向前填充 | 停牌、非交易日 | df.fillna(method='ffill') |
| 向后填充 | 数据延迟更新 | df.fillna(method='bfill') |
| 插值法 | 少量缺失、价格平稳 | df.interpolate() |
| 直接删除 | 缺失比例极小 | df.dropna() |
举个例子:
# 向前填充——停牌时用前一天价格
close_prices_filled = close_prices.fillna(method='ffill')
# 如果开头还有缺失,用向后填充
close_prices_filled = close_prices_filled.fillna(method='bfill')
# 或者直接删掉有缺失的行
close_prices_clean = close_prices.dropna()
3.4 异常值检测——那些离谱的价格
数据里偶尔会出现一些离谱的值。比如某只股票突然涨了1000%,或者跌到0。这通常是数据错误,不是市场行为。
3.4.1 用百分比变化检测
# 计算日收益率
returns = close_prices.pct_change()
# 找出收益率绝对值超过20%的日期
outliers = returns[returns.abs() > 0.2]
print(outliers.dropna().head())
输出:
000858.SZ 600519.SS AAPL TSLA
Date
2020-03-16 NaN NaN NaN 0.235
2020-03-18 NaN NaN NaN -0.218
你看,特斯拉在2020年3月出现过单日涨跌超过20%的情况。这其实是真实的市场波动,不是异常值。但如果你发现某只平时波动很小的银行股突然涨了50%,那大概率是数据问题。
3.4.2 用Z-score方法
from scipy import stats
import numpy as np
# 计算每只股票的Z-score
z_scores = np.abs(stats.zscore(close_prices.dropna()))
threshold = 3 # 超过3个标准差视为异常
# 找出异常值的位置
outlier_mask = z_scores > threshold
print(f"异常值数量: {outlier_mask.sum()}")
3.5 数据对齐与重采样
不同频率的数据需要对齐。比如你的策略是日频,但有些数据是周频。
# 将日数据重采样为周数据(取每周五的收盘价)
weekly_close = close_prices.resample('W-FRI').last()
# 或者取周均值
weekly_mean = close_prices.resample('W').mean()
# 对齐到统一日期
aligned_data = close_prices.align(weekly_close, join='inner')
嗯,这里要注意:重采样会丢失信息。周数据看不到周内的波动,做回测时容易高估策略表现。我一般只在做长周期策略时才用重采样。
3.6 保存与加载清洗后的数据
清洗一次,反复使用。别每次都重新下载。
# 保存为CSV
close_prices_clean.to_csv('cleaned_close_prices.csv')
# 保存为Parquet(更快、更小)
close_prices_clean.to_parquet('cleaned_close_prices.parquet')
# 下次直接加载
import pandas as pd
df = pd.read_parquet('cleaned_close_prices.parquet')
3.7 本章知识体系
下面这张图总结了数据获取与处理的完整流程:
数据获取与处理看似简单,但80%的回测错误都出在这一步。我见过太多人花大量时间调策略参数,结果数据本身就有问题——白忙一场。
记住:干净的数据是回测的基石。花30%的时间在数据上,一点都不浪费。