第二章 数据准备:获取历史期权数据与标的资产数据

做波动率套利,数据就是你的弹药库。弹药不行,枪法再好也白搭。

这一章,咱们聊聊怎么搞到靠谱的隐含波动率和历史波动率数据。我见过太多人,策略逻辑写得漂漂亮亮,结果数据源一塌糊涂,最后回测结果根本没法用。嗯,这里面的坑,我踩过不少。

2.1 数据源的选择:去哪儿拿数据?

说实话,数据源的选择直接决定了你后续工作的质量。我个人习惯把数据源分成三类:

数据源类型 代表 优点 缺点
商业金融终端 Wind、Bloomberg 数据全、清洗好、接口稳定 贵,一年几万到几十万
开源/免费数据 Yahoo Finance、Tushare、AKShare 免费、社区活跃 数据质量参差不齐,可能有缺失
交易所直连 上交所、深交所、CBOE 最原始、最权威 需要自己解析,技术门槛高

如果你刚开始做研究,我建议先用免费数据跑通逻辑。但记住——免费数据一定要做交叉验证。我曾经用某免费接口拿50ETF期权数据,结果某天的隐含波动率突然跳了20个点,后来发现是数据源把行权价搞错了。

2.2 隐含波动率数据的获取

隐含波动率(IV)是期权市场给出来的"情绪温度计"。它不像股价那样直接显示在行情里,需要从期权价格反推出来。

2.2.1 从期权价格反推IV

说白了,就是用BS模型或者更精确的数值方法,把期权价格里的波动率"解"出来。核心代码大概长这样:

# 使用Newton-Raphson法求解隐含波动率
def implied_volatility(option_price, S, K, T, r, q=0, option_type='call'):
    """
    从期权价格反推隐含波动率
    S: 标的价格
    K: 行权价
    T: 剩余期限(年)
    r: 无风险利率
    q: 股息率
    """
    max_iter = 100
    tol = 1e-6
    sigma = 0.3  # 初始猜测
    
    for i in range(max_iter):
        price_diff = bs_price(S, K, T, r, sigma, q, option_type) - option_price
        if abs(price_diff) < tol:
            return sigma
        vega = bs_vega(S, K, T, r, sigma, q)
        if vega < 1e-10:
            break
        sigma -= price_diff / vega
        sigma = max(0.01, min(2.0, sigma))  # 限制范围
    
    return sigma

这里有个坑——近月期权和远月期权的收敛速度不一样。近月期权时间价值衰减快,vega小,迭代容易震荡。我建议对剩余期限小于7天的期权,直接用二分法代替牛顿法。

2.2.2 数据字段对齐

拿到原始数据后,你会发现不同交易所、不同数据源的字段名五花八门。我习惯统一成以下格式:

# 标准化字段
standard_fields = {
    'trade_date': '交易日期',
    'option_code': '期权代码',
    'exercise_price': '行权价',
    'maturity_date': '到期日',
    'close_price': '期权收盘价',
    'underlying_close': '标的收盘价',
    'implied_vol': '隐含波动率',
    'volume': '成交量',
    'open_interest': '持仓量'
}

为什么要对齐?你想想看,后面做波动率锥的时候,需要把不同到期日的IV放在一起比较。字段名不统一,代码里全是if-else,维护起来想死的心都有。

2.3 历史波动率的计算

历史波动率(HV)是已经发生的"事实"。计算方式其实不复杂,但细节决定成败。

2.3.1 收益率计算

我一般用对数收益率,因为它更符合正态分布的假设:

# 计算对数收益率
import numpy as np
import pandas as pd

def calculate_log_returns(price_series):
    """
    计算对数收益率序列
    """
    log_returns = np.log(price_series / price_series.shift(1))
    return log_returns.dropna()

这里有个小细节——要不要包含分红除权日?我个人习惯是剔除。因为分红导致的跳空会扭曲波动率计算。我曾经没处理这个,结果某只股票的年化波动率算出来比实际高了3个点。

2.3.2 年化处理

计算出来的日波动率需要年化,才能和隐含波动率放在同一个维度比较:

# 年化波动率
def annualized_volatility(daily_vol, trading_days=252):
    """
    日波动率转年化
    trading_days: 年化天数,A股用252,美股用252,期货用365
    """
    return daily_vol * np.sqrt(trading_days)

注意:年化天数不要搞错。A股一年大概242-252个交易日,但有些策略用365天年化。我个人建议统一用252,这样和大多数金融终端保持一致。

2.4 数据清洗与对齐

数据清洗,说白了就是"去伪存真"。我总结了一套标准流程:

  1. 缺失值处理:连续缺失超过3天,直接剔除该段数据
  2. 异常值检测:IV超过100%或低于5%的,标记出来人工复核
  3. 日期对齐:期权数据和标的数据的交易日必须完全匹配
  4. 行权价对齐:只保留平值附近(0.8-1.2倍标的价格)的期权
⚠️ 避坑指南
我曾经在清洗50ETF期权数据时,发现某天的IV突然飙升到80%。查了半天,原来是那天标的有分红,期权合约做了调整,但数据源没更新合约乘数。所以——合约调整日的数据一定要单独处理

2.5 核心逻辑:数据流框架图

下面这张图,是我做波动率套利时数据处理的完整流程。你可以把它当成一个检查清单:

波动率套利数据流框架 数据源层 期权行情数据 标的资产行情数据 无风险利率/股息率 数据处理层 BS模型反推IV 对数收益率计算 年化处理 数据清洗 数据对齐层 日期对齐(交易日匹配) 期限对齐(剩余天数统一) 输出:对齐后的IV与HV数据集

2.6 实战中的几个小技巧

💡 经验之谈
  • 数据频率:做波动率锥,日频数据就够了。高频数据反而会引入太多噪音。
  • 滚动窗口:计算历史波动率时,我习惯用20天、60天、120天三个窗口。太短噪音大,太长反应慢。
  • IV期限结构:不同到期日的IV不要直接平均,要用插值法统一到标准期限上。

数据准备这块,看起来琐碎,但真的值得花时间。我见过太多人,策略逻辑写得天花乱坠,结果数据没对齐,回测曲线漂亮得不行,实盘一跑就崩。嗯,数据质量决定了你的策略天花板。

准备好了数据,下一步就是构建波动率锥了。不过那是下一章的事,咱们先把数据这关过了再说。


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