Overall Statistics |
Total Trades 52 Average Win 0.04% Average Loss -0.05% Compounding Annual Return -1.410% Drawdown 0.700% Expectancy -0.349 Net Profit -0.473% Sharpe Ratio -2.575 Probabilistic Sharpe Ratio 0.176% Loss Rate 62% Win Rate 38% Profit-Loss Ratio 0.69 Alpha -0.012 Beta 0.001 Annual Standard Deviation 0.005 Annual Variance 0 Information Ratio 0.425 Tracking Error 0.483 Treynor Ratio -12.451 Total Fees $52.00 |
''' TRADING IDEA - Decide on stocks to trade that are gapping up or down - Trade based on opening range breakout - Calculate a range based on high and low of the first n minutes (dynamic) - Place Stop Limit orders based on maximum and minimum range values - Liquidate if the price reaches the opposite end of range - 10 minutes before market close, liquidate all positions that has not hit TP or SL - TODO - Take n mins consolidator closing to take trades or not - Think of SL as ATR - dont trade after 2 PM Help and Resources - TradeBarConsolidator - https://www.quantconnect.com/forum/discussion/5273/consolidators-using-qcalgorithmframework-and-setuniverseselection/p1 - Remove Consolidator - https://github.com/QuantConnect/Lean/blob/master/Algorithm.Python/Alphas/GasAndCrudeOilEnergyCorrelationAlpha.py#L189 - Placing Orders - https://www.quantconnect.com/forum/discussion/1539/how-to-place-three-way-order/p1 ''' from datetime import datetime, time class HorizontalUncoupledAntennaArray(QCAlgorithm): def Initialize(self): self.SetStartDate(2020, 1, 13) # Set Start Date self.SetEndDate(2020, 5, 13) self.SetCash(10000) # Set Strategy Cash self.UniverseSettings.Resolution = Resolution.Minute self.SetWarmUp(120) self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Cash) self.SetAlpha(CustomAlphaModel()) self.SetTimeZone(TimeZones.NewYork) self.AddUniverse(self.CoarseSelectionFilter, self.FineSelectionFilter) self.numberOfSymbolsCoarse = 1000 self.numberOfSymbolsFine = 20 self.Schedule.On( self.DateRules.EveryDay(), \ self.TimeRules.At(15, 45), \ self.EveryDayBeforeMarketClose \ ) ''' Coarse selection function - Only run once every day - Sort by Daily Dollar volume - Returns symbols with price greater than 10 - 2000 symbols returned ''' def CoarseSelectionFilter(self, coarse): sortedByDollarVolume = sorted(coarse, key=lambda x: x.DollarVolume, reverse=True) return [ x.Symbol for x in sortedByDollarVolume if x.Price > 10 and x.HasFundamentalData][:self.numberOfSymbolsCoarse] ''' Fine selection filter - calculate market capital - filter based on market cap ''' def FineSelectionFilter(self, fine): market_cap = {} for i in fine: market_cap[i] = (i.EarningReports.BasicAverageShares.ThreeMonths * i.EarningReports.BasicEPS.TwelveMonths * i.ValuationRatios.PERatio) marketCapFilter = sorted([x for x in fine if market_cap[x] > 0], key=lambda x: market_cap[x], reverse=True) return [ x.Symbol for x in marketCapFilter][:self.numberOfSymbolsFine] def EveryDayBeforeMarketClose(self): self.Liquidate() class CustomAlphaModel: def __init__(self): self.symbolDataBySymbol = {} self.day = 0 self.shortSymbols = [] self.buySymbols = [] self.percentQuantity = 0.05 def Update(self, algorithm, data): insights = [] tradeBars = data.Bars for symb in tradeBars.Keys: if symb in self.symbolDataBySymbol and not (algorithm.Time.time().hour >= 14 and algorithm.Time.time().minute >= 0): if self.symbolDataBySymbol[symb].highRange > 0 and self.symbolDataBySymbol[symb].lowRange > 0 and \ symb.Value not in self.buySymbols and symb.Value not in self.shortSymbols: if self.symbolDataBySymbol[symb].currentFiveMinuteConsolidator is not None and \ self.symbolDataBySymbol[symb].currentFiveMinuteConsolidator.Close > self.symbolDataBySymbol[symb].highRange: buyquantity = round((self.percentQuantity*algorithm.Portfolio.TotalPortfolioValue)/tradeBars[symb].Close) algorithm.MarketOrder(symb, buyquantity) algorithm.StopMarketOrder(symb, -1*buyquantity, self.symbolDataBySymbol[symb].lowRange) self.buySymbols.append(symb.Value) if self.symbolDataBySymbol[symb].currentFiveMinuteConsolidator is not None and \ self.symbolDataBySymbol[symb].currentFiveMinuteConsolidator.Close < self.symbolDataBySymbol[symb].lowRange: sellquantity = round((self.percentQuantity*algorithm.Portfolio.TotalPortfolioValue)/tradeBars[symb].Close) algorithm.MarketOrder(symb, -1*sellquantity) algorithm.StopMarketOrder(symb, sellquantity, self.symbolDataBySymbol[symb].highRange) self.shortSymbols.append(symb.Value) for symb in algorithm.Portfolio.Keys: if algorithm.Securities[symb].Holdings.UnrealizedProfitPercent > 0.01: algorithm.Liquidate(symb, 'One Percent Profit') return insights def OnSecuritiesChanged(self, algorithm, changes): if self.day != algorithm.Time.day: self.day = algorithm.Time.day symbols = [ x.Symbol for x in changes.AddedSecurities ] for removed in changes.RemovedSecurities: symbolData = self.symbolDataBySymbol.pop(removed.Symbol, None) if symbolData is not None: symbolData.RemoveConsolidators(algorithm) for symbol in symbols: ## Create SymbolData objects for any new assets algorithm.Securities[symbol].SetDataNormalizationMode(DataNormalizationMode.SplitAdjusted); symbolData = SymbolData(algorithm, symbol) ## Assign object to a dictionary so you can access it later in the Update() method self.symbolDataBySymbol[symbol] = symbolData class CustomFeeModel: def GetOrderFee(self, parameters): fee = max(1, parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00001) return OrderFee(CashAmount(fee, 'USD')) class SymbolData: def __init__(self, algorithm, symbol): self.Symbol = symbol self.Algorithm = algorithm self.currentFiveMinuteConsolidator = None; self.highRange = -1 self.lowRange = -1 self.currentDay = -1 self.deviationSum = 0 self.gapPercentLow = 1 self.gapPercentHigh = 15 history = algorithm.History([symbol], 10, Resolution.Daily) for index, row in history.iterrows(): print(row['open'], row['close']) hiopen = row['high']- row['open'] openlo = row['open']- row['low'] self.deviationSum += min(hiopen, openlo) self.deviation = self.deviationSum/10 self.lastClose = history.loc[symbol]['close'].iloc[len(history.index) - 1] self.gapUpPercent = -1 self.gapDownPercent = -1 self.five_consolidator = TradeBarConsolidator(5) self.five_consolidator.DataConsolidated += self.FiveMinuteConsolidated self.range_consolidator = TradeBarConsolidator(65) self.range_consolidator.DataConsolidated += self.RangeConsolidated algorithm.SubscriptionManager.AddConsolidator(symbol, self.range_consolidator) ## Register consolidator algorithm.SubscriptionManager.AddConsolidator(symbol, self.five_consolidator) def FiveMinuteConsolidated(self, sender, bar): self.currentFiveMinuteConsolidator = bar pass def RangeConsolidated(self, sender, bar): if self.gapUpPercent == -1 and self.gapDownPercent == -1: if self.lastClose < bar.Open: # This is Gap up self.gapUpPercent = ((bar.Open - self.lastClose)/bar.Open)*100 elif self.lastClose > bar.Open: # This is Gap down self.gapDownPercent = ((self.lastClose - bar.Open)/bar.Open )*100 if self.Symbol.Value == 'SAP': self.Debug('Hello') if self.highRange == -1 and (self.gapPercentLow < self.gapUpPercent < self.gapPercentHigh or self.gapPercentLow < self.gapDownPercent < self.gapPercentHigh): self.highRange= max(bar.High, self.lastClose) + (0*self.deviation) if self.lowRange == -1 and (self.gapPercentLow < self.gapUpPercent < self.gapPercentHigh or self.gapPercentLow < self.gapDownPercent < self.gapPercentHigh): self.lowRange= min(self.lastClose, bar.Low) - (0*self.deviation) def RemoveConsolidators(self, algorithm): algorithm.SubscriptionManager.RemoveConsolidator(self.Symbol, self.range_consolidator) algorithm.SubscriptionManager.RemoveConsolidator(self.Symbol, self.five_consolidator)