VIX 期限结构 Carry:在 contango 里滚动收割升水
VIX 期货绝大多数时间处于 contango——近月比现货贵、次月又比近月贵,这条向上倾斜的曲线不是预期,是恐慌保险的卖方在收保费。做空近月、随时间下滑到现货、每月滚动,就是把这份升水一口口吃进来。8 年合成数据完整复现:期限结构 67% 时间处于 contango、正斜率日均 roll 高达 9%;裸空 carry 收益分布左偏 -4.03、单一危机日就能吞掉大半年利润,长期净值 CAGR -12.5%、最大回撤 -78%;加一层『倒挂即离场』的 regime 过滤,同一条腿变成年化 14.7%、Sharpe 0.64、回撤收窄到 -30%。对照做多 VIX 的绞肉机(CAGR -31%、回撤 -97%)说明升水结构的方向由谁买单。附期限结构快照、斜率 regime 时序、净值对比、收益分布四图与完整 Python,以及四类真实陷阱(中高阶)。
有一类策略,它的钱来得极其”无聊”:不预测方向、不抓拐点、不需要判断市场涨跌,只要世界不塌,它每天都在稳稳地捡钢镚儿。它的名字叫 VIX 期限结构 carry——在波动率期货的 contango(升水)里,做空近月合约,让它随时间滑向现货,每月滚动,把这份”升水”一口口吃进来。
结论先放这:VIX 期货绝大多数时间处于 contango——近月比现货贵、次月又比近月贵。这条向上倾斜的曲线不是市场对未来波动率的理性预期,而是恐慌保险的卖方在收保费。在 8 年合成数据上,期限结构 67% 的时间处于 contango,正斜率日均 roll 高达 9%(年化口径)。但直接裸空的收益分布严重左偏(偏度 -4.03),单一危机日就能吞掉大半年利润,长期净值 CAGR -12.5%、最大回撤 -78%——升水是真的,可你不一定拿得住。加一层『期限结构倒挂即离场』的 regime 过滤后,同一条腿变成年化 14.7%、Sharpe 0.64、回撤收窄到 -30%。 这就是波动率 carry 的全部真相:钱是真的,风险也是真的,赚它的关键不在信号而在活着。
一、升水从哪来:谁在为恐慌保险买单#
先说清楚什么是 VIX 期限结构。VIX 现货是标普 500 未来 30 天的隐含波动率指数,它本身不可交易;能交易的是 VIX 期货,有近月(F1)、次月(F2)等一串到期日。把现货、F1、F2 的价格画在一起,你会看到一条曲线。
大多数平静时期,这条曲线向上倾斜:F2 > F1 > 现货。这叫 contango(期货升水)。少数危机时刻,曲线向下倒挂:现货 > F1 > F2,这叫 backwardation(现货升水)。

为什么平静期总是 contango?不是因为市场”预期”未来波动率会上升,而是因为波动率保险的供需极度不对称:
- 买方一大群,且价格不敏感:机构投资者需要对冲尾部风险,买 VIX 期货、买标普看跌期权就是在买保险。他们要的是”崩盘时账户不归零”,为此愿意常年支付一笔溢价,就像你明知大概率用不上却还是年年续车险。
- 卖方少,且要求补偿:愿意卖这份保险的人,承担的是”平时赚小钱、崩盘时巨亏”的凸性风险。没有一笔额外的风险溢价,没人愿意站在这一侧。
这笔溢价,就沉淀在期限结构的正斜率里。近月合约越接近到期,它的价格就越往现货收敛——如果现货一直没动,这段”从 F1 跌到现货”的下滑(roll-down)就是空头的利润。这不是预测对了方向,是结构性地收取了别人的保费。
二、Carry 的机制:做空、下滑、滚动#
把机制拆成三步:
- 做空近月期货 F1:因为 F1 高于现货,只要现货不暴涨,F1 就会随到期日临近向现货收敛,空头获利。
- 收割 roll-down:这段收敛就是”roll yield”(滚动收益),它是 carry 的核心来源。斜率越陡,单位时间能吃的升水越多。
- 每月滚动:近月快到期时,平掉旧合约、开空新的近月,保持”永远做空最陡那一段”的敞口。
我用一个 log-OU(对数均值回归)过程模拟现货 VIX,长期中枢 17,叠加偶发的危机跳升;期货价格按”风险中性均值回归 + 持续为正的风险溢价”定价。下图是 8 年里期限结构斜率 (F2−F1)/F1 的时序:

67.4% 的交易日处于 contango,正斜率日均折算约 9%(年化口径)——这是一片肥沃的收割地。但请注意那几根深深扎下去的红色尖刺:每一次都是市场恐慌、期限结构瞬间倒挂的时刻。而这正是 carry 策略的生死线。
三、裸空的残酷真相:升水是真的,你拿不住#
先看最朴素的做法:不管三七二十一,永远做空近月。下面用 numpy 复现这条裸空 carry 曲线,同时对照”做多 VIX”(买近月)这个反向操作。
import numpy as np
np.random.seed(20260725)
N = 252 * 8
kappa, theta, sigma = 4.0, np.log(17.0), 0.9
# 现货 VIX:log-OU + 偶发危机跳升
logv = np.zeros(N); logv[0] = theta
for t in range(1, N):
logv[t] = logv[t-1] + kappa*(theta-logv[t-1])/252 + sigma*np.sqrt(1/252)*np.random.randn()
if np.random.rand() < 0.004: # ~1% 概率的恐慌跳升
logv[t] += np.random.uniform(0.4, 1.1)
spot = np.exp(logv)
# 近月/次月期货:向中枢均值回归 + 持续正溢价
def fut(sv, tau, prem):
m = np.exp(np.log(sv)*np.exp(-kappa*tau) + theta*(1-np.exp(-kappa*tau)))
return m * (1 + prem*tau)
tau1, tau2, premium = 20/252, 50/252, 0.55
f1, f2 = fut(spot, tau1, premium), fut(spot, tau2, premium)
roll = (f2 - f1) / f1 # 期限结构斜率
dfront = np.diff(np.log(f1), prepend=np.log(f1[0]))
strat_ret = (-dfront) * 0.5 - 0.0002 # 做空近月:价格跌则赚
eq_short = np.cumprod(1 + strat_ret)
eq_longvix = np.cumprod(1 + dfront - 0.0002) # 做多近月:反向绞肉机python关键在最后两行的对比。做空近月 strat_ret = -dfront,平静期每天吃一点点升水;做多近月 dfront 正好相反,平静期天天被升水侵蚀。谁在为谁买单,一目了然。
再看收益分布——这是理解波动率 carry 最重要的一张图:

均值为正,但偏度 -4.03。这就是所谓的”捡钢镚儿模式”(picking up nickels in front of a steamroller):绝大多数日子稳稳赚几个基点,但左尾拖着一条极长的崩塌尾巴。一次危机,现货 VIX 暴涨、期限结构从 contango 翻成 backwardation,你做空的近月不是慢慢下滑而是垂直飙升——单日就能亏掉大半年积累。
裸空的长期结局:CAGR -12.5%、最大回撤 -78.1%。升水是真的,可这条腿最终是亏钱的——不是因为升水不存在,而是因为你在崩盘时被抬着出场。
四、活下来的关键:一层 regime 过滤#
改进极其简单:当期限结构倒挂(roll ≤ 0)时,立即离场空仓。
倒挂意味着市场已经进入恐慌,此时 carry 的逻辑失效——你不再是收保费的一方,而是站在飓风口。历史上波动率的大崩塌,几乎都伴随着 contango→backwardation 的翻转,而这个翻转往往先于最猛烈的那一波拉升。所以用斜率符号当开关,能在大多数灾难前把仓位拉闸。
in_contango = (roll > 0).astype(float) # 仅在升水时持仓
strat_ret_filtered = strat_ret * in_contango
eq_filt = np.cumprod(1 + strat_ret_filtered)python就这一行 roll > 0,效果判若两人:

| 策略 | CAGR | Sharpe | 最大回撤 |
|---|---|---|---|
| 裸空近月 carry | -12.5% | -0.10 | -78.1% |
| Contango 过滤后 | +14.7% | 0.64 | -29.6% |
| 做多 VIX(对照) | -31.2% | — | -97.2% |
同一条 carry 腿,只是加了”倒挂即离场”这一层开关,就从长期亏钱变成年化 14.7%、回撤腰斩。而做多 VIX 的那条红线——CAGR -31%、回撤 -97% 几近归零——反过来印证了升水结构的方向:长期做多波动率是给 carry 卖方源源不断送钱的绞肉机。
注意,即便过滤后 Sharpe 也只有 0.64,回撤仍有 -30%。这不是策略没做好,而是波动率 carry 的天性——它永远藏着一条无法完全对冲的左尾。开关能砍掉大部分灾难,但砍不干净跳空的那一下。
五、结果解读#
收益来自哪里。 carry 的利润几乎全部来自 contango 时段的 roll-down,且是”高频小额”累积型的:每天吃一点点升水,靠 67% 时间的重复滚出来。这决定了它对交易成本极度敏感——滚动换月的点差、做空成本每多一点,都会直接从这条薄利里扣。
风险集中在哪里。 全部下行风险压缩在少数几个危机日。裸空版本 -78% 的回撤不是慢慢磨出来的,是几根红色尖刺一次性砸出来的。这也是为什么 Sharpe 这个指标会严重误导你——它用对称的标准差衡量一个极度左偏的分布,平静期 carry 的低波动会把 Sharpe 撑得很好看,直到崩盘那天数字瞬间崩塌。评估波动率 carry,回撤和偏度比 Sharpe 诚实得多。
过滤器的代价。 regime 开关不是免费午餐:它会在市场刚倒挂时离场(可能卖在恐慌初期),也会在市场刚恢复 contango 时才重新进场(可能错过反弹初段最陡的 roll)。它换来的是”活下来”,代价是”钝一点”。这是一个用收益换生存的交易,对 carry 这类左尾策略而言绝对划算。
适用边界。 这类策略在长期平静、温和 contango 的市场里是慢牛;在波动率频繁抽风、contango/backwardation 快速切换的环境里,开关会被反复打脸(频繁进出、两头挨耳光)。它最怕的不是一次大崩盘(开关能躲),而是”没有预警的隔夜跳空”——期限结构在你离场之前就已经翻转并暴涨。
A. 实现细节#
- 信号字段:期限结构斜率
(F2−F1)/F1用当日收盘的近月、次月期货价,判定当日是否处于 contango。 - 执行时点:本文为简化用当日斜率决定当日持仓(signal 与 execute 同日)。生产环境必须改为”昨日收盘斜率决定今日持仓”,否则引入 look-ahead。
- carry 收益口径:做空近月的日收益近似为
-Δlog(F1),即近月价格下跌则空头获利;乘 0.5 是敞口缩放,扣 2bp 近似滚动与做空成本。 - regime 开关:
roll > 0时持仓、roll ≤ 0时空仓,用乘子实现,等价于”倒挂日收益归零”。 - 净值口径:单腿复利净值,未叠加保证金利息与融券收益。
B. 已知偏差#
- 跳空无法躲:日线粒度下,开关只能在”倒挂已经发生”的收盘后反应。真实崩盘常在隔夜或盘中瞬间完成 contango→backwardation 的翻转,此时你的仓位已经挨了第一棍——回测的 -30% 回撤在真实滑点与跳空下可能更深。
- 合成数据的温柔:本文现货用 log-OU + 泊松跳升生成,跳升幅度有上限(0.4~1.1 的对数增量)。真实市场的波动率跳升(如 2018 年 2 月 Volmageddon、2020 年 3 月)尾部更肥、更突然,会把裸空版本的回撤推向更极端。
- 换月成本未精细建模:VIX 期货每月滚动的买卖价差、流动性在危机时急剧恶化,本文用固定 2bp 近似,低估了恰恰在你最想离场时的成交成本。
- 单一近月假设:真实 carry 常用 F1/F2 加权或 VXX/SVXY 这类 ETN 来实现,其内部滚动规则、跟踪误差、管理费会显著改变净值,本文未建模。
C. 一句话总结#
VIX 期限结构 carry 的本质是当波动率保险的卖方,收结构性升水——钱是真的,8 年里 67% 的时间都在向上倾斜等你收割。但它的收益分布左偏到 -4.03,裸空长期亏钱、回撤 -78%,赚它的关键从来不是找信号,而是在倒挂时活下来。一层 roll > 0 的 regime 开关就能把这条腿从绞肉机变成年化 14.7% 的慢牛。记住波动率 carry 的铁律:你赚的是别人的恐慌保费,代价是自己扛着那条永远砍不干净的左尾。