Overall Statistics
Total Trades
452
Average Win
0.59%
Average Loss
-0.74%
Compounding Annual Return
-8.318%
Drawdown
31.000%
Expectancy
-0.120
Net Profit
-16.463%
Sharpe Ratio
-0.318
Probabilistic Sharpe Ratio
1.657%
Loss Rate
51%
Win Rate
49%
Profit-Loss Ratio
0.80
Alpha
0
Beta
0
Annual Standard Deviation
0.175
Annual Variance
0.031
Information Ratio
-0.318
Tracking Error
0.175
Treynor Ratio
0
Total Fees
$686.89
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from QuantConnect.Data.UniverseSelection import *
from scipy.stats import linregress

class Momentum(QCAlgorithm):

    def __init__(self):
    # set the flag for rebalance
        self.reb = 1
    # Number of stocks to pass CoarseSelection process
        self.num_coarse = 500
    # Number of stocks to long
        self.num_fine = 10
        self.symbols = None
        
    def Initialize(self):
        self.SetStartDate(2018, 8, 1)  # Set Start Date
        self.SetCash(100000)  # Set Strategy Cash
        # set the flag for rebalance
        self.reb = 1
        self.AddUniverse(self.CoarseSelectionFunction,self.FineSelectionFunction)
        # for market state filter
        self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol
        self.Schedule.On(self.DateRules.MonthStart(self.spy), 
        self.TimeRules.AfterMarketOpen(self.spy,5), Action(self.rebalance))
        
    def CoarseSelectionFunction(self, coarse):
    # if the rebalance flag is not 1, return null list to save time.
        if self.reb != 1:
            return self.long 
        
        # make universe selection once a month
        sortedByDollarVolume = sorted(coarse, key=lambda x: x.DollarVolume, reverse=True)  
        filtered = [x.Symbol for x in sortedByDollarVolume if x.HasFundamentalData]
        
        # filtered down to the 500 most liquid stocks
        return filtered[:self.num_coarse]
        
    def FineSelectionFunction(self, fine):
    # return null list if it's not time to rebalance
        if self.reb != 1:
            return self.long 
            
        # drop counter (will update back to 1 after rebalancement has occurred)
        self.reb = 0
        
        # create dictionaries to store the indicator values
        
        stock_filter1 = {}
        
        # sort the fine list by their momentum 
        for security in fine: 
            
            hist = self.History(security.Symbol, timedelta(days=365), Resolution.Daily)
            
            if "close" in hist.columns and len(hist) > 239: 
                hist["log"] = np.log(hist["close"])
                x1 = np.arange(hist["close"].count())
                slope, _, rvalue, _, _ = linregress(x1, hist["log"])
                coeff = slope*252*(rvalue**2)
                        
                # we now have a dictionary storing the values 
                stock_filter1[security.Symbol] = coeff
                
        # we only want the highest values for the coeff
        self.sorted1 = sorted(stock_filter1.items(), key=lambda d:d[1],reverse=True)
        sorted1_symbol = [x[0] for x in self.sorted1]
        
        
        # long the top 10
        self.long = sorted1_symbol[:self.num_fine]
        return self.long

    def OnData(self, data):
        pass
    
    def rebalance(self):
        # at the start of rebalancement, if the stock is no longer in the long list, liquidate
        long_list = self.long
        for i in self.Portfolio.Values:
            if (i.Invested):
                self.Liquidate(i.Symbol)
            
    # Assign each stock equally. Alternatively you can design your own portfolio construction method
        for i in self.long:
            self.SetHoldings(i, 1/self.num_fine)
        
        self.reb = 1