Overall Statistics |
Total Trades 188 Average Win 1.56% Average Loss -0.79% Compounding Annual Return 12.010% Drawdown 18.200% Expectancy 0.159 Net Profit 25.541% Sharpe Ratio 0.745 Probabilistic Sharpe Ratio 32.634% Loss Rate 61% Win Rate 39% Profit-Loss Ratio 1.98 Alpha 0.113 Beta -0.039 Annual Standard Deviation 0.146 Annual Variance 0.021 Information Ratio 0.004 Tracking Error 0.284 Treynor Ratio -2.783 Total Fees $265.27 |
class EarningsReversal(QCAlgorithm): def Initialize(self): self.SetStartDate(2018, 10, 1) self.SetEndDate(2020, 10, 1) self.SetCash(100000) self.UniverseSettings.Resolution = Resolution.Daily self.AddUniverse(self.CoarseSelection, self.FineSelection) # Initialize lists/dicts self.longSymbols = [] self.entryPrices = {} self.highestPrice = {} self.stopMarketTicket = {} # Add custom bar chart stockPlot = Chart('Positions') stockPlot.AddSeries(Series('Longs', SeriesType.Bar, 0)) self.AddChart(stockPlot) # Set Benchmark self.AddEquity("SPY", Resolution.Daily) self.SetBenchmark("SPY") # Schedule EveryMarketOpen function self.Schedule.On(self.DateRules.EveryDay("SPY"), \ self.TimeRules.At(10, 00), \ self.EveryMarketOpen) # ------------- Paramters -------------- # Minimum percentage decline before entry self.entryMove = 0.02 # Max number of positions allowed at once (equal weighting) self.maxPositions = 10 # Size of coarse selection universe self.numOfCoarse = 6*self.maxPositions # Number of days past since earnings self.daysSinceEarnings = 1 # Percentage offset of trailing stop loss self.stopLoss = 0.10 # Pick the top 100 liquid equities as the coarse-selected universe def CoarseSelection(self, coarse): # Sort the equities (price > 5) by Dollar Volume descendingly selectedByDollarVolume = sorted([x for x in coarse if x.Price > 5 and x.HasFundamentalData], key = lambda x: x.DollarVolume, reverse = True) # Pick the top numOfCoarse liquid equities as the coarse-selected universe return [x.Symbol for x in selectedByDollarVolume[:self.numOfCoarse]] # Pick stocks with recent earnings and noticeable reaction def FineSelection(self, fine): # filter out stocks with recent earnings fine = [x for x in fine if self.Time == x.EarningReports.FileDate + timedelta(days=self.daysSinceEarnings)] symbols = [x.Symbol for x in fine] # Save the stock prices around earnings pricesAroundEarnings = self.History(symbols, self.daysSinceEarnings+3, Resolution.Daily) for sec in fine: # Find tradeable date closest to specified number of days before earnings date = min(pricesAroundEarnings.loc[sec.Symbol]["close"].index, key=lambda x:abs(x-(sec.EarningReports.FileDate - timedelta(1)))) priceOnEarnings = pricesAroundEarnings.loc[sec.Symbol]["close"][date] # Check if stock fell far enough if priceOnEarnings * (1-self.entryMove) > sec.Price: self.longSymbols.append(sec.Symbol) return self.longSymbols # Open equal weighted positions and create trailing stop loss def EveryMarketOpen(self): positions = [sec.Symbol for sec in self.Portfolio.Values if self.Portfolio[sec.Symbol].Invested] # Plot number of existing positions self.Plot("Positions", "Longs", len(positions)) availableTrades = self.maxPositions - len(positions) for symbol in [x for x in self.longSymbols if x not in positions][:availableTrades]: if self.Securities.ContainsKey(symbol): # Buy stock self.SetHoldings(symbol, 1 / self.maxPositions) self.longSymbols = [] for symbol in positions: # If no order exists, send stop-loss if not self.Transactions.GetOpenOrders(symbol): self.stopMarketTicket[symbol] = self.StopMarketOrder(symbol, \ -self.Portfolio[symbol].Quantity, \ (1-self.stopLoss) * self.entryPrices[symbol]) # Check if the asset's price is higher than earlier highestPrice elif self.Securities[symbol].Close > self.highestPrice[symbol]: # Save the new high to highestPrice self.highestPrice[symbol] = self.Securities[symbol].Close # Update the stop price updateFields = UpdateOrderFields() updateFields.StopPrice = self.Securities[symbol].Close * (1-self.stopLoss) self.stopMarketTicket[symbol].Update(updateFields) # On every order event, save fill price and current price def OnOrderEvent(self, orderEvent): if orderEvent.Status == OrderStatus.Filled: self.entryPrices[orderEvent.Symbol] = orderEvent.FillPrice self.highestPrice[orderEvent.Symbol] = orderEvent.FillPrice def OnData(self, data): pass