Overall Statistics |
Total Orders 210 Average Win 0.03% Average Loss 0.00% Compounding Annual Return 0.119% Drawdown 0.200% Expectancy 1.465 Start Equity 10000000 End Equity 10059850.98 Net Profit 0.599% Sharpe Ratio -5.055 Sortino Ratio -5.065 Probabilistic Sharpe Ratio 12.888% Loss Rate 74% Win Rate 26% Profit-Loss Ratio 8.51 Alpha -0.008 Beta 0.01 Annual Standard Deviation 0.001 Annual Variance 0 Information Ratio -0.981 Tracking Error 0.106 Treynor Ratio -0.691 Total Fees $645.43 Estimated Strategy Capacity $5100000000.00 Lowest Capacity Asset VX VF1TI9X216G9 Portfolio Turnover 0.07% |
#region imports from AlgorithmImports import * from collections import deque import statsmodels.api as sm #endregion class TermStructureOfVixAlgorithm(QCAlgorithm): def initialize(self): self.set_start_date(2012, 1, 1) # Set Start Date self.set_end_date(2017, 1, 1) # Set End Date self.set_cash(10000000) # Set Strategy Cash self.settings.minimum_order_margin_portfolio_percentage = 0 self.settings.daily_precise_end_time = False vix = self.add_index("VIX", Resolution.DAILY) self.vix = vix.symbol self.vix_multiplier = vix.symbol_properties.contract_multiplier self.vx1 = self.add_future(Futures.Indices.VIX, resolution=Resolution.DAILY, extended_market_hours=True, data_normalization_mode=DataNormalizationMode.BACKWARDS_RATIO, data_mapping_mode=DataMappingMode.LAST_TRADING_DAY, contract_depth_offset=0 ) self.vx1_multiplier = self.vx1.symbol_properties.contract_multiplier self.es1 = self.add_future(Futures.Indices.SP_500_E_MINI, resolution=Resolution.DAILY, extended_market_hours=True, data_normalization_mode=DataNormalizationMode.BACKWARDS_RATIO, data_mapping_mode=DataMappingMode.LAST_TRADING_DAY, contract_depth_offset=0 ) self.es1_multiplier = self.es1.symbol_properties.contract_multiplier # 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) stock_plot = Chart("Trade") stock_plot.add_series(Series("VIX", SeriesType.LINE, 0)) stock_plot.add_series(Series("VIX Futures", SeriesType.LINE, 0)) stock_plot.add_series(Series("Buy", SeriesType.SCATTER, 0)) stock_plot.add_series(Series("Sell", SeriesType.SCATTER, 0)) stock_plot.add_series(Series("Daily Roll", SeriesType.SCATTER, 0)) stock_plot.add_series(Series("Hedge Ratio", SeriesType.SCATTER, 0)) self.set_warm_up(253, Resolution.DAILY) def on_data(self, data): if data.bars.contains_key(self.vx1.symbol) and data.bars.contains_key(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.is_warming_up or not self.price_vx.is_ready or not self.price_es.is_ready or not self.days_to_maturity.is_ready or (self.vx1.mapped.id.date - self.time).days == 0): return if data.bars.contains_key(self.vx1.symbol) and data.bars.contains_key(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._calculate_hedge_ratio(data.bars[self.es1.symbol].close) self.plot("Trade", "Sell", vx1_price) qty = self.calculate_order_quantity(self.vx1.mapped, -1) // self.vx1.symbol_properties.contract_multiplier if qty: self.market_order(self.vx1.mapped, qty, tag='Enter: daily_roll > 0.1') qty = self.calculate_order_quantity(self.es1.mapped, -1*hedge_ratio) // self.es1.symbol_properties.contract_multiplier if qty: self.market_order(self.es1.mapped, qty, tag='Enter: daily_roll > 0.1') # Long if the contract is in backwardation with adaily roll less than -0.10 elif daily_roll < -0.1: hedge_ratio = self._calculate_hedge_ratio(data.bars[self.es1.symbol].close) self.plot("Trade", "Buy", vx1_price) qty = self.calculate_order_quantity(self.vx1.mapped, 1) // self.vx1.symbol_properties.contract_multiplier if qty: self.market_order(self.vx1.mapped, qty, tag='Enter: daily_roll < -0.1') qty = self.calculate_order_quantity(self.es1.mapped, 1*hedge_ratio) // self.es1.symbol_properties.contract_multiplier if qty: self.market_order(self.es1.mapped, qty, tag='Enter: daily_roll < -0.1') # exit if the daily roll being less than 0.05 if holding short positions if self.portfolio[self.vx1.mapped].is_short and daily_roll < 0.05: self.liquidate(tag='Exit: Short and daily_roll < 0.05') return # exit if the daily roll being greater than -0.05 if holding long positions if self.portfolio[self.vx1.mapped].is_long and daily_roll > -0.05: self.liquidate(tag='Exit: Long and daily_roll > -0.05') 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(tag='Exit: Expire within 2 days') def _calculate_hedge_ratio(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__v_x = np.diff(price_vx) res__e_s = np.diff(price_es)/price_es[:-1]*100 tts = np.array(list(self.days_to_maturity))[::-1][1:] df = pd.DataFrame({"delta__v_x":delta__v_x, "SPRET":res__e_s, "product":res__e_s*tts}).dropna() # remove rows with zero value df = df[(df != 0).all(1)] y = df['delta__v_x'].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