Overall Statistics |
Total Trades 10001 Average Win 0.02% Average Loss -0.02% Compounding Annual Return 9.562% Drawdown 3.100% Expectancy 0.215 Net Profit 21.305% Sharpe Ratio 1.963 Loss Rate 44% Win Rate 56% Profit-Loss Ratio 1.17 Alpha 0.085 Beta -0.1 Annual Standard Deviation 0.038 Annual Variance 0.001 Information Ratio -0.157 Tracking Error 0.136 Treynor Ratio -0.753 Total Fees $0.00 |
import json from datetime import datetime, timedelta # Copies logic from https://www.quantconnect.com/tutorials/strategy-library/fundamental-factor-long-short-strategy # Algo storing - https://www.quantconnect.com/docs/algorithm-framework/algorithm-scoring class ChartMillBackTester(QCAlgorithm): def Initialize(self): self.SetCash(1000*100) # Are't we rich? :D self.SetStartDate(2017, 1, 1) self.SetEndDate(2019, 9, 28) # if not specified, the Backtesting EndDate would be today self.MyPortfolio = {} # {:ticker => date_invested_on} self.MaxHoldingPeriodInDays = 5 self.MaxQuanityToHoldPerTicker = 10 # # Long tickers # self.LongScannerData = json.loads(self.Download("https://firebasestorage.googleapis.com/v0/b/red-zombies.appspot.com/o/chartmill_scans%2Flong_tickers_technical_breakout_2017-2019.json?alt=media")) # self.Debug("[LongScannerData] : " + str(self.LongScannerData)) # # Short tickers # self.ShortScannerData = json.loads(self.Download("https://firebasestorage.googleapis.com/v0/b/red-zombies.appspot.com/o/chartmill_scans%2Fshort_tickers_scanned.json?alt=media")) # self.Debug("[ShortScannerData] : " + str(self.ShortScannerData)) #scannerDataUrl = "https://firebasestorage.googleapis.com/v0/b/red-zombies.appspot.com/o/chartmill_scans%2Flong_tickers_technical_breakout_2017-2019.json?alt=media" #scannerDataUrl = "https://firebasestorage.googleapis.com/v0/b/red-zombies.appspot.com/o/chartmill_scans%2Fbull_bear_flags__2017-01-01__2019-09-28.json?alt=media" scannerDataUrl = "https://firebasestorage.googleapis.com/v0/b/red-zombies.appspot.com/o/chartmill_scans%2Ftechnical_breakout__strong_downtrend__2017-01-01__2019-10-03_new.json?alt=media" scannerData = json.loads(self.Download(scannerDataUrl)) # {:long_tickers => {}, :short_tickers => {}} self.LongScannerData = scannerData["long_tickers"] # {:date => [ticker, ...], ...} self.ShortScannerData = scannerData["short_tickers"] # {:date => [ticker, ...], ...} self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol self.SetBenchmark("SPY") self.UniverseSettings.Resolution = Resolution.Daily #self.AddUniverse(self.ChatMillScannerData_CoarseSelectionFunction,self.ChatMillScannerData_FineSelectionFunction) for date in self.LongScannerData: try: tickers = (self.LongScannerData[date] + self.ShortScannerData[date]) for ticker in tickers: self.AddEquity(ticker, Resolution.Daily) # Constant fee model - https://www.quantconnect.com/docs/algorithm-reference/reality-modelling#Reality-Modelling self.Securities[ticker].FeeModel = ConstantFeeModel(0) # Let's assume for a sec that there's no tx fee! :) except: pass # Schedule the rebalance function to execute at the begining of each day # https://www.quantconnect.com/docs/algorithm-reference/scheduled-events self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.AfterMarketOpen(self.spy,5), Action(self.LongShortStrategy)) # def ChatMillScannerData_CoarseSelectionFunction(self, coarse): # #self.Debug("Inside ChatMillScannerData_CoarseSelectionFunction") # return self.GetSymbolsFromChartMill() # def ChatMillScannerData_FineSelectionFunction(self, fine): # #self.Debug("Inside ChatMillScannerData_FineSelectionFunction") # return self.GetSymbolsFromChartMill() # Returns [Symbol, ...] # longOrShort => "long" / "short" def GetSymbolsFromChartMill(self, longOrShort): date = str(self.Time).split(" ")[0] symbols = [] symbolsForDate = [] try: symbolsForDate = self.LongScannerData[date] if (longOrShort == "long") else self.ShortScannerData[date] except: self.Debug("Error in fetching symbols for date : " + date) for ticker in symbolsForDate: try: symbols.append(Symbol.Create(ticker, SecurityType.Equity, Market.USA)) except: self.Debug("Error in creating symbol : " + ticker) #self.Debug(str(symbols)) return symbols def OnData(self, data): # This function is not needed as we rely on the self.Schedule function to trigger the algo pass # https://www.quantconnect.com/docs/algorithm-reference/securities-and-portfolio # https://www.quantconnect.com/docs/key-concepts/security-identifiers # Algo : if invested, liquidate after N days. Else, go long or short! # TODO : if benchmark is below 90 ema, give more weightage to short. else, to longs def LongShortStrategy(self): #self.Debug("Inside Rebalance") longSymbols = self.GetSymbolsFromChartMill("long") shortSymbols = self.GetSymbolsFromChartMill("short") # Go Long on long symbols for symbol in longSymbols: ticker = str(symbol) try: # If not bought the ticker already, buy it if not self.Securities[ticker].Invested: self.Order(ticker, self.MaxQuanityToHoldPerTicker) # Update the ticker's last invested date in MyPortfolio self.MyPortfolio[ticker] = self.Time except Exception as error: self.Debug("E/Long[" + ticker + "] ==> " + str(error)) # # Go Short on short symbols for symbol in shortSymbols: ticker = str(symbol) try: # If not bought the ticker already, buy it if not self.Securities[ticker].Invested: self.Order(ticker, -1 * self.MaxQuanityToHoldPerTicker) # Update the ticker's last invested date in MyPortfolio self.MyPortfolio[ticker] = self.Time except Exception as error: self.Debug("E/Short[" + ticker + "] ==> " + str(error)) # Liquidating for ticker in list(self.MyPortfolio): # we need to use list() because we pop https://stackoverflow.com/a/11941855/440362 try: # Buy the ticker and update it's holdings in MyPortfolio today = self.Time investedDate = self.MyPortfolio[ticker] tickerHoldingPeriod = abs((today - investedDate).days) if tickerHoldingPeriod > self.MaxHoldingPeriodInDays: # liquidate the ticker and remove it from holding tracking #self.Debug("Liquidating ticker : " + ticker) self.Liquidate(ticker) self.MyPortfolio.pop(ticker) except Exception as error: self.Debug("E/Liquidate[" + ticker + "] ==> " + str(error)) # Used to create some space at the bottom, ignore def Foo(self): pass