Overall Statistics
Total Trades
391
Average Win
1.05%
Average Loss
-1.19%
Compounding Annual Return
12.920%
Drawdown
20.100%
Expectancy
0.312
Net Profit
71.079%
Sharpe Ratio
0.725
Loss Rate
30%
Win Rate
70%
Profit-Loss Ratio
0.88
Alpha
0.376
Beta
-13.214
Annual Standard Deviation
0.178
Annual Variance
0.032
Information Ratio
0.621
Tracking Error
0.178
Treynor Ratio
-0.01
Total Fees
$802.96
# https://quantpedia.com/Screener/Details/14
from QuantConnect.Data.UniverseSelection import *
import math
import numpy as np
import pandas as pd
import scipy as sp

class MomentumEffectAlgorithm(QCAlgorithm):

    def Initialize(self):

        self.SetStartDate(2014, 1, 1)  # Set Start Date
        self.SetEndDate(2018, 6, 1)    # Set Start Date       
        self.SetCash(100000)           # Set Strategy Cash
        self.lookback = 21*12
    
        self.UniverseSettings.Resolution = Resolution.Daily
        self.num_coarse = 100
        self.num_fine = 50
        self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
        self.symbolDataDict = {}
        self.AddEquity("SPY", Resolution.Daily)
        # rebalance the portfolio every month
        self.Schedule.On(self.DateRules.MonthStart("SPY"),self.TimeRules.AfterMarketOpen("SPY"), self.rebalance)
        self.monthly_rebalance = True
    def CoarseSelectionFunction(self, coarse):
        if self.monthly_rebalance:
            # drop stocks which have no fundamental data or have too low prices
            selected = [x for x in coarse if (x.HasFundamentalData) and (float(x.Price) > 5)]
            # rank the stocks by dollar volume 
            filtered = sorted(selected, key=lambda x: x.DollarVolume, reverse=True) 
            self.filtered_coarse = [ x.Symbol for x in filtered[:self.num_coarse]]
            return self.filtered_coarse
        else: 
            return self.filtered_coarse
        

    def FineSelectionFunction(self, fine):
        if self.monthly_rebalance:
            filtered_fine = [x for x in fine if x.EarningReports.BasicEPS.TwelveMonths > 0
                                                and x.ValuationRatios.PERatio > 0
                                                and x.EarningReports.BasicAverageShares.ThreeMonths > 0]
            # filter stocks with the top market cap
            top = sorted(filtered_fine, key = lambda x: x.EarningReports.BasicAverageShares.ThreeMonths * (x.EarningReports.BasicEPS.TwelveMonths*x.ValuationRatios.PERatio), reverse=True)[:self.num_fine]
            self.filtered_fine = [x.Symbol for x in top]
            self.monthly_rebalance = False
            return self.filtered_fine
        else:
            return self.filtered_fine

    def rebalance(self):
        self.monthly_rebalance = True


    def OnData(self, data):
        for symbol, symbolData in self.symbolDataDict.items():
            # update the indicator value for securities already in the portfolio
            if symbol not in self.addedSymbols:
                symbolData.MOM.Update(IndicatorDataPoint(symbol, self.Time, self.Securities[symbol].Close))
            # liquidate removed securities
            if symbol in self.removedSymbols:
                self.Liquidate(symbol)
            
        self.addedSymbols = []
        self.removedSymbols = []
        
        if self.monthly_rebalance:
            sorted_symbolData = sorted(self.symbolDataDict, key=lambda x: self.symbolDataDict[x].MOM.Current.Value)
            long_stocks = sorted_symbolData[:5]
            for long_stock in long_stocks:
                self.SetHoldings(long_stock, 1/5)
            stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
            # liquidate stocks not in the list 
            for i in stocks_invested:
                if i not in long_stocks:
                    self.Liquidate(i)


    def OnSecuritiesChanged(self, changes):
        # clean up data for removed securities
        self.removedSymbols = [x.Symbol for x in changes.RemovedSecurities]
        for removed in changes.RemovedSecurities:
            symbolData = self.symbolDataDict.pop(removed.Symbol, None)
        # warm up the indicator with history price for newly added securities
        self.addedSymbols = [x.Symbol for x in changes.AddedSecurities if x.Symbol.Value != "SPY"]
        history = self.History(self.addedSymbols, self.lookback+1, Resolution.Daily)

        for symbol in self.addedSymbols:
            if symbol not in self.symbolDataDict.keys():
                symbolData = SymbolData(symbol, self.lookback)
                self.symbolDataDict[symbol] = symbolData
                if str(symbol) in history.index:             
                    symbolData.WarmUpIndicator(history.loc[str(symbol)])


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

    def WarmUpIndicator(self, history):
        # warm up the Momentum indicator with the history request
        for tuple in history.itertuples():
            item = IndicatorDataPoint(self.symbol, tuple.Index, float(tuple.close))
            self.MOM.Update(item)