Quant BuffetRelax, Not Over Thinking

Cash-Based Operating Profitability

Log in to collect

Academic paper

Accruals, Cash Flows, and Operating Profitability in the Cross Section of Stock Returns

AuthorsRay Ball; Joseph Gerakos; Juhani T. Linnainmaa; Valeri V. Nikolaev

Institute
  • University of Chicago
  • ?University of Chicago - Booth School of Business
  • Dartmouth College
  • ?Tuck School of Business at Dartmouth College
  • National Bureau of Economic Research
  • ?Dartmouth College - Tuck School of Business
  • ?National Bureau of Economic Research (NBER)
  • ?University of Chicago Booth School of Business

Strategy in a nutshell

Targets large-cap U.S. stocks, ranking them by cash-based operating profitability and going long on the top decile and short on the bottom decile. Portfolios are value-weighted and rebalanced annually to capture firm performance and optimize returns.

Economic rationale

Cash-based operating profitability outperforms accrual-inclusive measures in predicting returns, explains the accrual anomaly, and provides robust long-term return forecasts, reflecting gradual market adjustment or shared economic risks.

Backtest performance

Annualised return5%
Volatility11%
Beta0.069
Sharpe ratio0.45
Sortino ratio0.17
Win rate63%

Full Python code

from AlgorithmImports import *
from functools import reduce
from numpy import isnan
class CashBasedOperatingProfitability(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2005, 1, 1)
self.SetCash(100000)

self.weight:Dict[Symbol, float] = {}
self.fundamental_count:int = 3000
self.fundamental_sorting_key = lambda x: x.MarketCap
self.rebalance_month:int = 12
self.quantile:int = 10
self.leverage:int = 5
self.min_share_price:float = 5.
self.exchange_codes:List[str] = ['NYS', 'NAS', 'ASE']
self.financial_statement_names:List[str] = [
    'FinancialStatements.IncomeStatement.TotalRevenue.TwelveMonths',
    'FinancialStatements.IncomeStatement.SellingAndMarketingExpense.TwelveMonths',
    'FinancialStatements.IncomeStatement.CostOfRevenue.TwelveMonths',
    'FinancialStatements.IncomeStatement.GeneralAndAdministrativeExpense.TwelveMonths',
]
self.month:int = -1
self.rebalance_flag:bool = False
self.UniverseSettings.Resolution = Resolution.Daily
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
self.settings.daily_precise_end_time = False
self.AddUniverse(self.FundamentalSelectionFunction)

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 self.month == self.Time.month:
    return Universe.Unchanged
else:
    self.month = self.Time.month
    if self.Time.month != self.rebalance_month:
        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 \
    all((not isnan(self.rgetattr(x, statement_name)) and self.rgetattr(x, statement_name) != 0) for statement_name in self.financial_statement_names)
]
if len(selected) > self.fundamental_count:
    selected = [x for x in sorted(selected, key=self.fundamental_sorting_key, reverse=True)[:self.fundamental_count]]

# CBOP calculation
cbop:Dict[Fundamental, float] = { stock : (stock.FinancialStatements.IncomeStatement.TotalRevenue.TwelveMonths - stock.FinancialStatements.IncomeStatement.SellingAndMarketingExpense.TwelveMonths -
            stock.FinancialStatements.IncomeStatement.CostOfRevenue.TwelveMonths - stock.FinancialStatements.IncomeStatement.GeneralAndAdministrativeExpense.TwelveMonths)
            for stock in selected}

# Sorting by market cap and cbop.
if len(cbop) >= self.quantile * 2:
    top_market_cap:List = sorted(cbop.items(), key = lambda x: x[0].MarketCap, reverse=True)[:int(len(cbop) / 2)]
    sorted_by_cbop:List = sorted(top_market_cap, key = lambda x:x[1], reverse=True)
    quantile:int = int(len(sorted_by_cbop) / self.quantile)
    long:List[Fundamental] =  [x[0] for x in sorted_by_cbop[:quantile]]
    short:List[Fundamental] = [x[0] for x in sorted_by_cbop[-quantile:]]
        
    # Market cap weighting.
    for i, portfolio in enumerate([long, short]):
        mc_sum:float = sum(map(lambda x: x.MarketCap, portfolio))
        for stock in portfolio:
            self.weight[stock.Symbol] = ((-1) ** i) * stock.MarketCap / mc_sum

self.rebalance_flag = True
return list(self.weight.keys())

def OnData(self, data: Slice) -> None:
if not self.rebalance_flag:
    return
self.rebalance_flag = False

# Trade execution.
portfolio:List[PortfolioTarget] = [PortfolioTarget(symbol, w) for symbol, w in self.weight.items() if symbol in data and data[symbol]]
self.SetHoldings(portfolio, True)
self.weight.clear()
# https://gist.github.com/wonderbeyond/d293e7a2af1de4873f2d757edd580288
def rgetattr(self, obj, attr, *args):
def _getattr(obj, attr):
    return getattr(obj, attr, *args)
return reduce(_getattr, [obj] + attr.split('.'))
# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))