Overall Statistics
Total Trades
2542
Average Win
0.32%
Average Loss
-0.21%
Compounding Annual Return
6.348%
Drawdown
32.100%
Expectancy
0.189
Net Profit
59.532%
Sharpe Ratio
0.351
Loss Rate
53%
Win Rate
47%
Profit-Loss Ratio
1.52
Alpha
-0.393
Beta
28.236
Annual Standard Deviation
0.205
Annual Variance
0.042
Information Ratio
0.271
Tracking Error
0.205
Treynor Ratio
0.003
Total Fees
$2630.20
# https://quantpedia.com/Screener/Details/54
from System import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Data import SubscriptionDataSource
from QuantConnect.Python import PythonData
from datetime import date, timedelta, datetime
from decimal import Decimal
import numpy as np

class MomentumandStateofMarketFiltersAlgorithm(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2011, 1, 1)
        self.SetEndDate(2018, 8, 1)
        self.SetCash(100000)
        # add Wilshire 5000 Total Market Index data from Dropbox 
        self.AddData(Wilshire5000, "W5000", Resolution.Daily)
        self.W5000Return = self.ROC("W5000", 252)
        # initialize the RateOfChange indicator of Wilshire 5000 total market index
        history = self.History(["W5000"], 500, Resolution.Daily)
        for tuple in history.loc["W5000"].itertuples():
            self.W5000Return.Update(tuple.Index, tuple.value)
            
        self.Debug("W5000 Rate of Change indicator isReady: "+ str(self.W5000Return.IsReady))    
        self.AddEquity("SPY", Resolution.Daily)        
        self.AddUniverse(self.CoarseSelectionFunction)
        self.Schedule.On(self.DateRules.MonthStart("SPY"), self.TimeRules.At(0, 0), self.rebalance)
        # mark it's the start of each month
        self.month_start = False
        # mark the coarse universe selection has finished
        self.selection = False
        self.mom = {}
        self.lookback = 20*6
        self.long = None
        self.short = None
        self.tlt = self.AddEquity("TLT", Resolution.Daily).Symbol        
    
    def CoarseSelectionFunction(self, coarse):
        coarse = [x for x in coarse if (x.HasFundamentalData and x.AdjustedPrice > 1)]
        for i in coarse:
            if i.Symbol not in self.mom:
                self.mom[i.Symbol] = SymbolData(i.Symbol, self.lookback)
            self.mom[i.Symbol].MOM.Update(self.Time, i.AdjustedPrice)
        
        if self.month_start:
            self.selection = True
            self.MOMReady = {symbol: SymbolData for symbol, SymbolData in self.mom.items() if SymbolData.MOM.IsReady}
            if self.MOMReady:
                # sort stocks by 6-month momentum
                sortByMOM = sorted(self.MOMReady, key = lambda x: self.MOMReady[x].MOM.Current.Value, reverse = True)
                self.long = sortByMOM[:20]
                self.short = sortByMOM[-20:]
                return self.long+self.short
            else:
                return []
        else: 
            return []   

    def rebalance(self):
        # rebalance every month
        self.month_start = True
            
    def OnData(self, data):
        if self.month_start and self.selection: 
            self.month_start = False
            self.selection = False
            if self.long is None or self.short is None: return
            # if the previous 12 months return on the broad equity market was positive
            if self.W5000Return.Current.Value > 0: 
                stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
                for i in stocks_invested:
                    if i not in self.long+self.short:
                        self.Liquidate(i)

                short_weight = 0.5/len(self.short)
                # goes short on the prior six-month losers (lowest decile) 
                for short_symbol in self.short:
                    self.SetHoldings(short_symbol, -short_weight)
                # goes long on the prior six-month winners (highest decile)        
                long_weight = 0.5/len(self.long)
                for long_symbol in self.long:
                    self.SetHoldings(long_symbol, long_weight)
            else:
                self.Liquidate()
                self.SetHoldings(self.tlt, 1)
                   
        
class Wilshire5000(PythonData):
    "Class to import Wilshire 5000 Total Market Index data from Dropbox"
    
    def GetSource(self, config, date, isLiveMode):
        return SubscriptionDataSource("https://www.dropbox.com/s/z9rof4fr9cqzgpt/W5000.csv?dl=1", SubscriptionTransportMedium.RemoteFile)

    def Reader(self, config, line, date, isLiveMode):
        if not (line.strip() and line[1].isdigit()): return None
        index = Wilshire5000()
        index.Symbol = config.Symbol
        try:
            # Example File Format: (Data starts from 01/04/2010)
            # Date      Open           High           Low            Close          Adj Close      Volume
            # 1/4/10    11549.13965    11749.37012    11549.13965    11743.54004    11743.54004    0
            data = line.split(',')
            index.Time = datetime.strptime(data[0], "%Y-%m-%d")
            index.Value = Decimal(data[5])
        except:
            return None
            
        return index

class SymbolData:
    '''Contains data specific to a symbol required by this model'''
    
    def __init__(self, symbol, lookback):
        self.symbol = symbol
        self.MOM = Momentum(lookback)