2. 金融市场数据获取:使用yfinance、pandas-datareader获取股票、ETF、期货数据。数据清洗与预处理:处理缺失值、异常值、调整股息与拆股。
做统计套利,第一步就是搞数据。这步要是没做好,后面模型再漂亮也是白搭。我见过太多人花几个月调参数,最后发现是原始数据有坑——嗯,我自己也踩过这种坑。
今天咱们就把数据获取和清洗这块彻底讲透。说白了,就是两件事:怎么把数据拿进来,以及怎么把数据弄干净。
2.1 数据获取工具选型
Python里能拿金融数据的库不少,但我个人最常用的是两个:yfinance 和 pandas-datareader。为什么?
- yfinance:雅虎财经的接口,数据全、更新快。股票、ETF、期货都能拿。我项目中90%的场景用它就够了。
- pandas-datareader:更像一个数据源聚合器,可以连FRED(美联储经济数据)、World Bank等。做宏观因子分析时特别好用。
2.2 使用yfinance获取数据
先装库:
pip install yfinance pandas-datareader
拿股票数据,代码其实就几行:
import yfinance as yf
import pandas as pd
# 获取苹果公司历史数据
aapl = yf.download('AAPL', start='2020-01-01', end='2023-12-31')
print(aapl.head())
返回的DataFrame包含:Open, High, Low, Close, Volume, Adj Close。注意这个Adj Close——调整收盘价,它已经把股息和拆股的影响算进去了。我建议你直接用这一列做回测,别自己手动调,容易出错。
拿ETF也一样:
# 获取标普500 ETF
spy = yf.download('SPY', start='2020-01-01', end='2023-12-31')
期货数据呢?yfinance也能拿,但代码符号有点讲究:
# 黄金期货(GC=F)
gold = yf.download('GC=F', start='2020-01-01', end='2023-12-31')
# 原油期货(CL=F)
oil = yf.download('CL=F', start='2020-01-01', end='2023-12-31')
2.3 使用pandas-datareader获取数据
这个库更适合拿宏观经济数据。比如从FRED拿美国10年期国债收益率:
import pandas_datareader.data as web
import datetime
start = datetime.datetime(2020, 1, 1)
end = datetime.datetime(2023, 12, 31)
# 10年期国债收益率
tnx = web.DataReader('DGS10', 'fred', start, end)
print(tnx.head())
你想想看,做统计套利时,如果只盯着股票价格,很容易忽略宏观环境的变化。我建议你把利率、通胀这些因子也拉进来,作为辅助判断。
2.4 数据清洗与预处理
数据拿到手,别急着跑模型。先看看有没有脏数据。我总结了一套标准流程:
2.4.1 处理缺失值
金融数据缺失的原因很多:节假日、停牌、数据源故障。处理方法有三种:
| 方法 | 适用场景 | 代码示例 |
|---|---|---|
| 向前填充 | 非交易日的缺失(如周末) | df.fillna(method='ffill') |
| 向后填充 | 数据延迟发布 | df.fillna(method='bfill') |
| 插值法 | 少量随机缺失 | df.interpolate() |
| 直接删除 | 缺失比例极小(<1%) | df.dropna() |
我个人习惯:先用 df.isnull().sum() 看看缺失分布。如果连续缺失超过3天,我会标记出来,不强行填充——因为这种缺失往往意味着有特殊情况。
2.4.2 处理异常值
异常值怎么找?我常用两种方法:
- 标准差法:超过均值±3倍标准差的值视为异常
- IQR法:低于Q1-1.5*IQR或高于Q3+1.5*IQR的值视为异常
# 标准差法示例
mean = df['Close'].mean()
std = df['Close'].std()
outliers = df[(df['Close'] > mean + 3*std) | (df['Close'] < mean - 3*std)]
# IQR法示例
Q1 = df['Close'].quantile(0.25)
Q3 = df['Close'].quantile(0.75)
IQR = Q3 - Q1
outliers_iqr = df[(df['Close'] < Q1 - 1.5*IQR) | (df['Close'] > Q3 + 1.5*IQR)]
发现异常值后怎么办?别急着删。先看看是不是真实的市场事件——比如2020年3月的暴跌,那可不是异常值,那是真实行情。我一般会结合新闻和成交量来判断。
2.4.3 调整股息与拆股
yfinance的Adj Close已经帮我们处理了股息和拆股。但如果你用的是原始价格(Close),就得自己调。
为什么必须调?举个例子:一只股票100元,分红2元,除息日开盘价会变成98元。如果你用原始价格,会看到一个"下跌",但实际你拿到了2元现金。不调整的话,回测结果会失真。
调整方法:
# 手动计算调整因子(如果yfinance没提供Adj Close)
# 假设有除息日数据
df['Adjustment Factor'] = df['Close'] / df['Close'].shift(1)
# 但说实话,直接用yfinance的Adj Close省心多了
2.5 数据清洗完整流程
我把上面这些步骤串成一个函数,每次拿数据后直接调用:
def clean_market_data(df):
"""
市场数据清洗流水线
"""
# 1. 只保留调整收盘价
df = df[['Adj Close']].copy()
# 2. 检查缺失值
missing_pct = df.isnull().mean()
if missing_pct > 0.05:
print(f"警告:缺失比例 {missing_pct:.2%},建议检查数据源")
# 3. 向前填充缺失值(最多连续3天)
df = df.fillna(method='ffill', limit=3)
# 4. 删除剩余缺失
df = df.dropna()
# 5. 异常值检测(IQR法)
Q1 = df['Adj Close'].quantile(0.25)
Q3 = df['Adj Close'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 3*IQR # 放宽到3倍IQR,减少误杀
upper = Q3 + 3*IQR
df = df[(df['Adj Close'] >= lower) & (df['Adj Close'] <= upper)]
return df
这个函数我用了两年多,基本没出过问题。你拿去改改就能用。
2.6 本章知识体系
下面这张图把数据获取和清洗的流程串起来了:
这张图把整个流程分成了三层:数据源 → 获取方式 → 清洗步骤。你每次拿到新数据,按这个流程走一遍,基本不会出大问题。
好了,数据准备这块就讲到这里。记住一句话:数据质量决定模型上限。花80%的时间在数据上,20%的时间建模,这是量化交易里最划算的投入产出比。