Overall Statistics |
Total Trades 911 Average Win 1.08% Average Loss -0.28% Compounding Annual Return 34.424% Drawdown 48.900% Expectancy 1.380 Net Profit 1743.581% Sharpe Ratio 0.963 Probabilistic Sharpe Ratio 28.661% Loss Rate 51% Win Rate 49% Profit-Loss Ratio 3.88 Alpha 0.077 Beta 1.691 Annual Standard Deviation 0.288 Annual Variance 0.083 Information Ratio 0.793 Tracking Error 0.2 Treynor Ratio 0.164 Total Fees $39391.44 Estimated Strategy Capacity $15000.00 Lowest Capacity Asset VXXB XT2IGBLOG6ZQ|VXXB WRBPJAJZ2Q91 |
## WHEREAS PREVIOUS VERSIONS OF ALGORITHM INVEST IN ONE HEDGE AT A TIME, THIS VERSION ALLOCATES TO MULTI-LEG SPREAD ## ACROSS MULTIPLE MATURITY DATES (DTE). ## Allocate % of portfolio each to SSO (2x leveraged SP500 etf) and % to VXX weekly call option. ## VXX option strike is set as a percentage premium (e.g., 2.0 means strike is 200% premium to current VIX level) ## Percentage premium method is significantly faster than calculating Greeks (see delta-based algorithm) class VIXTailHedge(QCAlgorithm): def Initialize(self): self.SetStartDate(2012, 1, 12) # self.SetEndDate(2020, 8, 1) self.cash = 1000000 self.SetCash(self.cash) stock = self.AddEquity("SSO", Resolution.Minute) #portfolio holdings self.stock = stock.Symbol self.SetWarmUp(200) self.vxx_option = self.AddEquity("VXX", Resolution.Minute) self.vxx1_option = self.AddEquity("VXX.1", Resolution.Minute) self.vxx_option.SetDataNormalizationMode(DataNormalizationMode.Raw) self.vxx1_option.SetDataNormalizationMode(DataNormalizationMode.Raw) self.vix = self.AddIndex('VIX', Resolution.Minute).Symbol #Used in charts only, not in strategy self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x))) self.hedge = None self.contracts = [] #Plot hedge profits once per month self.Schedule.On(self.DateRules.MonthStart(self.stock), \ self.TimeRules.AfterMarketOpen(self.stock, 30), \ self.Monthlychart) #schedule Plotting function 30 minutes after every market open self.Schedule.On(self.DateRules.EveryDay(self.stock), \ self.TimeRules.AfterMarketOpen(self.stock, 30), \ self.Plotting) ######### Hedge Paramaters ######### #self.hedge_weight = 0.0025 # % of portfolio invested into hedge each month self.hedge_weight = float(self.GetParameter("hedge_weight")) # % of portfolio invested into hedge each month self.stock_weight = 1-self.hedge_weight # % of portfolio allocated to stock each month self.hedge_premium = 2 # % strike price premium on option call hedge self.target_DTE1 = 10 # target days to expiration for selected option contract self.target_DTE2 = 20 # target days to expiration for selected option contract self.target_DTE3 = 30 # target days to expiration for selected option contract #self.target_hedge_profit = 50 # percentage of portfolio covered in tail event self.target_hedge_profit = float(self.GetParameter("target_hedge_profit"))# percentage of portfolio covered in tail event ######### Charts / Plots ######### self.mkt = [] self.hedge_month_profit = 0 self.hedge_ytd_profit = 0 self.hedge_all_profit = 0 #master container for chart: hedgeProfit = Chart("Hedge Profit/Loss") hedgeProfit.AddSeries(Series("Monthly Return", SeriesType.Bar, 0, "%")) hedgeProfit.AddSeries(Series("YTD Return", SeriesType.Bar, 0, "%")) hedgeProfit.AddSeries(Series("All-Time Return", SeriesType.Bar, 1, "%")) self.AddChart(hedgeProfit) def OnData(self, data): ## VXX expired and was relisted in 2019, requires switching tickers ## --> https://www.quantconnect.com/forum/discussion/6804/vxx-minute-the-issue-starts-from-jan-30th-2009-and-continues-until-jan-17th-2018/p1 if self.Time.year < 2019: self.hedge = self.vxx1_option.Symbol else: self.hedge = self.vxx_option.Symbol if self.IsWarmingUp: return self.contracts = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option] for contract in self.contracts: #Use this code block if testing 2020 covid event only, and not including 2015's august vix spike if self.Time.month == 8 and self.Time.year == 2015: return #check to see if hedge meets return requirement (ie, in tail event) if self.Portfolio[contract].UnrealizedProfitPercent*self.hedge_weight*100 >= self.target_hedge_profit: #liquidate hedge position self.MarketOrder(contract, -self.Portfolio[contract].Quantity, False, "Tail Event!") if self.Time.hour == 11: #Placing trades at 11am prevents filling orders at stale prices (fillforward) #Check if we're holding the ideal 3 contracts, if not purchase them if len(self.contracts) < 3: if len(self.contracts) == 0: self.GetContract1() self.GetContract2() self.GetContract3() elif len(self.contracts) == 1: self.GetContract2() self.GetContract3() elif len(self.contracts) == 2: self.GetContract3() for contract in self.contracts: if not self.Portfolio[contract].Invested: self.SetHoldings(self.stock, self.stock_weight) #Purchase hedge contract self.SetHoldings(contract, self.hedge_weight/len(self.contracts)) # #Set stop loss orders in the event of a tail event # contract_quantity = self.Portfolio[contract].Quantity # contract_price = self.Portfolio[contract].Price # stoploss_price = self.contract_price*self.target_hedge_profit/(self.hedge_weight/len(self.contracts)*100) # self.StopLimitOrder(contract, -contract_quantity/3, round(stoploss_price*0.6, 2), round(stoploss_price*0.6, 2)) # self.StopLimitOrder(contract, -contract_quantity/3, round(stoploss_price*1.0, 2), round(stoploss_price*1.0, 2)) # self.StopLimitOrder(contract, -contract_quantity/3, round(stoploss_price*1.4, 2), round(stoploss_price*1.4, 2)) #Liquidate any options that are within 2 days of expiration option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option] if option_invested: for contract in option_invested: expirydate = contract.ID.Date if (expirydate - self.Time) < timedelta(2): # self.Debug("option less than 2 days before expiry") #liquidate hedge before expiration self.Liquidate(contract, "2 days before expiry") def Monthlychart(self): # self.Log("MonthlyChart() fired at : {0}".format(self.Time)) #log number of hedge contracts currently held number_option_contracts = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option] self.Debug(f"Number of hedge contracts held: {len(number_option_contracts)}") #log hedge profits in charts self.Plot("Hedge Profit/Loss", "Monthly Return", self.hedge_month_profit) self.Plot("Hedge Profit/Loss", "YTD Return", self.hedge_ytd_profit) self.Plot("Hedge Profit/Loss", "All-Time Return", self.hedge_all_profit) #reset monthly and yearly profit self.hedge_month_profit = 0 if self.Time.month == 1: self.hedge_ytd_profit = 0 def GetContract1(self): targetStrike = self.Securities[self.hedge].Price * self.hedge_premium contracts = self.OptionChainProvider.GetOptionContractList(self.hedge, self.Time) # self.Debug(f"VXX Total Contracts Found: {len(contracts)}") calls = [x for x in contracts if x.ID.OptionRight == OptionRight.Call] calls = [x for x in calls if abs(x.ID.StrikePrice - targetStrike) < 2] calls = [x for x in calls if 0 <= ((x.ID.Date - self.Time).days - self.target_DTE1) <= 14] # self.Debug(f"VXX Calls found: {len(calls)}") calls = sorted(sorted(calls, key = lambda x: x.ID.Date), key = lambda x: x.ID.StrikePrice) if len(calls) == 0: self.Debug(f"!!! no options available") return None # self.Debug(f"Selected option contract: {calls[0]}") self.AddOptionContract(calls[0], Resolution.Minute) self.contracts.append(calls[0]) return def GetContract2(self): targetStrike = self.Securities[self.hedge].Price * self.hedge_premium contracts = self.OptionChainProvider.GetOptionContractList(self.hedge, self.Time) # self.Debug(f"VXX Total Contracts Found: {len(contracts)}") calls = [x for x in contracts if x.ID.OptionRight == OptionRight.Call] calls = [x for x in calls if abs(x.ID.StrikePrice - targetStrike) < 2] calls = [x for x in calls if 0 <= ((x.ID.Date - self.Time).days - self.target_DTE2) <= 14] # self.Debug(f"VXX Calls found: {len(calls)}") calls = sorted(sorted(calls, key = lambda x: x.ID.Date), key = lambda x: x.ID.StrikePrice) if len(calls) == 0: self.Debug(f"!!! no options available") return None # self.Debug(f"Selected option contract: {calls[0]}") self.AddOptionContract(calls[0], Resolution.Minute) self.contracts.append(calls[0]) return def GetContract3(self): targetStrike = self.Securities[self.hedge].Price * self.hedge_premium contracts = self.OptionChainProvider.GetOptionContractList(self.hedge, self.Time) # self.Debug(f"VXX Total Contracts Found: {len(contracts)}") calls = [x for x in contracts if x.ID.OptionRight == OptionRight.Call] calls = [x for x in calls if abs(x.ID.StrikePrice - targetStrike) < 2] calls = [x for x in calls if 0 <= ((x.ID.Date - self.Time).days - self.target_DTE3) <= 14] # self.Debug(f"VXX Calls found: {len(calls)}") calls = sorted(sorted(calls, key = lambda x: x.ID.Date), key = lambda x: x.ID.StrikePrice) if len(calls) == 0: self.Debug(f"!!! no options available") return None # self.Debug(f"Selected option contract: {calls[0]}") self.AddOptionContract(calls[0], Resolution.Minute) self.contracts.append(calls[0]) return #Log hedge profits for charts def OnOrderEvent(self, orderEvent): if orderEvent.Status == OrderStatus.Filled and orderEvent.Symbol != self.stock and orderEvent.Direction == OrderDirection.Sell: # self.Log("{0}: {1}".format(self.Time, orderEvent)) # self.Debug(f"{orderEvent.Symbol} Profit_$: {self.Portfolio[orderEvent.Symbol].Profit}, \ # Profit_%_Portfolio: {self.Portfolio[orderEvent.Symbol].Profit/self.Portfolio.TotalPortfolioValue}") self.hedge_month_profit += self.Portfolio[orderEvent.Symbol].Profit/self.Portfolio.TotalPortfolioValue*100 self.hedge_ytd_profit += self.Portfolio[orderEvent.Symbol].Profit/self.Portfolio.TotalPortfolioValue*100 self.hedge_all_profit += self.Portfolio[orderEvent.Symbol].Profit/self.Portfolio.TotalPortfolioValue*100 def Plotting (self): mkt_price = self.Securities[self.stock].Close self.mkt.append(mkt_price) mkt_perf = self.mkt[-1] / self.mkt[0] * self.cash self.Plot('Strategy Equity', 'SSO', mkt_perf) #plot strike price of option option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option] if option_invested and self.contracts is not None: self.Plot("Hedge Strike", "Strike", option_invested[0].ID.StrikePrice) self.Plot("Hedge Strike", "VXX Price", self.Securities[self.hedge].Price) self.Plot("Hedge Strike", "VIX Price", self.Securities[self.vix].Price) self.Plot("Hedge Unrealized Profit", "VXX Price", self.Securities[self.hedge].Price) self.Plot("Hedge Unrealized Profit", "VIX Price", self.Securities[self.vix].Price) self.Plot("Hedge Unrealized Profit", "Unrealized Profit", self.Portfolio[option_invested[0]].UnrealizedProfitPercent)