
The strategy trades non-financial AMEX, NYSE, and NASDAQ stocks, going long on those with the largest long-term liability increases, short on smallest changes, with annual rebalancing and equal weighting.
ASSET CLASS: stocks | REGION: United States | FREQUENCY:
Yearly | MARKET: equities | KEYWORD: Long Term ,Debt Factor , Stocks
I. STRATEGY IN A NUTSHELL: Annual U.S. Equity Long-Term Debt Change Strategy
This annual strategy targets non-financial U.S. stocks (AMEX, NYSE, NASDAQ). Stocks are ranked by annual changes in long-term financial liabilities, including long-term debt, debt in current liabilities, and preferred stock. A zero-investment portfolio is formed by going long on stocks with the largest increases in liabilities and shorting those with the smallest changes. Positions are equally weighted and rebalanced yearly.
II. ECONOMIC RATIONALE
Financial liabilities reflect future cash obligations discounted at issuance rates, with minimal subjectivity due to restrictions on anticipated non-payments. This objective measurement ensures reliable accruals, making long-term debt changes a credible indicator for assessing financial health and forecasting potential stock price movements.
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; Mark T. Soliman, University of Southern California – Leventhal School of Accounting; Irem Tuna, University of Southern California – Marshall School of Business
<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 | 10.5% |
| Volatility | 6.5% |
| Beta | 0.073 |
| Sharpe Ratio | 1.62 |
| Sortino Ratio | 0.012 |
| Maximum Drawdown | N/A |
| Win Rate | 55% |
V. FULL PYTHON CODE
from AlgorithmImports import *
from typing import Dict, List
import numpy as np
class LongTermDebtFactorWithinStocks(QCAlgorithm):
def Initialize(self) -> None:
self.SetStartDate(2008, 1, 1)
self.SetCash(100_000)
self.UniverseSettings.Leverage = 10
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.FundamentalSelectionFunction)
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.0
self.exchange_codes: List[str] = ['NYS', 'NAS', 'ASE']
self.fundamental_count: int = 3_000
self.fundamental_sorting_key = lambda x: x.MarketCap
self.fin_sector_code: int = 103
self.rebalancing_month: int = 1
self.quantile: int = 10
self.selection_flag: bool = True
self.last_year_liabilities: Dict[Symbol, float] = {}
self.long_symbols: List[Symbol] = []
self.short_symbols: List[Symbol] = []
market: Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.Schedule.On(self.DateRules.MonthStart(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())
for security in changes.RemovedSecurities:
if security.Symbol in self.last_year_liabilities:
del self.last_year_liabilities[security.Symbol]
def FundamentalSelectionFunction(self, fundamental: List[Fundamental]) -> List[Symbol]:
if not self.selection_flag:
return Universe.Unchanged
filtered: List[Fundamental] = [
f for f in fundamental if f.HasFundamentalData
and f.SecurityReference.ExchangeId in self.exchange_codes
and not np.isnan(f.MarketCap)
and f.MarketCap != 0
and not np.isnan(f.FinancialStatements.BalanceSheet.TradingandFinancialLiabilities.TwelveMonths)
and f.FinancialStatements.BalanceSheet.TradingandFinancialLiabilities.TwelveMonths > 0
and f.asset_classification.morningstar_industry_code != self.fin_sector_code
]
sorted_filter: List[Fundamental] = sorted(filtered,
key=self.fundamental_sorting_key,
reverse=True)[:self.fundamental_count]
change_in_liabilities: Dict[Symbol, float] = {}
for f in sorted_filter:
liabilities: float = f.FinancialStatements.BalanceSheet.TradingandFinancialLiabilities.TwelveMonths
if f.Symbol not in self.last_year_liabilities:
self.last_year_liabilities[f.Symbol] = liabilities
continue
change_in_liabilities[f.Symbol] = liabilities / self.last_year_liabilities[f.Symbol] - 1
if len(change_in_liabilities) >= self.quantile:
# Sorting by change in Longterm financial liabilities
sorted_by_liabilities: List = sorted(change_in_liabilities.items(), key=lambda x: x[1], reverse=True)
decile: int = int(len(sorted_by_liabilities) / self.quantile)
self.long_symbols = [x[0] for x in sorted_by_liabilities[:decile]]
self.short_symbols = [x[0] for x in sorted_by_liabilities[-decile:]]
return self.long_symbols + self.short_symbols
def OnData(self, slice: Slice) -> None:
if not self.selection_flag:
return
self.selection_flag = False
# Trade execution
targets: List[PortfolioTarget] = []
for i, portfolio in enumerate([self.long_symbols, self.short_symbols]):
for symbol in portfolio:
if slice.ContainsKey(symbol) and slice[symbol] is not None:
targets.append(PortfolioTarget(symbol, ((-1) ** i) / len(portfolio)))
self.SetHoldings(targets, True)
self.long_symbols.clear()
self.short_symbols.clear()
def Selection(self) -> None:
if self.Time.month == self.rebalancing_month:
self.selection_flag = True
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
VI. Backtest Performance