
“Universe: NYSE, Amex, NASDAQ stocks. Sorted by size and investment independently. Intersected to form 25 portfolios. Long highest size, lowest investment; short highest size, highest investment. Value-weighted.”
ASSET CLASS: stocks | REGION: United States | FREQUENCY: Yearly | MARKET: equities | KEYWORD: Investment
I. STRATEGY IN A NUTSHELL
The investment universe consists of all NYSE, Amex, and NASDAQ stocks. Firstly, stocks are allocated to five Size groups (Small to Big) at the end of each June using NYSE market cap breakpoints. Stocks are allocated independently to five Investment (Inv) groups (Low to High) still using NYSE breakpoints. The intersections of the two sorts produce 25 Size-Inv portfolios. For portfolios formed in June of year t, Inv is the growth of total assets for the fiscal year ending in t-1 divided by total assets at the end of t-1. Long portfolio with the highest Size and simultaneously with the lowest Investment. Short portfolio with the highest Size and simultaneously with the highest Investment. The portfolios are value-weighted.
II. ECONOMIC RATIONALE
Past data and research proved that a conservative investment portfolio is connected with greater returns compared to an aggressive investment portfolio. To explain it briefly, aggressive investments do not improve returns in the near future, and yet it is questioned if those investments would improve returns in the future. Moreover, looking at the Size-Inv portfolios, estimates suggest that the five-factor model leaves only around 28% of the cross-section variance of expected returns unexplained. This is far less than the variance ratios produced by the Fama-French three-factor model, which are mostly greater than 50% for the Size-Inv portfolio. As a result, the investment factor could and should be used by investors.
III. SOURCE PAPER
A Five-Factor Asset Pricing Model [Click to Open PDF]
- Eugene F. Fama, University of Chicago – Finance
- Kenneth R. French, Dartmouth College – Tuck School of Business; National Bureau of Economic Research (NBER)
<Abstract>
A five-factor model directed at capturing the size, value, profitability, and investment patterns in average stock returns performs better than the three-factor model of Fama and French (FF 1993). The five-factor model’s main problem is its failure to capture the low average returns on small stocks whose returns behave like those of firms that invest a lot despite low profitability. The model’s performance is not sensitive to the way its factors are defined. With the addition of profitability and investment factors, the value factor of the FF three-factor model becomes redundant for describing average returns in the sample we examine.

IV. BACKTEST PERFORMANCE
| Annualised Return | 3.54% |
| Volatility | N/A |
| Beta | 0.034 |
| Sharpe Ratio | 0.363 |
| Sortino Ratio | 0.385 |
| Maximum Drawdown | 18.9% |
| Win Rate | 52% |
V. FULL PYTHON CODE
from AlgoLib import *
class InvestmentFactor(XXX):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbol:Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.fundamental_count:int = 3000
self.fundamental_sorting_key = lambda x: x.MarketCap
self.long:List[Symbol] = []
self.short:List[Symbol] = []
self.quantile:int = 5
self.leverage:int = 3
self.rebalance_month:int = 6
self.min_share_price:float = 1.
self.weight:Dict[Symbol, float] = {}
self.selection_flag:bool = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.FundamentalSelectionFunction)
self.Settings.MinimumOrderMarginPortfolioPercentage = 0
self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Selection)
def OnSecuritiesChanged(self, changes: SecurityChanges) -> None:
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(self.leverage)
def FundamentalSelectionFunction(self, fundamental: List[Fundamental]) -> List[Symbol]:
if not self.selection_flag:
return Universe.Unchanged
selected:List[Fundamental] = [x for x in fundamental if x.HasFundamentalData and x.Market == 'usa' and x.MarketCap != 0 and x.AdjustedPrice >= self.min_share_price and \
((x.SecurityReference.ExchangeId == "NYS") or (x.SecurityReference.ExchangeId == "NAS") or (x.SecurityReference.ExchangeId == "ASE")) and \
not np.isnan(x.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths) and not np.isnan(x.OperationRatios.TotalAssetsGrowth.OneYear) and \
x.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths != 0 and x.OperationRatios.TotalAssetsGrowth.OneYear != 0
]
if len(selected) > self.fundamental_count:
selected = [x for x in sorted(selected, key=self.fundamental_sorting_key, reverse=True)[:self.fundamental_count]]
# Sorting by investment factor.
sorted_by_inv_factor:List[Fundamental] = sorted(selected, key = lambda x: (x.OperationRatios.TotalAssetsGrowth.OneYear / x.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths), reverse=True)
if len(sorted_by_inv_factor) >= self.quantile:
quintile:int = int(len(sorted_by_inv_factor) / self.quantile)
self.long = [x.Symbol for x in sorted_by_inv_factor[-quintile:]]
self.short = [x.Symbol for x in sorted_by_inv_factor[:quintile]]
return self.long + self.short
def OnData(self, data: Slice) -> None:
if not self.selection_flag:
return
self.selection_flag = False
# order execution
targets:List[PortfolioTarget] = []
for i, portfolio in enumerate([self.long, self.short]):
for symbol in portfolio:
if symbol in data and data[symbol]:
targets.append(PortfolioTarget(symbol, ((-1) ** i) / len(portfolio)))
self.SetHoldings(targets, True)
self.long.clear()
self.short.clear()
def Selection(self) -> None:
if self.Time.month == self.rebalance_month:
self.selection_flag = True
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))