| 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