
The strategy trades non-financial AMEX, NYSE, and NASDAQ stocks, going long on largest decreases in Non-Current Operating Assets, short on largest increases, with annual rebalancing and equal weighting.
ASSET CLASS: stocks | REGION: United States | FREQUENCY:
Yearly | MARKET: equities | KEYWORD: Effect of Change , Non-Current ,Operating Assets
I. STRATEGY IN A NUTSHELL: Annual U.S. Equity Non-Current Operating Assets Strategy
This annual strategy targets non-financial U.S. stocks on AMEX, NYSE, and NASDAQ. It ranks stocks by the one-year change in Non-Current Operating Assets (ΔNCOA = Total Assets − Current Assets − Investments & Advances). A zero-investment portfolio is formed by going long on stocks with the most negative change (largest decrease or lowest increase) and shorting those with the most positive change (largest increase). Positions are equally weighted and rebalanced annually, aiming to exploit operational asset management shifts.
II. ECONOMIC RATIONALE
Non-current operating assets primarily include property, plant, equipment, and intangibles, which carry significant accrual uncertainty. Firms with low levels of NCOA and negative changes tend to outperform those with large increases, reflecting superior efficiency or disciplined operational management.
III. SOURCE PAPER
Accrual Reliability, Earnings Persistence and Stock Prices[Click to Open PDF]
Scott A. Richardson, London Business School; Richard G. Sloan, Acadian Asset Management
<Abstract>
This paper extends the work of Sloan (1996) by linking accrual reliability to earnings persistence. We construct a model showing that less reliable accruals lead to lower earnings persistence. We then develop a comprehensive balance sheet categorization of accruals and rate each category according to the reliability of the underlying accruals. Empirical tests generally confirm that less reliable categories of accruals lead to lower earnings persistence and that investors do not fully anticipate the lower earnings persistence, leading to significant security mispricing. We conclude that there are significant costs associated with the recognition of unreliable information in financial statements.


IV. BACKTEST PERFORMANCE
| Annualised Return | 16.1% |
| Volatility | 7.26% |
| Beta | -0.056 |
| Sharpe Ratio | 1.67 |
| Sortino Ratio | -0.145 |
| Maximum Drawdown | N/A |
| Win Rate | 52% |
V. FULL PYTHON CODE
from AlgorithmImports import *
class EffectofChangeinNonCurrentOperatingAssets(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetCash(100000)
market:Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.long:List[Symbol] = []
self.short:List[Symbol] = []
# Non-Current Operating Assets.
self.operating_assets:Dict[Symbol, float] = {}
self.quantile:int = 10
self.leverage:int = 5
self.rebalance_month:int = 12
self.min_share_price:float = 5.
self.exchange_codes:List[str] = ['NYS', 'NAS', 'ASE']
self.fundamental_count:int = 500
self.fundamental_sorting_key = lambda x: x.DollarVolume
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)
self.settings.daily_precise_end_time = False
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.Price >= self.min_share_price and x.Market == 'usa' and x.SecurityReference.ExchangeId in self.exchange_codes and \
not np.isnan(x.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths) and x.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths != 0 and \
not np.isnan(x.FinancialStatements.BalanceSheet.CurrentAssets.TwelveMonths) and x.FinancialStatements.BalanceSheet.CurrentAssets.TwelveMonths != 0 and \
not np.isnan(x.FinancialStatements.BalanceSheet.InvestmentsAndAdvances.TwelveMonths) and x.FinancialStatements.BalanceSheet.InvestmentsAndAdvances.TwelveMonths != 0 and \
x.AssetClassification.MorningstarSectorCode != MorningstarSectorCode.FinancialServices
]
if len(selected) > self.fundamental_count:
selected = [x for x in sorted(selected, key=self.fundamental_sorting_key, reverse=True)[:self.fundamental_count]]
d_assets:Dict[Symbol, float] = {}
for stock in selected:
symbol:Symbol = stock.Symbol
if symbol not in self.operating_assets:
self.operating_assets[symbol] = -1.
assets:float = stock.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths - \
stock.FinancialStatements.BalanceSheet.CurrentAssets.TwelveMonths - \
stock.FinancialStatements.BalanceSheet.InvestmentsAndAdvances.TwelveMonths
if symbol in self.operating_assets and self.operating_assets[symbol] not in [-1, 0]:
d_assets[symbol] = assets / self.operating_assets[symbol] - 1
# Update assets value.
self.operating_assets[symbol] = assets
# NOTE: Get rid of old advertisment records so we work with latest values.
for symbol in self.operating_assets:
if symbol not in [x.Symbol for x in selected]:
self.operating_assets[symbol] = -1
if len(d_assets) >= self.quantile:
sorted_by_assets:List = sorted(d_assets.items(), key = lambda x: x[1], reverse = True)
quantile:int = int(len(sorted_by_assets) / self.quantile)
self.long = [x[0] for x in sorted_by_assets[-quantile:]]
self.short = [x[0] for x in sorted_by_assets[:quantile]]
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"))
VI. Backtest Performance