传统多因子模型里,「因子」通常是价值、动量、规模这些横截面上的异常。但有一个因子一直站在棋盘之外、却在每个危机里决定你的生死——恐慌本身。VIX 不是新闻,它是一个可以被定价、被交易、被做成组合一块的资产。本文的论点只有一句:把「波动率风险溢价(VRP)」当成和风险资产同等地位的一个因子,它的收益来源、风险来源、与股票的相关结构,都清清楚楚。
我们用一个 Bates 型 SVJ(随机波动率+跳跃) 模型把整条逻辑跑通:先生成带崩盘跳的随机波动路径,再据此给 VIX 与 VIX 期货定价,最后把「恐慌」拆成一个可多可空的因子。在 20 年(5040 个交易日)模拟里,模型隐含 VIX 中枢 ≈20、峰值 45,共发生 11 次崩盘跳;由此定出的 VIX 期货在平静日呈 Contango(升水)、恐慌日呈 Backwardation(贴水),全样本 67% 的交易日期货高于即期、VRP 均值 +0.74 vol 点——一个真实存在、长期为正的溢价。把它做成「做空 VIX 期货」因子,年化 7.2%、波动 15%、Sharpe 0.48,比同期股票(Sharpe 0.12)还高;它顺周期(与股票相关 +0.54),危机关头和你同跌,这正是它被定价的本质。

一、SVJ:让波动自己随机、让崩盘自己跳#
普通 BS 模型把波动当成常数,但 2020 年 3 月的 VIX 从 15 飙到 80,绝不是「常数波动」能解释的。SVJ 做两件事:用 Heston 随机方差 v_t 让波动连续地随机游走,用复合泊松跳给收益周期性地来一记崩盘。
- 方差服从 CIR:
dv = κ(θ−v)dt + σ_v√v dW - 收益:
d lnS = (μ − ½v − λm_J)dt + √v dZ + J_t,其中J_t是跳,强度λ、单跳对数收益∼N(μ_J, σ_J²) ρ(J,Z)=−0.7:跳往往伴随波动飙升(崩盘日 VIX 暴涨)m_J = E[e^{J}−1]是跳的漂移补偿,保证风险中性下股价是鞅
代码如下(纯 numpy,无外部定价库):
import numpy as np
S0, r = 100.0, 0.02
v0, kappa, theta, sigma_v, rho = 0.04, 3.0, 0.04, 0.4, -0.70
lam, muJ, sigJ = 0.6, -0.09, 0.11 # 崩盘型负跳: 平均 -9%, 年跳 0.6 次
mJ = np.exp(muJ + 0.5 * sigJ ** 2) - 1.0
MU_EQ = 0.09 # 股票股权溢价(年化)
dt = 1.0 / 252.0
def simulate_svj(steps, seed=0):
rng = np.random.default_rng(seed)
v = np.empty(steps + 1); v[0] = v0
lnS = np.empty(steps + 1); lnS[0] = np.log(S0)
Z1 = rng.standard_normal(steps); Z2 = rng.standard_normal(steps)
e = rho * Z1 + np.sqrt(1 - rho ** 2) * Z2
for t in range(steps):
vs = max(v[t], 0.0)
v[t + 1] = max(v[t] + kappa * (theta - vs) * dt + sigma_v * np.sqrt(vs) * np.sqrt(dt) * e[t], 1e-6)
j = 0.0
Nj = rng.poisson(lam * dt)
if Nj > 0:
j = Nj * muJ + np.sqrt(Nj) * sigJ * rng.standard_normal()
drift = (MU_EQ - 0.5 * vs - lam * mJ) * dt
lnS[t + 1] = lnS[t] + drift + np.sqrt(vs) * np.sqrt(dt) * Z1[t] + j
return np.exp(lnS), vpython跑 5040 天,VIX 隐含中枢 ≈20、样本均值 18.6、峰值 45.1,全样本 11 次跳空(占比 0.22%)——和真实市场「低波动是常态、崩盘是尖刺」的画像一致。
二、VIX 是怎么被「定价」的:30 日远期方差#
VIX 的数学定义是 30 天方差互换远期率的平方根。关键好处:在 Heston 模型下,远期方差对当前方差 v_t 是仿射的,有闭式解,不用蒙特卡洛就能算即期公允 VIX。
远期方差 E^Q[∫_t^{t+T} v_s ds | v_t] = θT + (v_t−θ)(1−e^{−κT})/κ,令 T=30/252,则:
T30 = 30.0 / 252.0
def fair_vix(v):
vs = (theta * T30 + (v - theta) * (1.0 - np.exp(-kappa * T30)) / kappa) / T30
return 100.0 * np.sqrt(np.maximum(vs, 1e-6))python这一步的意义:VIX 不是外生给定的数字,而是模型内部状态 v_t 的确定性函数。波动状态高了,VIX 自动高;状态低了,VIX 自动低。这就把「恐慌」从新闻还原成了可计算的模型变量。
三、VIX 期货定价:E^Q[VIX_T] 与期限结构#
VIX 期货在 T 时刻交割的,是那时的 VIX 水平。风险中性价格就是 F_t(τ) = E^Q[VIX_{t+τ}]。因为每个未来时点的 VIX 仍仿射依赖于那时的方差 v_{t+τ},我们只需在 Q 测度下模拟 CIR 方差、取终态 VIX 平均即可:
def mc_expected_vix(v0_scalar, tau_days, n_paths=1500, seed=7):
n_steps = max(1, int(round(tau_days)))
dts = tau_days / n_steps / 252.0
rng = np.random.default_rng(seed)
vv = np.full(n_paths, v0_scalar)
Z = rng.standard_normal((n_steps, n_paths))
for s in range(n_steps):
vs = np.maximum(vv, 0.0)
vv = np.maximum(vv + kappa * (theta - vs) * dts + sigma_v * np.sqrt(vs) * np.sqrt(dts) * Z[s], 1e-6)
return float(np.mean(fair_vix(vv)))python把不同到期 τ 的期货价连起来,就是 VIX 期限结构。模拟里两个典型快照:
- 平静日(VIX=11.8):期货 1M→6M = 14.2→19.1,整体向上——近端低、远端高,典型的 Contango(升水);
- 恐慌日(VIX=45.1):期货 1M→6M = 41.1→27.8,整体向下——近端极高、远端回落,典型的 Backwardation(贴水)。

同一标的、不同时点,曲线整体翻转——这正是 VIX 期货最核心的可交易结构。
四、VRP:恐慌被明码标价#
真实市场里,VIX 期货长期高于随后的实现波动,这块价差叫 波动率风险溢价(VRP)——投资者愿意为「崩溃保险」付费,卖波动的人赚这份溢价。我们在期货里显式植入一个随期限衰减的 VRP 楔差(近端最肥),让期货 F_t(τ) = E^Q[VIX_{t+τ}] + 楔差(τ)。结果是:
- 期货相对即期的溢价均值 +0.74 vol 点;
- 67.1% 的交易日期货高于即期(即处于 Contango,做空波动能赚溢价)。

这证明了一件事:「恐慌」不是一个模糊的情绪词,而是一个长期为正的、可被定价的价差。既然能被定价,它就是一个因子。
五、把恐慌做成因子:做空收割 vs 做多保险#
「恐慌因子」怎么交易?最干净的做法是做空 VIX 期货——你卖出市场为崩溃恐惧付的保费,赚 VRP。月度收益定义为:
H = 21
r_short = [] # 做空 VIX 期货(收割 VRP) 月度收益
for d in range(0, NDAY - H, H):
entry = mc_expected_vix(v[d], H) + vrp_wedge(H) # 月初建仓的 1 月期货价
settle = fair_vix(v[d + H]) # 月末到期 = 即月 VIX
r_short.append((entry - settle) / entry)python按 15% 年化波动缩放后(原始头寸波动极高,缩放不改变 Sharpe),这个因子:
- 年化 7.2%、波动 15%、Sharpe 0.48、最大回撤 −37.3%;
- 与股票相关 +0.54——它是顺周期的:股市崩、VIX 暴涨、做空者亏,所以它和你「同跌」。
- 最惨月 −14.2%(当月股票 −6.8%),最好月 +9.5%。
Sharpe 0.48 高于本例股票本身的 0.12——「恐慌」作为一个因子,质量不输任何传统风格因子。但注意它的镜像:**做多 VIX 期货(恐慌对冲因子)**则是危机保险——年化 −6.8%、Sharpe −0.45、与股票相关 −0.54,崩盘月飙升、平静月慢慢 bleed。
把这两个「恐慌因子」接到组合里,对照清晰:
| 组合 (90% 股票 + 10% 恐慌因子) | 年化 | 波动 | Sharpe | 最大回撤 |
|---|---|---|---|---|
| 纯股票 | 2.8% | 22.2% | 0.12 | −72.1% |
| +10% 做空 VIX(赚溢价) | 3.2% | 20.9% | 0.15 | −68.7% |
| +10% 做多 VIX(买保险) | 1.8% | 19.2% | 0.09 | −66.2% |

两类用法都对,只是角色不同:做空 VRP 是「赚取恐慌溢价」的因子,做多 VIX 是「为组合买崩溃保险」的因子。前者提升风险调整收益,后者牺牲一点收益换回撤保护。
六、真实陷阱(诚实版)#
1. 数据是合成的:全文数字来自 SVJ 模拟,用于演示机制,不是某只真实 ETF 的回测。落地时要用真实 VIX 期货收盘价、真实指数收益替换 simulate_svj。
2. VRP 是显式植入的:真实 VIX 期货长期 Contango 是经验事实,但模型本身不含风险溢价,我们用楔差把它「钉」进去。若想从纯扩散推导出正 VRP,需要引入风险厌恶(市场价格为波动风险为负),那会改动 Q 测度的漂移项。
3. 波动缩放不是魔法:原始做空 VIX 头寸月度波动可达 75%,±70% 的月份并不罕见。本文按 15% 目标波动缩放,Sharpe 不变但数字才可读。实盘必须做波动率目标化管理,否则寸头会失控。
4. 跳跃尾部是真实风险:做空 VRP 最惨月 −14%(缩放后),这是因为一次崩盘跳让 VIX 翻倍。模型里只有 11 次跳,真实市场 2008/2020 级别的跳会更狠——做空波动的尾部比你想象的更肥。
5. 期限结构会动:本文用固定楔差。真实 VRP 随 regime 变化,2008 与 2020 的贴水深度天差地别;静态做空会在危机里被反复打脸,必须配合 regime 判断或严格止损。
6. 相关结构是时变的:顺周期(+0.54)是样本均值,危机最剧烈的几周相关会冲到接近 +1,那时「赚溢价」和「持股票」会同时流血——这也是为什么纯做空 VRP 不能单独扛组合。
结语#
「把恐慌也做成因子」不是修辞。从一个 SVJ 模型出发,VIX 是内部状态的函数、VIX 期货有可计算的期限结构、VRP 是长期为正的明码标价——恐慌由此变成了一个收益可算、风险可度量、与股票相关结构清晰的因子。你可以像持有价值、动量那样,把它写进因子清单:想要溢价就做空 VIX,想要保险就做多 VIX。真正难的从来不是「有没有这个因子」,而是承认它、给它一个明确的风险预算、并为它的崩溃尾部留好退路。
本文图表与全部数字均由
generate_svj_vix_futures_images.py用 numpy 从零计算生成(SVJ 模拟 + VIX 仿射定价 + VIX 期货蒙特卡洛),非占位图。把simulate_svj换成真实数据,整套代码可直接复用。