Quant BuffetRelax, Not Over Thinking

Effect of Change in Non-Current Operating Assets

Log in to collect

Academic paper

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.

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.

Backtest performance

Annualised return16.1%
Volatility7.26%
Beta-0.056
Sharpe ratio1.67
Sortino ratio-0.145
Win rate52%

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"))