
“该策略将60%的股票和40%的债券投资组合与动态分配的VIX看涨期权相结合。它根据VIX指数调整VIX期权权重,并每月进行重新平衡。”
资产类别: ETF、期权 | 地区: 美国 | 周期: 每月 | 市场: 债券、股票 | 关键词: 对冲、VIX期权
I. 策略概要
该策略采用60%的股票和40%的债券投资组合,股票使用SPDR标准普尔500指数ETF(SPY),债券使用iShares 7-10年期美国国债ETF(IEF)。投资组合分配0-100个基点(bps)给VIX看涨期权,投资于VIX期货的1个月、2个月、3个月和4个月期权。VIX看涨期权的权重根据VIX指数水平调整:若VIX在15至30之间,则为1%;若在30至50之间,则为0.5%;否则为0%。期权在到期前展期,若具有内在价值则卖出。股票/债券部分每月重新平衡,期权销售所得现金再投资于该投资组合。该策略利用VIX期权对冲波动性,重点是基于市场状况的系统性动态分配。
II. 策略合理性
该策略通过每月将投资组合的固定百分比分配给VIX看涨期权,利用波动率的均值回复特性。当VIX低于均值时,购买更多期权;当VIX高于均值时,购买较少期权。该策略旨在确定期权的最佳实值性、到期时间和最小资本要求。仅当VIX在15%至50%之间时才购买看涨期权,确保投资者避免为可能不必要的期权支付过高的费用,从而调整投资组合的风险和回报状况。
III. 来源论文
A Study in Portfolio Diversification Using VIX Options [点击查看论文]
- 多米尼克·保罗尼,IPS 战略资本
<摘要>
对可靠、低成本的投资组合尾部保护或对冲(以应对1987年崩盘、2000年互联网泡沫、2008年信贷危机和2011年欧洲危机等外生事件)的寻找仍在继续。本研究评估了一种系统性的VIX看涨期权购买策略的绩效,该策略具有明确的成本,用于对冲股票投资组合的系统性风险。投资组合经理必须权衡这些成本与可能具有无法定义成本的对冲策略(例如,保护性看跌期权或卖空股票指数期货)的成本。分析表明,被动分配VIX看涨期权在大规模回撤期间已被证明是有效的,并且可以在不需要对冲时花费相对较小的已定义资本百分比来实现。该研究将一组固定规则应用于实证数据,目的是在研究期间事后优化VIX看涨期权的实值性和到期日。在分析期间,系统性购买适当位置的VIX看涨期权往往能在尾部风险事件中提供足够的保护,并在不需要对冲时以最小的成本实现。


IV. 回测表现
| 年化回报 | 8.63% |
| 波动率 | 8.44% |
| β值 | 0.636 |
| 夏普比率 | 1.02 |
| 索提诺比率 | 0.504 |
| 最大回撤 | -10.47% |
| 胜率 | 35% |
V. 完整的 Python 代码
from AlgorithmImports import *
class PortfolioHedgingUsingVIXOptions(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetCash(1000000)
data = self.AddEquity("SPY", Resolution.Minute)
data.SetLeverage(5)
self.spy = data.Symbol
data = self.AddEquity("IEF", Resolution.Minute)
data.SetLeverage(5)
self.ief = data.Symbol
data = self.AddEquity("VIXY", Resolution.Minute)
data.SetLeverage(5)
self.vix = data.Symbol
option = self.AddOption('VIXY', Resolution.Minute)
option.SetFilter(-20, 20, 25, 35)
def OnData(self,slice):
for i in slice.OptionChains:
chains = i.Value
# Max 2 positions - spy and ief are opened. That means option expired.
invested = [x.Key for x in self.Portfolio if x.Value.Invested]
if len(invested) <= 2:
calls = list(filter(lambda x: x.Right == OptionRight.Call, chains))
if not calls: return
underlying_price = self.Securities[self.vix].Price
expiries = [i.Expiry for i in calls]
# Determine expiration date nearly one month.
expiry = min(expiries, key=lambda x: abs((x.date() - self.Time.date()).days - 30))
strikes = [i.Strike for i in calls]
# Determine out-of-the-money strike.
otm_strike = min(strikes, key = lambda x:abs(x - (float(1.35) * underlying_price)))
otm_call = [i for i in calls if i.Expiry == expiry and i.Strike == otm_strike]
if otm_call:
# Option weighting.
weight = 0.0
if underlying_price >= 15 and underlying_price <= 30:
weight = 0.01
elif underlying_price > 30 and underlying_price <= 50:
weight = 0.005
if weight != 0:
options_q = int((self.Portfolio.MarginRemaining * weight) / (underlying_price * 100))
# Set max leverage.
self.Securities[otm_call[0].Symbol].MarginModel = BuyingPowerModel(5)
# Sell out-the-money call.
self.Buy(otm_call[0].Symbol, options_q)
self.SetHoldings(self.spy, 0.6)
self.SetHoldings(self.ief, 0.4)