Overall Statistics |
Total Trades 155 Average Win 40.55% Average Loss -0.16% Compounding Annual Return 4.831% Drawdown 9.700% Expectancy 5.438 Net Profit 60.360% Sharpe Ratio 0.229 Probabilistic Sharpe Ratio 0.000% Loss Rate 97% Win Rate 3% Profit-Loss Ratio 246.85 Alpha 0.068 Beta -0.151 Annual Standard Deviation 0.215 Annual Variance 0.046 Information Ratio -0.276 Tracking Error 0.266 Treynor Ratio -0.325 Total Fees $2375.50 Estimated Strategy Capacity $3400000.00 Lowest Capacity Asset QQQ 31R15TVANMZ8M|QQQ RIWIV7K5Z9LX |
# Watch my Tutorial: https://youtu.be/Lq-Ri7YU5fU # Options: Options perform best during periods of short sharp drops when volatility spikes. # Options held till expiry do a great job of reducing drawdown severity but won't boost returns unless the drawdown is # protracted and still exists at time of Expiry. from datetime import timedelta class OptionChainProviderPutProtection(QCAlgorithm): def Initialize(self): # set start/end date for backtest self.SetStartDate(2011, 10, 1) self.SetEndDate(2021, 10, 1) # set starting balance for backtest self.SetCash(400000) # add the underlying asset self.equity = self.AddEquity("QQQ", Resolution.Minute) self.equity.SetDataNormalizationMode(DataNormalizationMode.Raw) self.underlyingSymbol = self.equity.Symbol # initialize the option contract with empty string self.contract = str() self.contractsAdded = set() # parameters ------------------------------------------------------------ self.OTM = 0.20 # target percentage OTM of put self.StaggerStrike = 0.05 + self.OTM # Stagger strikes at X% levels OTM self.DTE = 60 # target 'Calendar' days till expiration self.DTEBuffer = 15 self.DaysBeforeExp = 2 # number of days before expiry to exit self.ProfitTarget = 0.5 # OptionValue as a Percentage of Portfolio self.percentage = 0.97 # percentage of portfolio for underlying asset self.options_weight = 0.02 * 30 / 365 # This allows roughly X% per year for StrikeStaggeredPuts # ------------------------------------------------------------------------ # schedule Plotting function 30 minutes after every market open self.Schedule.On(self.DateRules.EveryDay(self.underlyingSymbol), self.TimeRules.AfterMarketOpen(self.underlyingSymbol, 30), self.Plotting) def OnData(self, data): if not data.ContainsKey(self.underlyingSymbol) or data[self.underlyingSymbol] is None or data[self.underlyingSymbol].IsFillForward: return # OPTIONS ------------------------------------------- # Close Options Positions. for contract in self.contractsAdded: if self.Portfolio[contract].Invested: if not data.ContainsKey(contract): continue # Do nothing if data is NoneType. if data[contract] is None: continue # 2) Close put if Target achieved (Rebalance proceeds?). optionValue = self.Portfolio[contract].Quantity * 100 * self.Securities[contract].BidPrice optionExposure = optionValue / self.Portfolio.TotalPortfolioValue if optionExposure > self.ProfitTarget: self.SellPut(contract, data) self.Log(f'Put Target achieved @ {self.Time}') self.Log(f'BidPrice: {self.Securities[contract].BidPrice}, AskPrice: {self.Securities[contract].AskPrice}') # 3) Close put before it expires if self.Time.minute % 5 == 1 and (contract.ID.Date - self.Time) <= timedelta(self.DaysBeforeExp): self.SellPut(contract, data) # Initiate our Options position option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option] option_invested = sorted(option_invested, key=lambda x: x.ID.StrikePrice, reverse=True) buyOption = False # 1) If no options, BUY. if not option_invested: buyOption = True # 2) If price has exceeded our current strike by X%, BUY elif option_invested[0]: buyOption = bool( (1 - option_invested[0].ID.StrikePrice / self.Securities[self.underlyingSymbol].Price) > self.StaggerStrike) # Don't try to buy on each data point. Wait a few minutes to search for our contract and initiate a position. if buyOption: if self.Time.minute % 5 == 0: self.contract = self.OptionsFilter(data) if self.Time.minute % 5 == 3 and self.contract and not self.Portfolio[self.contract].Invested: self.BuyPut(data) def BuyPut(self, data): # Do nothing if data is NoneType or data is stale. if data.ContainsKey(self.contract) and not self.Portfolio[self.contract].Invested: if data[self.contract] is None or data[self.contract].IsFillForward: return quantity = self.CalculateOrderQuantity(self.contract, self.options_weight) if quantity < 1: self.Log('Cannot buy Options... too expensive!') self.SetHoldings(self.contract, self.options_weight) def SellPut(self, contract, data): self.SetHoldings(contract, 0) #, ([PortfolioTarget(self.underlyingSymbol, self.percentage)]) self.contract = str() def OptionsFilter(self, data): ''' OptionChainProvider gets a list of option contracts for an underlying symbol at requested date. Then you can manually filter the contract list returned by GetOptionContractList. The manual filtering will be limited to the information included in the Symbol (strike, expiration, type, style) and/or prices from a History call ''' contracts = self.OptionChainProvider.GetOptionContractList(self.underlyingSymbol, data.Time) underlyingPrice = self.Securities[self.underlyingSymbol].Price # filter the out-of-money put options from the contract list which expire close to self.DTE num of days from now otm_puts = [i for i in contracts if i.ID.OptionRight == OptionRight.Put and underlyingPrice - i.ID.StrikePrice > self.OTM * underlyingPrice and (self.DTE - self.DTEBuffer) < (i.ID.Date - data.Time).days < (self.DTE + self.DTEBuffer)] if len(otm_puts) > 0: # sort options by closest to self.DTE days from now and desired strike, and pick first contract = sorted( sorted(otm_puts, key = lambda x: abs((x.ID.Date - self.Time).days - self.DTE)), key = lambda x: underlyingPrice - x.ID.StrikePrice)[0] if contract not in self.contractsAdded: self.contractsAdded.add(contract) self.AddOptionContract(contract, Resolution.Minute) return contract else: #self.Log('No Options at this time...') return str() def Plotting(self): self.Plot("Data Chart", self.underlyingSymbol, self.Securities[self.underlyingSymbol].Close) # plot strike of put option option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option] option_invested = sorted(option_invested, key=lambda x: x.ID.StrikePrice) if option_invested: self.Plot("Opton Invested", "Options", len(option_invested)) self.Plot("Data Chart", "Strike1", option_invested[0].ID.StrikePrice) def OnOrderEvent(self, orderEvent): # log order events pass #self.Log(str(orderEvent))