The strategy trades SPY, SH, VXX, and XIV based on VIX futures contango/backwardation, adjusting positions with hedges. Positions are closed when the relative basis crosses predefined thresholds, with varying hedge levels.

I. STRATEGY IN A NUTSHELL

The strategy trades SPY and SH for S&P 500 exposure and VXX and XIV for short-term VIX futures exposure. It calculates the relative difference between front-month VIX futures and spot VIX to identify contango or backwardation:

Contango (futures > spot): Buy XIV and hedge with SH.

Backwardation (futures < spot): Buy VXX and hedge with SPY.

Positions are closed when the relative basis crosses predetermined sell thresholds. The strategy performs best with a 0% hedge ratio, but alternative hedge levels can be applied to adjust risk-return profiles.

II. ECONOMIC RATIONALE

VIX futures consistently trade at a premium to spot VIX because VIX is non-tradable and investors pay for volatility protection. Research (Simon & Campasano, 2014) shows that VIX futures prices revert toward the spot index: futures above VIX tend to fall, while those below tend to rise. This mispricing, driven by risk aversion and volatility hedging demand, creates persistent opportunities for systematic trading.

III. SOURCE PAPER

VIX Exchange Traded Products: Price Discovery, Hedging and Trading Strategy [Click to Open PDF]

Bordonado, Molnar, Samdal, Norwegian University of Science and Technology (NTNU), University of Stavanger, Norwegian University of Science and Technology (NTNU)

<Abstract>

This paper investigates the most traded VIX exchange traded products (ETPs) with focus on their performance, price discovery, hedging ability and trading strategy. The VIX ETPs track their benchmark indices well. They are therefore exposed to the same time-decay (high negative expected returns) as these indices. This makes them unsuitable for buy-and-hold investments, but it gives rise to a highly profitable trading strategy. Despite being negatively correlated with the S&P 500, the ETPs perform poorly as a hedging tool; their inclusion in a portfolio based on S&P 500 will decrease the risk-adjusted performance of the portfolio.

IV. BACKTEST PERFORMANCE

Annualised Return 69%
Volatility 39%
Beta-0.243
Sharpe Ratio 2.11
Sortino Ratio0.239
Maximum Drawdown-24.5%
Win Rate53%

V. FULL PYTHON CODE

from QuantConnect.Python import PythonQuandl
from AlgorithmImports import *
class TradingVIXETFsv2(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2010, 1, 1)
        self.SetCash(100000)
        self.vixy = self.AddEquity('VIXY', Resolution.Minute).Symbol
        
        # Vix futures data.
        self.vix_future = self.AddFuture(Futures.Indices.VIX, Resolution.Minute)
        # Vix spot.
        self.vix_spot = self.AddData(CBOE, 'VIX', Resolution.Daily).Symbol
        
        self.vix_future.SetFilter(timedelta(0), timedelta(30))
        
        # Vix futures active contract updated on expiration.
        self.active_contract = None
        
        self.Schedule.On(self.DateRules.EveryDay(self.vixy), self.TimeRules.AfterMarketOpen(self.vixy, 1), self.Rebalance)
    def Rebalance(self):
        # split data error prevention
        if self.Time.year == 2021 and self.Time.month == 5:
            self.Liquidate()
            return
            
        if self.active_contract:
            if self.Securities.ContainsKey(self.vix_spot):
                spot_price = self.Securities[self.vix_spot].Price
                vix_future_price = self.active_contract.LastPrice
                if spot_price == 0 or vix_future_price == 0: 
                    return
                
                relative_basis = vix_future_price / spot_price
                
                # BU 8%, SU 6%, BL -8%, SL -6% thresholds.
                # Short volatility.
                if relative_basis > 1.08:
                    if not self.Portfolio[self.vixy].IsShort and self.Securities[self.vixy].Price != 0:
                        self.SetHoldings(self.vixy, -1)
                
                if relative_basis >= 1.06 and relative_basis <= 1.08 and self.Portfolio[self.vixy].IsLong:
                    self.Liquidate(self.vixy)
                
                if relative_basis < 1.06 and relative_basis > 0.94:
                    if self.Portfolio[self.vixy].Invested:
                        self.Liquidate(self.vixy)
                
                if relative_basis <= 0.94 and relative_basis >= 0.92 and self.Portfolio[self.vixy].IsShort:
                    self.Liquidate(self.vixy)
                
                # Long volatility.
                if not self.Portfolio[self.vixy].IsLong and relative_basis < 0.92:
                    if self.Securities[self.vixy].Price != 0:
                        self.SetHoldings(self.vixy, 1)
    def OnData(self, slice):
        chains = [x for x in slice.FutureChains]
        cl_chain = None
        if len(chains) > 0:
            cl_chain = chains[0]
        else:
            return
        
        if cl_chain.Value.Contracts.Count >= 1:
            contracts = [i for i in cl_chain.Value]
            contracts = sorted(contracts, key = lambda x: x.Expiry)
            near_contract = contracts[0]
            self.active_contract = near_contract

Leave a Reply

Discover more from Quant Buffet

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

Continue reading