The strategy combines a 60% stock and 40% bond portfolio with dynamic allocation to VIX call options. It adjusts VIX option weights based on the VIX Index, with monthly rebalancing.

I. STRATEGY IN A NUTSHELL

The strategy combines a 60% stock and 40% bond portfolio using SPY for equities and IEF for bonds, with a small allocation to VIX call options (0–100 bps) across one- to four-month maturities. The allocation is adjusted dynamically based on VIX levels, rolled before expiration, and reinvested into the stock/bond portfolio, providing a systematic hedge against market volatility.

II. ECONOMIC RATIONALE

The strategy exploits the mean-reverting behavior of volatility, buying more VIX calls when volatility is low and fewer when it is high. By carefully selecting moneyness, expiry, and timing, the investor avoids overpaying for protection, maintaining an optimal risk-return balance while hedging against market turbulence.

III. SOURCE PAPER

A Study in Portfolio DIversification Using VIX Options [Click to Open PDF]

Dominick Paoloni, IPS Strategic Capital

<Abstract>

The search for dependable, low-cost portfolio tail protection or hedge from exogenous events such as the 1987 crash, the 2000 dot-com bubble, the 2008 credit crisis, and the 2011 European crisis continues. This study assesses the performance of a systematic VIX call buying strategy with a defined cost to hedge an equity portfolio from systemic risk. A portfolio manager must weigh these costs against those of hedging strategies that have potentially undefinable costs, for example, protective puts or shorting equity index futures. The analysis shows that a passive allocation to VIX calls has proven effective in large drawdown periods and can be accomplished by spending a relatively small defined percentage of capital when the hedge is not needed. The study applies a set of fixed rules to empirical data with the goal of optimizing ex-post the moneyness and expiry of VIX call options over the period studied. For the period of analysis, the systematic purchase of properly placed VIX calls tends to provide sufficient protection in tail risk events for minimal cost when hedging is not needed.

IV. BACKTEST PERFORMANCE

Annualised Return8.63%
Volatility8.44%
Beta0.636
Sharpe Ratio1.02
Sortino Ratio0.504
Maximum Drawdown-10.47%
Win Rate35%

V. FULL PYTHON CODE

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)

VI. Backtest Performance

Leave a Reply

Discover more from Quant Buffet

Subscribe now to keep reading and get access to the full archive.

Continue reading