3. 数据获取与预处理:从零搭建你的量化数据库
做量化交易,尤其是波动率预测,第一步永远不是写模型,而是搞定数据。我见过太多人一上来就调包跑LSTM,结果数据没对齐,回测曲线漂亮得像假的一样——嗯,其实就是假的。今天这章,咱们就踏踏实实把数据这块地基打牢。
3.1 用yfinance获取股票/期权数据
yfinance这个库,说白了就是雅虎财经的非官方Python接口。我最早用的时候它还叫yahoo-finance,后来雅虎把API关了,社区又搞出了yfinance。现在它依然是获取免费行情数据最方便的工具,没有之一。
先装包:
pip install yfinance pandas matplotlib
获取股票数据很简单:
import yfinance as yf
# 获取苹果股票数据
aapl = yf.download('AAPL', start='2023-01-01', end='2024-01-01')
print(aapl.head())
这里有个坑,我踩过好几次:yfinance默认返回的数据是调整后的价格(Adjusted Close)。对于期权定价和波动率计算,我们通常需要原始收盘价。我个人习惯在获取时加上参数 auto_adjust=False:
aapl_raw = yf.download('AAPL', start='2023-01-01', end='2024-01-01', auto_adjust=False)
获取期权数据稍微复杂一点:
# 获取期权链
ticker = yf.Ticker('AAPL')
expirations = ticker.options # 获取所有到期日
print(expirations[:5]) # 打印前5个到期日
# 获取特定到期日的期权链
opt_chain = ticker.option_chain(expirations[0])
calls = opt_chain.calls
puts = opt_chain.puts
print(calls[['strike', 'lastPrice', 'impliedVolatility']].head())
你看,期权数据里直接包含了隐含波动率(impliedVolatility),这对我们做波动率预测模型来说,简直是现成的标签数据。
3.2 数据清洗与对齐
真实世界的数据从来不会干干净净。我处理过最离谱的一次,某只股票某天成交量是0,但价格却跳涨了20%。你想想看,这明显是数据错误。
常见的脏数据问题:
- 缺失值:节假日、停牌日导致数据缺失
- 异常值:价格跳空、成交量异常
- 时间戳不对齐:不同市场交易时间不同
我的清洗流程是这样的:
import pandas as pd
import numpy as np
def clean_stock_data(df):
# 1. 处理缺失值
df = df.dropna(subset=['Close']) # 删除收盘价为空的记录
# 2. 处理异常值(3倍标准差法)
for col in ['Close', 'Volume']:
mean = df[col].mean()
std = df[col].std()
df = df[(np.abs(df[col] - mean) <= 3 * std)]
# 3. 向前填充缺失的日期(非交易日)
df = df.asfreq('D').fillna(method='ffill')
return df
# 对齐多个股票数据
def align_multiple_stocks(tickers, start, end):
data = {}
for ticker in tickers:
df = yf.download(ticker, start=start, end=end)
data[ticker] = clean_stock_data(df)['Close']
# 合并并对齐
aligned = pd.DataFrame(data)
aligned = aligned.dropna() # 删除任何一只股票缺失的日期
return aligned
# 使用示例
tickers = ['AAPL', 'MSFT', 'GOOGL']
aligned_prices = align_multiple_stocks(tickers, '2023-01-01', '2024-01-01')
print(aligned_prices.head())
3.3 计算日收益率与对数收益率
为什么要算收益率而不是直接用价格?因为价格是非平稳的,而收益率(尤其是对数收益率)在统计上更友好。说白了,用价格建模就像在沙滩上盖楼——地基不稳。
两种收益率的计算:
# 简单收益率
df['simple_return'] = df['Close'].pct_change()
# 对数收益率(连续复利收益率)
df['log_return'] = np.log(df['Close'] / df['Close'].shift(1))
# 对比一下
print(df[['Close', 'simple_return', 'log_return']].head())
你可能会问:到底用哪个?我的建议是:
- 简单收益率:用于计算投资组合的实际盈亏
- 对数收益率:用于建模和统计分析(因为它的分布更接近正态,且具有时间可加性)
我记得有一次做波动率预测,用简单收益率算出来的波动率总是有偏,换成对数收益率后,模型效果立马提升了一个档次。原因很简单:对数收益率可以跨时间累加,而简单收益率不行。
3.4 数据可视化基础
可视化不是花架子,它是你发现数据问题的第一道防线。我每次拿到新数据,第一件事就是画图——用眼睛看,比任何统计检验都直观。
几个必备的可视化技巧:
import matplotlib.pyplot as plt
import seaborn as sns
# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 1. 价格走势图
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 左上:收盘价走势
axes[0, 0].plot(df.index, df['Close'], label='收盘价', color='#2E86AB')
axes[0, 0].set_title('收盘价走势')
axes[0, 0].legend()
# 右上:收益率分布
axes[0, 1].hist(df['log_return'].dropna(), bins=50, color='#A23B72', alpha=0.7)
axes[0, 1].set_title('对数收益率分布')
axes[0, 1].axvline(x=0, color='red', linestyle='--')
# 左下:收益率QQ图(检验正态性)
from scipy import stats
stats.probplot(df['log_return'].dropna(), dist='norm', plot=axes[1, 0])
axes[1, 0].set_title('QQ图(正态性检验)')
# 右下:波动率聚类现象
df['volatility'] = df['log_return'].rolling(20).std() * np.sqrt(252)
axes[1, 1].plot(df.index, df['volatility'], color='#F18F01')
axes[1, 1].set_title('20日滚动年化波动率')
plt.tight_layout()
plt.show()
看到右下角的波动率图了吗?你会发现波动率有聚类现象——高波动率后面跟着高波动率,低波动率后面跟着低波动率。这就是我们做波动率预测的理论基础之一。
3.5 本章知识体系
下面这张图,是我做这章内容时画的框架。它把数据获取到预处理的全流程串起来了:
这张图把整个流程串起来了。你跟着走一遍,基本不会出大问题。嗯,至少数据这块不会翻车。
3.6 避坑指南
最后分享几个我踩过的坑,希望能帮你省点时间:
- 不要直接用调整后的价格做期权分析——我吃过这个亏,算出来的隐含波动率偏差大到没法看。
- 对齐数据时,先检查交易时间——美股和A股交易时间不同,直接合并会出问题。
- 收益率计算前,先检查是否有0值——如果某天收盘价为0(数据错误),pct_change会返回inf。
- 可视化时,记得看分布形态——如果收益率分布严重偏态,你的正态假设可能不成立。
好了,数据准备就绪。下一章,我们会正式进入波动率模型的世界。不过在那之前,建议你把今天的内容动手跑一遍——数据搞定了,后面的事就顺了。