Overall Statistics |
Total Trades
506
Average Win
10.58%
Average Loss
-7.81%
Compounding Annual Return
-20.284%
Drawdown
94.800%
Expectancy
0.062
Net Profit
-92.012%
Sharpe Ratio
-0.153
Probabilistic Sharpe Ratio
0.000%
Loss Rate
55%
Win Rate
45%
Profit-Loss Ratio
1.35
Alpha
-0.063
Beta
0.053
Annual Standard Deviation
0.383
Annual Variance
0.146
Information Ratio
-0.368
Tracking Error
0.405
Treynor Ratio
-1.111
Total Fees
$4625.00
Estimated Strategy Capacity
$9000000.00
Lowest Capacity Asset
ES XFH59UK0MYO1
|
# https://quantpedia.com/strategies/exploiting-term-structure-of-vix-futures/ # # The trading strategy is using VIX futures as a trading vehicle and S&P mini for hedging purposes. The investor sells (buys) the nearest # VIX futures with at least ten trading days to maturity when it is in contango (backwardation) with a daily roll greater than 0.10 # (less than -0.10) points and holds it for five trading days, hedged against changes in the level of spot VIX by (long) short positions # in E-mini S&P 500 futures. The daily roll is defined as the difference between the front VIX futures price and the VIX, divided by the # number of business days until the VIX futures contract settles, and measures potential profits assuming that the basis declines linearly # until settlement. The hedge ratios are constructed from regressions of VIX futures price changes on a constant and on contemporaneous # percentage changes of the front mini-S&P 500 futures contract both alone and multiplied by the number of days to the settlement of the # VIX futures contract (see equation 3 on page 12) import numpy as np import pandas as pd import statsmodels.api as sm from collections import deque class ExploitingTermStructureVIXFutures(QCAlgorithm): def Initialize(self): self.SetStartDate(2011, 1, 1) self.SetCash(100000) self.vix = self.AddData(QuandlVix, "CBOE/VIX", Resolution.Daily).Symbol # Add Quandl VIX price (daily) self.vx1 = self.AddData(QuandlFutures, "CHRIS/CBOE_VX1", Resolution.Daily).Symbol # Add Quandl VIX front month futures data (daily) self.es1 = self.AddData(QuandlFutures, "CHRIS/CME_ES1", Resolution.Daily).Symbol # Add Quandl E-mini S&P500 front month futures data (daily) vx_data = self.AddFuture(Futures.Indices.VIX) vx_data.SetFilter(timedelta(0), timedelta(days=180)) vx_data.MarginModel = BuyingPowerModel(5) # leverage es_data = self.AddFuture(Futures.Indices.SP500EMini) es_data.SetFilter(timedelta(0), timedelta(days=180)) es_data.MarginModel = BuyingPowerModel(5) # leverage self.front_VX = None self.front_ES = None # request the history to warm-up the price and time-to-maturity hist = self.History([self.vx1, self.es1], timedelta(days=450), Resolution.Daily) settle = hist['settle'].unstack(level=0) # the rolling window to save the front month VX future price self.price_VX = deque(maxlen=252) # the rolling window to save the front month ES future price self.price_ES = deque(maxlen=252) # the rolling window to save the time-to-maturity of the contract self.days_to_maturity = deque(maxlen=252) expiry_date = self.get_expiry_calendar() df = pd.concat([settle, expiry_date], axis=1, join='inner') for index, row in df.iterrows(): self.price_VX.append(row[str(self.vx1) + ' 2S']) self.price_ES.append(row[str(self.es1) + ' 2S']) self.days_to_maturity.append((row['expiry']-index).days) self.Schedule.On(self.DateRules.EveryDay(self.vix), self.TimeRules.AfterMarketOpen(self.vix), self.Rebalance) def OnData(self, data): # select the nearest VIX and E-mini S&P500 futures with at least 10 trading days to maturity # if the front contract expires, roll forward to the next nearest contract for chain in data.FutureChains: future_indices = chain.Key.Value[1:] # First letter in this variable is '/' if future_indices == Futures.Indices.VIX: if self.front_VX is None or ((self.front_VX.Expiry-self.Time).days <= 1): contracts = list(filter(lambda x: x.Expiry >= self.Time + timedelta(days = 10), chain.Value)) self.front_VX = sorted(contracts, key = lambda x: x.Expiry)[0] if future_indices == Futures.Indices.SP500EMini: if self.front_ES is None or ((self.front_ES.Expiry-self.Time).days <= 1): contracts = list(filter(lambda x: x.Expiry >= self.Time + timedelta(days = 10), chain.Value)) self.front_ES = sorted(contracts, key = lambda x: x.Expiry)[0] def Rebalance(self): if self.Securities.ContainsKey(self.vx1) and self.Securities.ContainsKey(self.es1): # update the rolling window price and time-to-maturity series every day if self.front_VX and self.front_ES: self.price_VX.append(float(self.Securities[self.vx1].Price)) self.price_ES.append(float(self.Securities[self.es1].Price)) self.days_to_maturity.append((self.front_VX.Expiry-self.Time).days) # calculate the daily roll daily_roll = (self.Securities[self.vx1].Price - self.Securities[self.vix].Price)/(self.front_VX.Expiry-self.Time).days if not self.Portfolio[self.front_VX.Symbol].Invested: # Short if the contract is in contango with adaily roll greater than 0.10 if daily_roll > 0.1: hedge_ratio = self.CalculateHedgeRatio() self.SetHoldings(self.front_VX.Symbol, -0.4) self.SetHoldings(self.front_ES.Symbol, -0.4*hedge_ratio) # Long if the contract is in backwardation with adaily roll less than -0.10 elif daily_roll < -0.1: hedge_ratio = self.CalculateHedgeRatio() self.SetHoldings(self.front_VX.Symbol, 0.4) self.SetHoldings(self.front_ES.Symbol, 0.4*hedge_ratio) # exit if the daily roll being less than 0.05 if holding short positions if self.Portfolio[self.front_VX.Symbol].IsShort and daily_roll < 0.05: self.Liquidate() self.front_VX = None self.front_ES = None return # exit if the daily roll being greater than -0.05 if holding long positions if self.Portfolio[self.front_VX.Symbol].IsLong and daily_roll > -0.05: self.Liquidate() self.front_VX = None self.front_ES = None return if self.front_VX and self.front_ES: # if these exit conditions are not triggered, trades are exited two days before it expires if self.Portfolio[self.front_VX.Symbol].Invested and self.Portfolio[self.front_ES.Symbol].Invested: if (self.front_VX.Expiry-self.Time).days <=2 or (self.front_ES.Expiry-self.Time).days <=2: self.Liquidate() self.front_VX = None self.front_ES = None return def CalculateHedgeRatio(self): price_VX = np.array(self.price_VX) price_ES = np.array(self.price_ES) delta_VX = np.diff(price_VX) res_ES = np.diff(price_ES) / price_ES[:-1]*100 tts = np.array(self.days_to_maturity)[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 = abs((1000*beta_1 + beta_2*((self.front_VX.Expiry-self.Time).days)*1000)/(0.01*50*float(self.Securities[self.es1].Price))) return hedge_ratio def get_expiry_calendar(self): # import the futures expiry calendar url = "data.quantpedia.com/backtesting_data/economic/vix_futures_expiration.csv" csv_string_file = self.Download(url) dates = csv_string_file.split('\r\n') dates = [datetime.strptime(x, "%Y-%m-%d") for x in dates] df_date = pd.DataFrame(dates, index = dates, columns = [ 'expiry']) # convert the index and expiry column to datetime format # df_date.index = pd.to_datetime(df_date.index) df_date['expiry'] = pd.to_datetime(df_date['expiry']) # idx = pd.date_range('19-01-2005', '16-12-2020') # populate the date index and backward fill the dataframe # return df_date.reindex(idx, method='bfill') return df_date class QuandlVix(PythonQuandl): def __init__(self): self.ValueColumnName = "close" class QuandlFutures(PythonQuandl): def __init__(self): self.ValueColumnName = "settle"