第一章:数据获取与清洗——用yfinance搞定期权数据

做波动率期限结构分析,第一步就是搞数据。这活儿看着简单,其实坑不少。我刚开始做期权量化那会儿,光在数据清洗上就栽过好几个跟头。今天咱们就聊聊,怎么用Python把期权数据从yfinance里拽出来,再收拾得干干净净。

1.1 为什么选yfinance?

市面上能拿期权数据的渠道不少,但yfinance有个好处——免费、简单、覆盖面广。虽然它数据精度比不上Bloomberg或者Wind,但做策略研究和回测完全够用。我个人习惯先用yfinance快速验证想法,等策略成熟了再换专业数据源。

说白了,yfinance就像一把瑞士军刀。功能不算最强,但胜在顺手。你想想看,几行代码就能拿到标普500期权的完整链数据,这性价比没谁了。

1.2 安装与导入

先装包,这个简单:

pip install yfinance pandas numpy

导入的时候我建议这样写:

import yfinance as yf
import pandas as pd
import numpy as np
from datetime import datetime, timedelta

嗯,这里要注意一点——yfinance版本更新挺勤快的。我去年有个项目,就因为版本不一致,跑出来的数据格式全变了。建议你固定版本号:

pip install yfinance==0.2.28

1.3 获取期权链数据

核心逻辑其实就三步:选标的、拿期权链、提取具体合约。咱们以SPY为例:

# 获取SPY期权链
spy = yf.Ticker("SPY")

# 查看所有到期日
expirations = spy.options
print(f"可用到期日: {expirations[:5]}")  # 只看前5个

# 获取最近到期日的期权链
opt_chain = spy.option_chain(expirations[0])

# 看涨和看跌数据
calls = opt_chain.calls
puts = opt_chain.puts

这里有个坑——option_chain()返回的数据里,strike列是行权价,lastPrice是最新成交价,impliedVolatility就是我们要的隐含波动率。我曾经遇到过数据里impliedVolatility全是NaN的情况,后来发现是当天没有交易,换了个交易日就好了。

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

拿到原始数据后,清洗是重头戏。我总结了一套标准流程:

  1. 去空值:删除隐含波动率或成交量为空的记录
  2. 去异常值:隐含波动率超过200%或低于5%的,基本是数据错误
  3. 去深度实值/虚值:Delta绝对值小于0.05或大于0.95的合约,流动性太差
  4. 去临近到期:到期日小于3天的合约,价格行为太诡异

代码实现长这样:

def clean_option_data(df, min_dte=3, max_iv=2.0, min_iv=0.05):
    """
    清洗期权数据
    df: 原始DataFrame
    min_dte: 最小到期天数
    """
    # 计算到期天数
    df['dte'] = (df['expiration'] - datetime.now()).dt.days
    
    # 清洗逻辑
    clean_df = df[
        (df['impliedVolatility'].notna()) &  # 去空值
        (df['volume'] > 0) &                 # 去零成交量
        (df['impliedVolatility'] < max_iv) &  # 去异常高IV
        (df['impliedVolatility'] > min_iv) &  # 去异常低IV
        (df['dte'] >= min_dte)               # 去临近到期
    ]
    
    return clean_df

避坑指南:我曾经在清洗时忘了处理除权除息日的数据。那天SPY分红,期权价格剧烈波动,隐含波动率直接飙到300%+。如果不剔除这些异常点,整个期限结构曲线都会变形。

1.5 构建标准化数据集

清洗完数据后,我们需要把它整理成统一格式。我个人习惯用这种结构:

字段名 类型 说明
ticker str 标的代码
date datetime 数据日期
expiration datetime 到期日
strike float 行权价
option_type str call/put
implied_vol float 隐含波动率
delta float Delta值
volume int 成交量
open_interest int 未平仓量
dte int 到期天数

标准化函数:

def standardize_option_data(raw_df, ticker, data_date):
    """
    将原始数据转为标准化格式
    """
    std_df = pd.DataFrame()
    std_df['ticker'] = ticker
    std_df['date'] = data_date
    std_df['expiration'] = raw_df['expiration']
    std_df['strike'] = raw_df['strike']
    std_df['option_type'] = raw_df['option_type']
    std_df['implied_vol'] = raw_df['impliedVolatility']
    std_df['delta'] = raw_df['delta']
    std_df['volume'] = raw_df['volume']
    std_df['open_interest'] = raw_df['openInterest']
    std_df['dte'] = (raw_df['expiration'] - data_date).dt.days
    
    return std_df

1.6 完整流程——从零到标准化数据集

把上面所有步骤串起来,就是一个完整的pipeline:

def get_clean_option_data(ticker, target_date=None):
    """
    一站式获取并清洗期权数据
    """
    if target_date is None:
        target_date = datetime.now()
    
    # 获取数据
    stock = yf.Ticker(ticker)
    expirations = stock.options
    
    all_data = []
    for exp in expirations[:5]:  # 取最近5个到期日
        chain = stock.option_chain(exp)
        
        # 合并call和put
        calls = chain.calls.copy()
        puts = chain.puts.copy()
        calls['option_type'] = 'call'
        puts['option_type'] = 'put'
        
        combined = pd.concat([calls, puts])
        combined['expiration'] = pd.to_datetime(exp)
        all_data.append(combined)
    
    # 合并所有到期日
    full_df = pd.concat(all_data, ignore_index=True)
    
    # 清洗
    clean_df = clean_option_data(full_df)
    
    # 标准化
    std_df = standardize_option_data(clean_df, ticker, target_date)
    
    return std_df

# 使用示例
spy_data = get_clean_option_data("SPY")
print(f"获取到 {len(spy_data)} 条有效期权数据")

小技巧:yfinance获取期权数据时,建议加上time.sleep(1)避免请求过快被封。我一般会写个循环,每次请求间隔1-2秒。

1.7 数据质量检查

数据清洗完,别急着用。先做个快速检查:

# 检查各到期日的合约数量
print(spy_data.groupby('dte').size())

# 检查隐含波动率的分布
print(spy_data['implied_vol'].describe())

# 检查是否有重复数据
print(f"重复数据量: {spy_data.duplicated().sum()}")

如果发现某个到期日的合约数量特别少,或者隐含波动率分布明显异常,那就要回头看看是不是清洗条件设得太严了。我一般会先宽松清洗,再逐步收紧,找到那个平衡点。

1.8 本章知识体系

下面这张图,把整个数据获取与清洗的流程串起来了:

期权数据获取与清洗流程 步骤1:数据获取 yfinance.Ticker() 步骤2:提取期权链 option_chain() 步骤3:合并数据 pd.concat() 步骤4:数据清洗 • 去空值 (impliedVolatility) • 去异常值 (IV范围) • 去深度实值/虚值 • 去临近到期 (DTE) 步骤5:标准化 • 统一字段命名 • 计算DTE • 添加元数据 • 类型转换 步骤6:质量检查 • 合约数量分布 • IV统计描述 • 重复数据检查 • 异常值复查 标准化数据集 可用于期限结构分析 常见问题 • 除权除息日异常 • 流动性不足 • 数据缺失

这张图把整个流程分成了六个步骤。从数据获取开始,到最终输出标准化数据集。中间的数据清洗和标准化是最关键的两步,也是我花时间最多的地方。

核心要点:数据清洗不是越严格越好,也不是越宽松越好。关键是要理解你的策略需要什么样的数据。做波动率期限结构分析,我们最关心的是隐含波动率的准确性和一致性。清洗标准要围绕这个目标来设定。

好了,数据获取和清洗这块就聊到这儿。下一章咱们会深入探讨如何利用这些标准化数据,构建真正的波动率期限结构曲线。


公众号:蓝海资料掘金营,微信deep3321