Overall Statistics
Total Trades
115
Average Win
0%
Average Loss
0%
Compounding Annual Return
0.885%
Drawdown
5.400%
Expectancy
0
Net Profit
11.403%
Sharpe Ratio
0.458
Probabilistic Sharpe Ratio
1.636%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0.001
Beta
0.06
Annual Standard Deviation
0.014
Annual Variance
0
Information Ratio
-0.635
Tracking Error
0.138
Treynor Ratio
0.103
Total Fees
$284.05
Estimated Strategy Capacity
$0
Lowest Capacity Asset
VX V1NCGGOCJHT5
Portfolio Turnover
0.04%
#region imports
from AlgorithmImports import *
#endregion
from collections import deque
import statsmodels.api as sm

class TermStructureOfVixAlgorithm(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2011, 1, 1)   # Set Start Date
        #self.SetEndDate(2017, 1, 1)     # Set End Date
        self.SetCash(1000000)          # Set Strategy Cash

        vix = self.AddIndex("VIX", Resolution.Daily)
        self.vix = vix.Symbol 
        self.vix_multiplier = vix.SymbolProperties.ContractMultiplier
        self.vx1 = self.AddFuture(Futures.Indices.VIX,
                resolution = Resolution.Daily,
                extendedMarketHours = True,
                dataNormalizationMode = DataNormalizationMode.BackwardsRatio,
                dataMappingMode = DataMappingMode.OpenInterest,
                contractDepthOffset = 0
            )
        self.vx1_multiplier = self.vx1.SymbolProperties.ContractMultiplier
        self.es1 = self.AddFuture(Futures.Indices.SP500EMini,
                resolution = Resolution.Daily,
                extendedMarketHours = True,
                dataNormalizationMode = DataNormalizationMode.BackwardsRatio,
                dataMappingMode = DataMappingMode.OpenInterest,
                contractDepthOffset = 0
            )
        self.es1_multiplier = self.es1.SymbolProperties.ContractMultiplier
        
        # the rolling window to save the front month VX future price
        self.price_VX = RollingWindow[float](252)
        # the rolling window to save the front month ES future price
        self.price_ES = RollingWindow[float](252)
        # the rolling window to save the time-to-maturity of the contract
        self.days_to_maturity = RollingWindow[float](252)
        
        stockPlot = Chart("Trade")
        stockPlot.AddSeries(Series("VIX", SeriesType.Line, 0))
        stockPlot.AddSeries(Series("VIX Futures", SeriesType.Line, 0))
        stockPlot.AddSeries(Series("Buy", SeriesType.Scatter, 0))
        stockPlot.AddSeries(Series("Sell", SeriesType.Scatter, 0))
        stockPlot.AddSeries(Series("Daily Roll", SeriesType.Scatter, 0))
        stockPlot.AddSeries(Series("Hedge Ratio", SeriesType.Scatter, 0))

        self.SetWarmUp(253, Resolution.Daily)

    def OnData(self, data):
        if data.Bars.ContainsKey(self.vx1.Symbol) and data.Bars.ContainsKey(self.es1.Symbol):
            # update the rolling window price and time-to-maturity series every day
            vx1_price = data.Bars[self.vx1.Symbol].Close
            self.price_VX.Add(float(data.Bars[self.vx1.Symbol].Close))
            self.price_ES.Add(float(data.Bars[self.es1.Symbol].Close))
            self.days_to_maturity.Add((self.vx1.Mapped.ID.Date-self.Time).days)
            
        if self.IsWarmingUp or not self.price_VX.IsReady or not self.price_ES.IsReady or not self.days_to_maturity.IsReady\
            or (self.vx1.Mapped.ID.Date - self.Time).days == 0:
            return

        if data.Bars.ContainsKey(self.vx1.Symbol) and data.Bars.ContainsKey(self.es1.Symbol):
            # calculate the daily roll
            daily_roll = (vx1_price*self.vx1_multiplier - self.Securities[self.vix].Price*self.vix_multiplier)\
                /(self.vx1.Mapped.ID.Date - self.Time).days
            self.Plot("Trade", "VIX", vx1_price)
            self.Plot("Trade", "VIX Futures", vx1_price)
            self.Plot("Trade", "Daily Roll", daily_roll)
            
            if not self.Portfolio[self.vx1.Mapped].Invested:
                # Short if the contract is in contango with adaily roll greater than 0.10 
                if daily_roll > 0.1:
                    hedge_ratio = self.CalculateHedgeRatio(data.Bars[self.es1.Symbol].Close)
                    self.Plot("Trade", "Sell", vx1_price)
                    qty = self.CalculateOrderQuantity(self.vx1.Mapped, -1)
                    self.MarketOrder(self.vx1.Mapped, qty // self.vx1.SymbolProperties.ContractMultiplier)
                    qty = self.CalculateOrderQuantity(self.es1.Mapped, -1*hedge_ratio)
                    self.MarketOrder(self.es1.Mapped, qty // self.es1.SymbolProperties.ContractMultiplier)
                # Long if the contract is in backwardation with adaily roll less than -0.10
                elif daily_roll < -0.1:
                    hedge_ratio = self.CalculateHedgeRatio(data.Bars[self.es1.Symbol].Close)
                    self.Plot("Trade", "Buy", vx1_price)
                    qty = self.CalculateOrderQuantity(self.vx1.Mapped, 1)
                    self.MarketOrder(self.vx1.Mapped, qty // self.vx1.SymbolProperties.ContractMultiplier)
                    qty = self.CalculateOrderQuantity(self.es1.Mapped, 1*hedge_ratio)
                    self.MarketOrder(self.es1.Mapped, qty // self.es1.SymbolProperties.ContractMultiplier)
            
            # exit if the daily roll being less than 0.05 if holding short positions                 
            if self.Portfolio[self.vx1.Mapped].IsShort and daily_roll < 0.05:
                self.Liquidate()
                return
            
            # exit if the daily roll being greater than -0.05 if holding long positions                    
            if self.Portfolio[self.vx1.Mapped].IsLong and daily_roll > -0.05:
                self.Liquidate()
                return
                
        if self.vx1.Mapped and self.es1.Mapped:
            # if these exit conditions are not triggered, trades are exited two days before it expires
            if self.Portfolio[self.vx1.Mapped].Invested and self.Portfolio[self.es1.Mapped].Invested: 
                if (self.vx1.Mapped.ID.Date-self.Time).days <= 2 or (self.es1.Mapped.ID.Date-self.Time).days <= 2:
                    self.Liquidate()
                    
    def CalculateHedgeRatio(self, es1_price):
        price_VX = np.array(list(self.price_VX))[::-1]/self.vx1_multiplier
        price_ES = np.array(list(self.price_ES))[::-1]/self.es1_multiplier
        delta_VX = np.diff(price_VX)
        res_ES = np.diff(price_ES)/price_ES[:-1]*100
        tts = np.array(list(self.days_to_maturity))[::-1][1:]
        df = pd.DataFrame({"delta_VX":delta_VX, "SPRET":res_ES, "product":res_ES*tts}).dropna()
        # remove rows with zero value
        df = df[(df != 0).all(1)]
        y = df['delta_VX'].astype(float)
        X = df[['SPRET', "product"]].astype(float)
        X = sm.add_constant(X)
        model = sm.OLS(y, X).fit()
        beta_1 = model.params[1]
        beta_2 = model.params[2]
        
        hedge_ratio = (beta_1 + beta_2*((self.vx1.Mapped.ID.Date-self.Time).days))/float(es1_price)
        
        self.Plot("Trade", "Hedge Ratio", hedge_ratio)
        
        return hedge_ratio