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.

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 Return10.5%
Volatility6.5%
Beta0.073
Sharpe Ratio1.62
Sortino Ratio0.012
Maximum DrawdownN/A
Win Rate55%

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

Leave a Reply

Discover more from Quant Buffet

Subscribe now to keep reading and get access to the full archive.

Continue reading