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