Overall Statistics
Total Trades
4069
Average Win
0.45%
Average Loss
-0.42%
Compounding Annual Return
3.817%
Drawdown
39.400%
Expectancy
0.071
Net Profit
73.243%
Sharpe Ratio
0.392
Loss Rate
48%
Win Rate
52%
Profit-Loss Ratio
1.06
Alpha
0.05
Beta
-0.096
Annual Standard Deviation
0.106
Annual Variance
0.011
Information Ratio
-0.231
Tracking Error
0.222
Treynor Ratio
-0.431
Total Fees
$7544.00
 
 
# https://quantpedia.com/strategies/momentum-factor-combined-with-asset-growth-effect/
#
# The investment universe consists of NYSE, AMEX and NASDAQ stocks (data for the backtest in the source paper are from Compustat). 
# Stocks with a market capitalization less than the 20th NYSE percentile (smallest stocks) are removed. The asset growth variable 
# is defined as the yearly percentage change in balance sheet total assets. Data from year t-2 to t-1 are used to calculate asset
# growth, and July is the cut-off month. Every month, stocks are then sorted into deciles based on asset growth and only stocks 
# with the highest asset growth are used. The next step is to sort stocks from the highest asset growth decile into quintiles, 
# based on their past 11-month return (with the last month’s performance skipped in the calculation). The investor then goes long
# on stocks with the strongest momentum and short on stocks with the weakest momentum. The portfolio is equally weighted and is
# rebalanced monthly. The investor holds long-short portfolios only during February-December -> January is excluded as this month
# has been repeatedly documented as a negative month for a momentum strategy (see “January Effect Filter and Momentum in Stocks”).

import numpy as np
from collections import deque

class Momentum_Factor_Asset_Growth_Effect(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2005, 1, 1)
        self.SetEndDate(2019, 9, 1)
        self.SetCash(100000)

        self.last_course_year = -1
        self.last_traded_month = -1
        self.course_count = 500
        
        self.total_assets_history_period = 2
        self.total_assets = {}
        self.top_by_growth = []

        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
        
    def CoarseSelectionFunction(self, coarse):
        if self.last_course_year == self.Time.year or self.Time.month != 7:
            return Universe.Unchanged
        self.last_course_year = self.Time.year

        selected = sorted([x for x in coarse if x.HasFundamentalData and x.Price > 5],
            key=lambda x: x.DollarVolume, reverse=True)
        
        #volumes =  [x.DollarVolume for x in selected]
        #percentile = np.percentile(volumes, 20)
        #return [x.Symbol for x in selected if x.DollarVolume > percentile]
        
        return [x.Symbol for x in selected[:self.course_count]]

    def FineSelectionFunction(self, fine):
        selected = [x for x in fine if x.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths > 0]
        
        asset_growth = {}
        for stock in selected:
            symbol = stock.Symbol
            if not symbol in self.total_assets:
                self.total_assets[symbol] = deque(maxlen = self.total_assets_history_period)

            if len(self.total_assets[symbol]) == self.total_assets_history_period:
                values = [x for x in self.total_assets[symbol]]
                asset_growth[symbol] = (values[1] - values[0]) / values[0]
            
            self.total_assets[symbol].append(stock.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths)
        
        sorted_by_growth = sorted(asset_growth.items(), key = lambda x: x[1], reverse = True)
        sorted_by_growth = [x[0] for x in sorted_by_growth]
        
        decile = int(len(sorted_by_growth) / 10)
        
        self.top_by_growth = sorted_by_growth[:decile]
        
        return self.top_by_growth

    def OnData(self, data):
        if self.last_traded_month == self.Time.month:
            return
        
        if self.Time.month == 1: 
            if self.Portfolio.Invested:
                self.Liquidate()
            return
        
        returns = {}
        lookup_period = 11*21
        for symbol in self.top_by_growth:
            hist = self.History([symbol], lookup_period, Resolution.Daily)
            if 'close' in hist.columns:
                hist = hist['close']
                if len(hist) == lookup_period:
                    # Return calculation
                    hist = hist[:lookup_period - 21]
                    returns[symbol] = (hist[-1] - hist[0]) / hist[0]
                    
        sorted_by_ret = sorted(returns.items(), key = lambda x: x[1], reverse = True)
        sorted_by_ret = [x[0] for x in sorted_by_ret]

        quintile  = int(len(sorted_by_ret) / 5)
        
        long = sorted_by_ret[:quintile]
        short = sorted_by_ret[-quintile:]

        self.Liquidate()

        count = len(long) + len(short)
        if count == 0: return
        
        for symbol in long:
            self.SetHoldings(symbol, 1/count)
        for symbol in short:
            self.SetHoldings(symbol, -1/count)
        
        self.last_traded_month = self.Time.month