Overall Statistics |
Total Trades 35 Average Win 82.56% Average Loss -13.95% Compounding Annual Return 4994726.341% Drawdown 82.400% Expectancy 5.511 Net Profit 336080.175% Sharpe Ratio 6210465.379 Probabilistic Sharpe Ratio 99.990% Loss Rate 6% Win Rate 94% Profit-Loss Ratio 5.92 Alpha 25959543.25 Beta -1.629 Annual Standard Deviation 4.18 Annual Variance 17.472 Information Ratio 6108229.276 Tracking Error 4.25 Treynor Ratio -15936514.701 Total Fees $2146.50 |
########################################################################## # The 'Mini-Leap' Roller # Author: Ikezi Kamanu # ------------------------------------------------- # # Entry: # 30 Mins before market close, if the portfolio is empty, # open as many single OTM call options as possible, with # strikes that are 10% above the underlying, expiring in 120 days. # # Exit: # 30 Mins after market open, for every call in the portfolio, # if the call is ITM or there are less than 30 days till expiry, # liquidate the position. They will be re-opened (rolled) before close. # ########################################################################## class MiniLeapRoller(QCAlgorithm): # ============================================================== # Initialize data, capital, scheduled routines, etc. # ============================================================== def Initialize(self): # set params: ticker, entry, exit thresholds # ----------------------------------------------------- self.ticker = "ZM" self.distFromPrice = int(self.GetParameter("priceDist")) # pick strike that is this % above from price self.enterAtDTE = int(self.GetParameter("enterDTE")) # buy option with this many days till expiry self.exitAtDTE = int(self.GetParameter("exitDTE")) # sell when this many days left tille expiry # set start/end date for backtest # -------------------------------------------- self.SetStartDate(2020, 1, 1) # Set Start Date self.SetEndDate(2020, 9, 30) # Set End Date # set starting balance for backtest # -------------------------------------------- self.SetCash(2000) # add the underlying asset # --------------------------------- self.equity = self.AddEquity(self.ticker, Resolution.Minute) self.equity.SetDataNormalizationMode(DataNormalizationMode.Raw) self.symbol = self.equity.Symbol self.forceInitialized = False # set custom security intializer # ------------------------------- self.SetSecurityInitializer(self.InitializeSecurities) # schedule routine to run 30 minutes after every market open # -------------------------------------------------------------- self.Schedule.On(self.DateRules.EveryDay(self.symbol), \ self.TimeRules.AfterMarketOpen(self.symbol, 30), \ self.OnThirtyMinsAfterMarketOpen) # schedule routine to run 30 minutes before every market close # -------------------------------------------------------------- self.Schedule.On(self.DateRules.EveryDay(self.symbol), \ self.TimeRules.BeforeMarketClose(self.symbol, 30), \ self.OnThirtyMinsBeforeMarketClose) # set the warmup period # ---------------------- self.SetWarmUp(200) # ============================================================== # Initialize the security # ============================================================== def InitializeSecurities(self, security): # intialize securities with last known price, # so that we can immediately trade the security # ------------------------------------------------ bar = self.GetLastKnownPrice(security) security.SetMarketPrice(bar) # ============================================================== # OnData Event handler # ============================================================== def OnData(self, data): # if we have data # ------------------------------ if (self.Securities[self.symbol] is not None) and \ (self.Securities[self.symbol].HasData) and \ (data.Bars.ContainsKey(self.symbol)): # keep track of the current close price # ------------------------------------------ self.underlyingPrice = data.Bars[self.symbol].Close # ============================================================== # Run this 30 minutes before market close # ============================================================== def OnThirtyMinsBeforeMarketClose(self): # exit if we are still warming up # or if we do not have any data # ---------------------------------- if(self.IsWarmingUp) or \ (not self.Securities[self.symbol].HasData): return # otherwise, if we have no holdings, open a position. # -------------------------------------------------- if not self.Portfolio.Invested: # set strikes and expiration # ------------------------------ callStrike = self.underlyingPrice * (1 + (self.distFromPrice/100) ) expiration = self.Time + timedelta(days=self.enterAtDTE) ### todo: consider using ATR for strike selection ### todo: consider using delta for strike selection ### todo: encapsulate strike and expiry selection in GetLongCall() # retrive closest call contracts # ------------------------------- callContract = self.GetLongCall(self.symbol, callStrike, expiration) # subscribe to data for those contracts # ----------------------------------------- self.AddOptionContract(callContract, Resolution.Minute) # buy call # -------------- self.SetHoldings(callContract, 1) self.Debug ("\r+-------------") self.Debug (f"| {self.Time} [+]--- BUY {str(callContract)} || Stock @ {str(self.underlyingPrice)}") # ============================================================== # Run this 30 minutes after market open # ============================================================== def OnThirtyMinsAfterMarketOpen(self): # exit if we are still warming up # or if we do not have any data # ---------------------------------- if(self.IsWarmingUp) or \ (not self.Securities[self.symbol].HasData): return # check for open contracts and close them if warranted. # ------------------------------------------------------ for symbol in self.Securities.Keys: if self.Securities[symbol].Invested: # if current contract is ITM, liquidate # --------------------------------------- if (self.underlyingPrice > self.Securities[symbol].StrikePrice): self.Debug (f"| {self.Time} [-]--- SELL {symbol} || ITM | Stock @ {str(self.underlyingPrice)}") self.Debug ("+-------------") self.Liquidate() return # if current contract expiry is less than 'X' liquidate # ----------------------------------------------------------- elif ((self.Securities[symbol].Expiry - self.Time).days < self.exitAtDTE): self.Debug (f"| {self.Time} [-]--- SELL {symbol} || <30 DTE | {(self.Securities[symbol].Expiry - self.Time).days} DTE") self.Debug ("+-------------") self.Liquidate() return # ============================================================== # Get Long Call, given a symbol, desired strike and expiration # ============================================================== def GetLongCall(self, symbolArg, callStrikeArg, expirationArg): contracts = self.OptionChainProvider.GetOptionContractList(symbolArg, self.Time) # get all calls # ------------- calls = [symbol for symbol in contracts if symbol.ID.OptionRight == OptionRight.Call] # sort contracts by expiry dates and select expiration closest to desired expiration # -------------------------------------------- callsSortedByExpiration = sorted(calls, key=lambda p: abs(p.ID.Date - expirationArg), reverse=False) closestExpirationDate = callsSortedByExpiration[0].ID.Date # get all contracts for selected expiration # ------------------------------------------------ callsFilteredByExpiration = [contract for contract in callsSortedByExpiration if contract.ID.Date == closestExpirationDate] # sort contracts and select the one closest to desired strike # ----------------------------------------------------------- callsSortedByStrike = sorted(callsFilteredByExpiration, key=lambda p: abs(p.ID.StrikePrice - callStrikeArg), reverse=False) callOptionContract = callsSortedByStrike[0] return callOptionContract