| 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)