
“Universe: NYSE, NASDAQ, AMEX stocks. In April, compute 5-year R&D expenditure to Market Cap ratio. Long (short) highest (lowest) quintile. Equal-weighted, annual rebalance. Utilized Quantconnect for recent backtest.”
ASSET CLASS: stocks | REGION: United States | FREQUENCY: Yearly | MARKET:
equities | KEYWORD: R&D
I. STRATEGY IN A NUTSHELL
The investment universe consists of stocks that are listed on NYSE NASDAQ or AMEX. At the end of April, for each stock in the universe, calculate a measure of total R&D expenditures in the past 5 years scaled by the firm’s Market cap (defined on page 7, eq. 1). Go long (short) on the quintile of firms with the highest (lowest) R&D expenditures relative to their Market Cap. Weight the portfolio equally and rebalance next year. The backtested performance of the paper is substituted by our more recent backtest in Quantconnect.
II. ECONOMIC RATIONALE
Under the efficient market hypothesis, the investor should be able to recognize the value of less-tangible assets. However, in conditions of an inefficient market, the presence of such intangible assets could possibly lead to mispricing. One of the reasons for possible mispricing lies in the US GAAP and IFRS accounting standards. Under these standards, the costs of R&D must be expensed in the same fiscal year as they occur and therefore could significantly influence the reported earnings of a company in the current year. However, the R&D expenditures usually represent a long-term investment that implies a possible future revenue and cash flow.
III. SOURCE PAPER
The Stock Market Valuation of Research and Development Expenditures [Click to Open PDF]
- Louis K.C. Chan, University of Illinois at Urbana-Champaign – Department of Finance
- Josef Lakonishok, University of Illinois at Urbana-Champaign; National Bureau of Economic Research (NBER)
- Theodore Sougiannis, University of Illinois at Urbana-Champaign – Department of Accountancy
<Abstract>
We examine whether stock prices fully reflect the value of firms’ intangible assets, focusing on research and development (R&D). Since intangible assets are not reported on financial statements under current U.S. accounting standards and R&D spending is expensed, the valuation problem may be especially challenging. Nonetheless we find that historically the stock returns of firms doing R&D on average matches the returns on firms with no R&D. For companies engaged in R&D, high R&D intensity has a distinctive effect on returns for two groups of stocks. Within the set of growth stocks, R&D-intensive stocks tend to out-perform stocks with little or no R&D. Companies with high R&D relative to equity market value (who tend to have poor past returns) show strong signs of mis-pricing. In both cases the market apparently fails to give sufficient credit for firms’ R&D investments. Our exploratory investigation of the effects of advertising on returns yields similar results. We also provide evidence that R&D intensity is positively associated with return volatility, everything else equal. Insofar as the association reflects investors’ lack of information about firms’ R&D activity, increased accounting disclosure may be beneficial.

IV. BACKTEST PERFORMANCE
| Annualised Return | 4.67% |
| Volatility | 8.23% |
| Beta | 0.041 |
| Sharpe Ratio | 0.34 |
| Sortino Ratio | 0.33 |
| Maximum Drawdown | 49.88% |
| Win Rate | 45% |
V. FULL PYTHON CODE
from AlgoLib import *
from numpy import log, average
from scipy import stats
import numpy as np
#endregion
class RDExpendituresandStockReturns(XXX):
def Initialize(self) -> None:
self.SetStartDate(1998, 1, 1)
self.SetCash(100000)
self.fundamental_count:int = 500
self.fundamental_sorting_key = lambda x: x.DollarVolume
self.rebalance_month:int = 4
self.quantile:int = 5
self.leverage:int = 5
self.min_share_price:float = 5.
self.exchange_codes:List[str] = ['NYS', 'NAS', 'ASE']
# R&D history.
self.RD:Dict[Symbol, float] = {}
self.rd_period:int = 5
self.long:List[Symbol] = []
self.short:List[Symbol] = []
data:Equity = self.AddEquity('XLK', Resolution.Daily)
data.SetLeverage(self.leverage)
self.technology_sector:Symbol = data.Symbol
market:Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.selection_flag:bool = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.FundamentalSelectionFunction)
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
self.Schedule.On(self.DateRules.MonthEnd(market), self.TimeRules.AfterMarketOpen(market), 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.AdjustedPrice > self.min_share_price and x.SecurityReference.ExchangeId in self.exchange_codes and x.MarketCap != 0 and \
not np.isnan(x.FinancialStatements.IncomeStatement.ResearchAndDevelopment.TwelveMonths) and x.FinancialStatements.IncomeStatement.ResearchAndDevelopment.TwelveMonths != 0
]
if len(selected) > self.fundamental_count:
selected = [x for x in sorted(selected, key=self.fundamental_sorting_key, reverse=True)[:self.fundamental_count]]
selected_symbols:List[Symbol] = list(map(lambda x: x.Symbol, selected))
ability:Dict[Fundamental, float] = {}
updated_flag:List[Symbol] = [] # updated this year already
for stock in selected:
symbol:Symbol = stock.Symbol
# prevent storing duplicated value for the same stock in one year
if symbol not in updated_flag:
# Update RD.
if symbol not in self.RD:
self.RD[symbol] = RollingWindow[float](self.rd_period)
if self.RD[symbol].IsReady:
coefs:np.ndarray = np.array([1, 0.8, 0.6, 0.4, 0.2])
rds:np.ndarray = np.array([x for x in self.RD[symbol]])
rdc:float = sum(coefs * rds)
ability[stock] = rdc / stock.MarketCap
rd:float = stock.FinancialStatements.IncomeStatement.ResearchAndDevelopment.TwelveMonths
self.RD[symbol].Add(rd)
# prevent storing duplicated value for the same stock in one year
if selected_symbols.count(symbol) > 1:
updated_flag.append(symbol)
# Remove not updated symbols
symbols_to_delete:List[Symbol] = []
for symbol in self.RD.keys():
if symbol not in selected_symbols:
symbols_to_delete.append(symbol)
for symbol in symbols_to_delete:
if symbol in self.RD:
del self.RD[symbol]
# starts trading after data storing period
if len(ability) >= self.quantile:
# Ability sorting.
sorted_by_ability:List = sorted(ability.items(), key = lambda x: x[1], reverse = True)
quantile:int = int(len(sorted_by_ability) / self.quantile)
high_by_ability:List[Symbol] = [x[0].Symbol for x in sorted_by_ability[:quantile]]
low_by_ability:List[Symbol] = [x[0].Symbol for x in sorted_by_ability[-quantile:]]
self.long = high_by_ability
self.short = low_by_ability
return self.long + self.short
def Selection(self) -> None:
if self.Time.month == self.rebalance_month:
self.selection_flag = True
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()
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))