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