Overall Statistics
Total Trades
3275
Average Win
0.44%
Average Loss
-0.72%
Compounding Annual Return
0%
Drawdown
103.500%
Expectancy
-0.234
Net Profit
-103.758%
Sharpe Ratio
-0.594
Probabilistic Sharpe Ratio
0%
Loss Rate
53%
Win Rate
47%
Profit-Loss Ratio
0.62
Alpha
-0.351
Beta
-0.05
Annual Standard Deviation
0.599
Annual Variance
0.359
Information Ratio
-0.737
Tracking Error
0.613
Treynor Ratio
7.089
Total Fees
$4788.07
Estimated Strategy Capacity
$24000.00
Lowest Capacity Asset
SIEB R735QTJ8XC9X
# region imports
from AlgorithmImports import *
# endregion

# https://quantpedia.com/Screener/Details/54
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
        self.wilshire_symbol = self.AddData(Fred, Fred.Wilshire.Price5000, Resolution.Daily).Symbol
        self.W5000Return = self.ROC(self.wilshire_symbol, 252)
        # initialize the RateOfChange indicator of Wilshire 5000 total market index
        history = self.History(self.wilshire_symbol, 500, Resolution.Daily)
        for tuple in history.loc[self.wilshire_symbol].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.UniverseSettings.Resolution = 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.momp = {}
        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.momp:
                self.momp[i.Symbol] = SymbolData(i.Symbol, self.lookback, self)
            else:
                self.momp[i.Symbol].MOMP.Update(self.Time, i.AdjustedPrice)
        
        if self.month_start:
            self.selection = True
            self.MOMPReady = {symbol: SymbolData for symbol, SymbolData in self.momp.items() if SymbolData.MOMP.IsReady}
            if self.MOMPReady:
                # sort stocks by 6-month momentum
                sortByMOMP = sorted(self.MOMPReady, key = lambda x: self.MOMPReady[x].MOMP.Current.Value, reverse = True)
                self.long = sortByMOMP[:20]
                self.short = sortByMOMP[-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 = [symbol for symbol in self.short if symbol in data.Bars]
                short_weight = 0.5/len(short)
                # goes short on the prior six-month losers (lowest decile) 
                for short_symbol in short:
                    self.SetHoldings(short_symbol, -short_weight)
                    
                # goes long on the prior six-month winners (highest decile)
                long = [symbol for symbol in self.long if symbol in data.Bars]
                long_weight = 0.5/len(long)
                for long_symbol in long:
                    self.SetHoldings(long_symbol, long_weight)
            else:
                self.Liquidate()
                self.SetHoldings(self.tlt, 1)

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

        trade_bars = algorithm.History[TradeBar](symbol, lookback, Resolution.Daily)
        for trade_bar in trade_bars:
            self.MOMP.Update(trade_bar.EndTime, trade_bar.Close)