Python复现VIX恐慌指数:公式拆解、数据清洗与验证全指南

发布时间:2026/9/13 6:39:47
Python复现VIX恐慌指数:公式拆解、数据清洗与验证全指南 简介面向金融量化初学者和 Python 数据分析者的 VIX 恐慌指数计算资源包定位在帮助读者打通从原始行情数据到波动率指数的完整计算链路并借助期权价格与市场利率洞察情绪变化。压缩包共 7 个文件大小 704KB包含 4 个 CSV 数据文件期权行情文件提供行权价、到期日和期权价格用于估算隐含波动率SHIBOR 利率及 iVIX 序列用于观察资金面波动交易日历则帮助对齐时间轴。另有 iVIX.py 核心脚本、HTML 历史数据页面和恐慌指数走势图数据与代码配合紧密便于按步骤复现。资源已吸引 2289 人浏览学习适合作为金融工程、量化交易方向的上手练习也可作为期权定价与波动率建模课程的小型案例。通过阅读 iVIX.py 和对照数据文件读者可以理解 Black-Scholes 期权定价框架内隐含波动率的反推思路掌握 pandas 进行数据清洗与合并、处理交易日历缺失值以及用可视化图形评估市场恐慌程度的方法为后续构建更完整的风险监测工具打下基础。1. 打开 python 恐慌指数计算 VIX.zip 之前先把公式背后的数据口径想清楚拿到python恐慌指数计算VIX.zip这类压缩包第一反应往往是解压、看代码、跑一遍。但真正卡住你的通常不是那十几行核心公式而是数据对不上同一个交易日的 VIX官网显示 18.6本地脚本算出来 20.3偏差来自哪一步这个问题的答案几乎都落在行权价筛选、到期时间精度、无风险利率取值这三件事上。VIX 是 CBOE 基于 SP 500 指数期权SPX的报价反推出的 30 天预期波动率公式公开了快二十年难在把“公开公式”翻译成“对真实行情可复现的程序”。这篇文章按公式拆解、数据清洗、核心实现、误差验证四步走把每个参数为什么这么定讲清楚适合准备做期权波动率监控、量化风控或者只是想把 VIX 当风险指标来用的开发与研究人员。2. VIX 公式拆解从 CBOE 白皮书到可落地的数学表达VIX 不是把一堆隐含波动率做个加权平均那么简单它本质上是给一个 30 天期限的方差互换合约定价。理解这一点之后公式的每一段就都有了具体的金融含义。2.1 远期价格 F 与执行价 K0整条期权链先要找到锚点计算 VIX 的第一步是从当前期权链里找到一个“平值”位置作为锚点。CBOE 的做法是对同一个到期日找到让看涨期权价格与看跌期权价格之差绝对值最小的执行价记为 K0符号含义取值口径K0平值执行价使 call 价格 - put 价格绝对值最小的执行价F远期价格F K0 e^(rT) × (call(K0) - put(K0))r无风险利率与 T 同时间单位的连续复利利率T到期时间剩余分钟数 / 年分钟数不是简单天数这背后的金融逻辑是 call-put 平价关系对于同一执行价的欧式期权call 与 put 的价格差应当等于远期价格减执行价的贴现值。所以从期权报价反推出来的 F就是市场对指数到期水平的预期。注意这里用的执行价正是 K0 那一条因为在这个位置期权价格对 F 的变化最敏感反推出来的误差最小。这个步骤常见的一个错误是直接用“距离当前价格最近的执行价”当 K0或者用期货价格代替 F。前一种做法在 SPX 执行价间距为 5 或 10 美元的盘口上问题不大但当指数点差较大、执行价不再是均匀间距时会让后续所有 OTM 期权的筛选区间整体偏移而用期货价格代替 F 的问题在于期货合约与期权到期日不一致时引入了基差。代码层面的表达很直接# legs 为当前到期日的 call/put 合并表包含 strike、call_mid、put_mid legs[cp_diff] legs[call_mid] - legs[put_mid] idx_k0 legs[cp_diff].abs().idxmin() K0 legs.loc[idx_k0, strike] F K0 math.exp(r * T) * legs.loc[idx_k0, cp_diff]这里math.exp(r * T)把连续复利系数从利率和时间的乘积还原出来cp_diff的正负决定了 F 相对 K0 的方向。如果计算出的 F 与 K0 偏离超过 2%通常意味着这个到期日的期权链流动性不足或者 bid/ask 报价有明显陈旧。2.2 两期限方差 σ² 的计算逐腿贡献与 ΔK 的取法有了 F 和 K0下一步对执行价区间内的每一腿期权计算贡献量然后汇总成该到期日的模型方差σ² (2/T) × Σ( ΔK_i / K_i² ) × e^(rT) × Q(K_i) - (1/T) × (F/K0 - 1)²其中 Q(K_i) 是在执行价 K_i 上“虚值期权”的报价中点。具体规则是K0 之下的执行价全部取看跌期权 put 的 mid 价格K0 之上的执行价全部取看涨期权 call 的 mid 价格而 K0 本身那一条取 call 与 put 报价中点的平均。这个规则很好理解虚值期权包含的时间价值信息最纯粹且 Spread 相对更窄不会受到深实值期权流动性差的干扰。ΔK_i 是第 i 个执行价与相邻执行价的间距折半# 左右相邻执行价差的一半边界处直接使用单边差值 strikes legs[strike].to_numpy() upper np.roll(strikes, -1) lower np.roll(strikes, 1) delta_k np.where( np.isin(strikes, strikes), (upper - lower) / 2, 0 ) # 修正边界 delta_k[0] strikes[1] - strikes[0] delta_k[-1] strikes[-1] - strikes[-2]第二个减法项(F/K0 - 1)²是方差互换的对数收益修正。它把“从 K0 到 F 的离散跳跃”从整个组合收益里剥离出来否则连 F 都不等于 K0 时方差会被系统性高估。这一项在正常市场里通常很小但当 F 与 K0 偏差明显时它能撬动小数点后第三位的差异。整个区间并不包含全部执行价而是从 K0 开始向下和向上各取一段连续报价。CBOE 的规则是在某个方向上如果连续出现两个执行价没有有效买入报价bid 为 0则截断该方向。实操中许多本地实现简化成“取 K0 到最大可用执行价之间的所有腿”这在 SPX 深度虚值期权报价充足的时期没问题但在极端行情下会让尾部价值被高估。2.3 近月与次近月加权合成 30 天固定期限VIX 的定义是“未来 30 天的预期波动率”但期权到期日很少恰好落在 30 天所以 CBOE 的做法是同时使用两个到期日一个剩余期限小于 30 天近月一个大于 30 天次近月。若近月距到期不足 8 天则弃掉它向后顺延一组避免到期前的价格扭曲污染指标。两个期限的权重按时间距离线性分配T1, T2 expiry_1, expiry_2 # 年化时间T1 1/12 T2 w1 (T2 - 1/12) / (T2 - T1) w2 (1/12 - T1) / (T2 - T1) vix 100 * math.sqrt( w1 * (T1 * var1) w2 * (T2 * var2) ) * math.sqrt(365/30)权重本质上是把两个期限的方差按“剩余 30 天距离”做线性插值w1 与 w2 之和恒等于 1。当 T1 非常接近 30 天时w1 接近 1当 T1 只有 9 天时次近月会被赋予更高权重。因此 VIX 对近月消息的敏感度不是固定的它会随着日历位置自然变化这也是用 VIX 做择时的人需要理解的特性。3. 数据准备期权链、利率与到期时间的清洗公式只是骨架数据才是血肉。真实世界里没有任何一份数据源直接给你“已按 CBOE 规则清洗好的 SPX 期权链”无论是本地压缩包里的 CSV还是从行情接口拿到的原始表都需要自己做规整。3.1 zip 里期权链的标准字段与缺失处理我接触过的 VIX 计算压缩包数据结构五花八门但最终都能归约成三类期权链数据、无风险利率序列、到期时间配置。与其花时间找什么 zip 密码移除工具不如先确认里面的 CSV 是否包含下表这些字段字段类型缺失时的处理strikefloat无法计算直接丢弃该行option_typestr必须能区分 call / put否则合并时出错bid / askfloat双零则剔除该腿单侧为零用 lastPrice 补lastPricefloat仅作兜底不应作为首选报价expiration / expiry_dtdatetime用于计算 T缺失时从文件名推断可能不准volume / openInterestint建议保留辅助判断流动性但不参与公式读取 zip 内的 CSV 不需要先解压pandas 可以直接从压缩包内读取import zipfile import pandas as pd with zipfile.ZipFile(python恐慌指数计算VIX.zip) as z: names z.namelist() if calls.csv in names and puts.csv in names: calls pd.read_csv(z.open(calls.csv)) puts pd.read_csv(z.open(puts.csv)) else: legs pd.read_csv(z.open(options_chain.csv))这里有个容易忽略的点z.open()返回的是二进制文件对象pd.read_csv可以直接接收它不需要先把文件写入临时目录。如果你发现压缩包里的 CSV 是 GBK 编码记得在read_csv中加encodinggbk否则中文表头会直接报错。至于爬虫抓 CBOE 官网的 HTML 页面被反爬拦截的成本远高于直接找一份结构化行情文件所以 zip 里如果已经有 CSV优先用它。3.2 用 yfinance 一次拿齐 SPX 期权链与无风险利率如果你拿到的压缩包只有计算代码、没有数据那么最省事的实时数据源是 yfinance。它同时提供 SPX 的期权链和 13 周美国国债收益率^IRX后者可以直接当作无风险利率 r 的近似。在 Python 3.9 以上的环境安装依赖后就能跑通pip install yfinance pandas numpy然后按下面这个模式抽取最近的期权到期日import yfinance as yf from datetime import datetime spx yf.Ticker(^SPX) # 获取全部可交易到期日并按日期排序 expirations [datetime.strptime(d, %Y-%m-%d).date() for d in spx.options] today datetime.now().date() # CBOE 要求距到期至少 8 天这里同时排除当天到期的合约 valid sorted(e for e in expirations if (e - today).days 8) near_expiry, next_expiry valid[0], valid[1]这个方案的优势是整个计算链路只依赖^SPX和^IRX两个 ticker数据源统一时间戳天然对齐。需要说明的是yfinance 的期权链在盘中有一定延迟且部分执行价的 bid/ask 会出现单侧为 0所以它更适合做日终计算或者研究回测不适合做实时盘中监控。关于到期时间 T精确到天远远不够。SPX 是欧式期权结算价格在到期日上午 8:30 美东时间确定所以 T 应该用分钟数折算成年SETTLEMENT_TS datetime(expiry.year, expiry.month, expiry.day, 8, 30) remaining SETTLEMENT_TS - datetime.now() T remaining.total_seconds() / (365 * 24 * 3600)把8, 30写死是为了对齐 CBOE 官方口径。如果直接用到期日当天的零点和当前时间相减在收盘后计算时 T 会凭空少半天对应到方差上的误差可以忽略但当 VIX 处于低波动区间这点误差就足以让你的结果在 0.2 个点上下浮动。3.3 合并 call/put 成中间表拿到某一天到期日的 call 表和 put 表之后需要按执行价合并成一张宽表。合并的键是strike因为 CBOE 公式需要同一执行价上 call 与 put 的报价来计算 F 和 K0chain spx.option_chain(near_expiry.strftime(%Y-%m-%d)) calls, puts chain.calls.copy(), chain.puts.copy() # 统一列名后按执行价合并 keep [strike, bid, ask, lastPrice] merged calls[keep].merge( puts[keep], onstrike, suffixes(_call, _put) ) # mid 价格优先买卖价中点兜底 last merged[call_mid] (merged[bid_call] merged[ask_call]) / 2 merged[put_mid] (merged[bid_put] merged[ask_put]) / 2 merged[call_mid] merged[call_mid].fillna(merged[lastPrice_call]) merged[put_mid] merged[put_mid].fillna(merged[lastPrice_put]) # 剔除双零报价的无效腿 merged merged[(merged[call_mid] 0) | (merged[put_mid] 0)]这里坚持用买卖价中点而不是最后成交价是因为做市商报价的中点更能反映当前市场的公允预期而 lastPrice 往往滞后于盘口甚至在行情剧烈波动时出现明显偏离。合并之后这张表就是第 4 章主函数的直接输入。4. 核心实现从原始期权链到 VIX 数值的完整 Python 代码前面所有的清洗动作最终都要汇入一个可复核的计算流程。这部分给出一个我从多个开源实现中提炼出来的骨架它接受第 3 章生成的legs中间表输出该到期日的模型方差再由外部函数合成 VIX。4.1 从 DataFrame 到方差单到期日方差计算函数核心函数做四件事定位 K0 与 F、筛选 OTM 区间、逐腿累加、执行对数修正。下面是完整的 Python 实现import math import numpy as np import pandas as pd def compute_expiry_variance(legs: pd.DataFrame, r: float, T: float) - float: 参数 legs : 必须包含 strike / call_mid / put_mid 三列 r : 连续复利无风险利率小数非百分数 T : 年化到期时间例如 25 天折算为 25/365 返回 该到期日的模型方差未乘 T 的原始值 legs legs.sort_values(strike).reset_index(dropTrue) strikes legs[strike].to_numpy() # 1. 计算 F 与 K0 cp_diff (legs[call_mid] - legs[put_mid]).to_numpy() idx_k0 int(np.argmin(np.abs(cp_diff))) K0 strikes[idx_k0] F K0 math.exp(r * T) * cp_diff[idx_k0] # 2. 计算每行执行价的 ΔK半间距 delta_k np.zeros_like(strikes) delta_k[1:-1] (strikes[2:] - strikes[:-2]) / 2 delta_k[0] strikes[1] - strikes[0] delta_k[-1] strikes[-1] - strikes[-2] # 3. 确定 OTM 行权价区间K0 以下用 put以上用 call mid_price np.where(strikes K0, legs[put_mid], legs[call_mid]) # K0 本身取两腿中点 mid_price[idx_k0] (legs[call_mid][idx_k0] legs[put_mid][idx_k0]) / 2 # 4. 剔除报价无效的腿 valid mid_price 0 strikes strikes[valid] delta_k delta_k[valid] mid_price mid_price[valid] contribution delta_k / (strikes ** 2) * math.exp(r * T) * mid_price var_sum (2.0 / T) * np.sum(contribution) correction (1.0 / T) * ((F / K0 - 1) ** 2) return var_sum - correction第 2 步的半间距计算是关键细节。SPX 的执行价在平值附近是 5 美元一档但向两侧延伸后可能变成 10 美元一档如果统一用一个固定间距高行权价区域的贡献就会被低估。delta_k[1:-1]用前后执行价差值的一半自动适配非均匀间距边界腿直接用单边差值因为最外侧只有一侧有邻居。第 3 步的np.where(strikes K0, put_mid, call_mid)保证每一条腿都是虚值期权。这里有个取舍当某执行价上 call 和 put 都失效都为 0时mid_price为 0第 4 步会把它剔除。但如果只有 put 为 0 而 call 正常np.where会把该行也当作 call 腿保留这种残差通常出现在深实值区域对结果影响很小但你要知道它的存在。4.2 时间加权合成 VIX 完整脚本有了单到期日的方差再加上第 3 章的合并逻辑整个流程可以串成下面这个伪主函数def compute_vix(spx_ticker, irx_ticker): r yf.Ticker(irx_ticker).history(period5d)[Close].iloc[-1] / 100.0 expirations get_valid_expiries(spx_ticker) # 第 3 章代码 T1_raw time_to_expiry(expirations[0]) T2_raw time_to_expiry(expirations[1]) legs1 build_merged_legs(spx_ticker, expirations[0]) legs2 build_merged_legs(spx_ticker, expirations[1]) var1 compute_expiry_variance(legs1, r, T1_raw) var2 compute_expiry_variance(legs2, r, T2_raw) w1 (T2_raw - 30 / 365) / (T2_raw - T1_raw) w2 (30 / 365 - T1_raw) / (T2_raw - T1_raw) vix 100 * math.sqrt(w1 * T1_raw * var1 w2 * T2_raw * var2) vix vix * math.sqrt(365 / 30) return vix, {T1: T1_raw, T2: T2_raw, w1: w1, w2: w2}参数表中的几个常量直接影响结果精度建议显式声明在配置区而非散落在代码里参数建议值说明MIN_DAYS_TO_EXPIRY8CBOE 要求的近月最短剩余天数YEAR_DAYS365年化天数不要用 360SETTLEMENT_HOUR8.5SPX 到期结算时间 8:30 AM 美东30 / 365固定 30 天期限VIX 指数定义T1_raw * var1这一步容易看晕var1已经是“单位时间方差近似”乘上 T 之后再和时间权重 w1 线性加权最终开方得到年化波动率再乘 100 变成指数点位。4.3 边界条件与易错点K0 处取 put 还是平均计算到一半最容易出问题的位置是 K0 那一条腿。严格按 CBOE 口径K0 处 call 与 put 的 mid 各取一半权重但不少开源实现为了省事直接取 put_mid 作为 K0 的报价。这两种写法的差异在正常市场里通常小于 0.1 个 VIX 点位原因是 K0 附近 call 和 put 的 mid 非常接近价差很小。而在快速下跌行情中put 的 mid 会明显高于 call此时用 put 单腿会让尾部区间整体上移导致 VIX 略微高估。另一个高频错误是对整个执行价区间做“全区间平均 mid”再回填缺失值这会抹平深度虚值期权之间的相对价值结构让方差偏小。正确的做法是我前面写的保留每一条腿的原始 mid只在单条腿报价缺失时用 lastPrice 兜底而不是用统计量填充。提示当某腿的 bid 为 0 但 ask 正常时不要直接丢弃整行。可以把 mid 替换为ask * 0.5保留这条腿对尾部的贡献。只有当 bid 和 ask 同时为 0 时才剔除该行。这个规则在极端行情下比“全保留”和“全丢弃”更接近官方口径。5. 验证没有捷径把算出来的 VIX 与官方值、收益率序列对齐本地脚本输出一个数字很容易难的是确认这个数字具备参考价值。我的做法是三段式验证先和官方收盘 VIX 对比再检查中间变量是否落入合理区间最后用历史序列做一次相关性粗筛。5.1 和官方 VIX 对齐偏差方向即故障方向选一个最近交易日拉当天的期权链算出 VIX与 CBOE 官方收盘值对比。误差在 ±0.5 以内属于正常因为 yfinance 的 mid 报价和官方内部撮合数据存在细微差异。如果偏差超过这个范围偏差方向能直接给出排查线索偏差特征优先排查点系统性偏高 0.5 以上r 取值过大put 腿用了 ask 而非 mid系统性偏低 0.5 以上行权价覆盖区间太窄深度虚值腿被错误剔除只在波动剧烈日偏大K0 处直接用了 put midF 与 K0 偏离超 2%数值乱跳无稳定规律T 的天数折算错误到期时间用了零点而非 8:30其中利率 r 是最容易被忽视的变量。^IRX的收盘价是百分数比如显示 5.32实际利率是 0.0532忘记除以 100 会让方差整体上移。这个错误在 VIX 处于 20 以下时能把结果拉高 0.3 到 0.6 个点。5.2 三个一般人不会查的验证点第一分钟精度对 T 的影响。用到期日零点还是 8:30 结算时间在盘中计算时差值最大可达半天。对 25 天到期的期权来说半天对应 T 约 0.0014 年方差误差约千分之一放大到 VIX 点位后大约 0.2。只有把 T 的每一分钟都榨干才能让日间计算结果保持稳定。第二权重连续性检查。合法状态下 w1 与 w2 之和必须恒等于 1且 30/365 必须落在 T1 与 T2 之间。如果你的近月到期日只剩 3 天而代码没有执行 8 天剔除规则w2 会出现负数最终输出一个虚高的 VIX。可以在主函数里加一行断言assert abs(w1 w2 - 1) 1e-9, 时间权重不为 1检查到期日选择 assert T1_raw 30/365 T2_raw, 30 天不在两个到期日之间第三历史序列相关性检验。算过去 250 个交易日的 VIX 序列与同期 SPX 日收益率做 Pearson 相关正常情况下应当落在 -0.7 到 -0.9 之间。如果算出正相关或接近 0说明数据源存在系统性缺陷通常是期权链报价被整体平移或同一天重复使用。5.3 做一个最小单测固话正确性最后给一个不依赖行情数据的验证技巧人工构造一个只有 3 个执行价的迷你期权链赋予对称的 call/put 价格和固定 T、r然后手算预期方差用函数输出和手算结果对比。这个测试能在没有网络的环境下排查公式逻辑错误也能在更换数据源时快速确认核心代码没有被破坏。构造数据时让 F 尽量接近 K0这样修正项趋近于零任何输出偏差都能直接定位到累加环节。本文还有配套的精品资源点击获取