Quant BuffetRelax, Not Over Thinking

Accrual Effect in Family Firms

Log in to collect

Academic paper

Strategy in a nutshell

The strategy focuses on family firms across 34 global markets, using accrual-based sorting. It goes long low-accrual family firms and shorts high-accrual ones, with yearly rebalancing.

Economic rationale

Accrual anomalies in family firms arise from earnings management, uncertainty, and limits to arbitrage. Mispricing persists, offering profitable long-short opportunities not seen in non-family firms.

Backtest performance

Annualised return18.7%
Beta-0.001
Win rate50%

Full Python code

from AlgorithmImports import *
from typing import Dict, List
from data_tools import SymbolData, CustomFeeModel
# endregion

class AccrualEffectInFamilyFirms(QCAlgorithm):

def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)

self.leverage:int = 5
self.quantile:int = 5

self.max_missing_days:int = 356 + 10

self.data:Dict[Symbol, SymbolData] = {}
self.weights:Dict[Symbol, float] = {}

family_businesses_csv:str = self.Download('data.quantpedia.com/backtesting_data/economic/family_businesses_tickers.csv').replace('\r\n', '')
self.tickers:List[str] = family_businesses_csv.split(';')

self.market:Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol

self.selection_month:int = 6
self.selection_flag:bool = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)

self.Schedule.On(self.DateRules.MonthEnd(self.market), self.TimeRules.BeforeMarketClose(self.market, 0), self.Selection)

def OnSecuritiesChanged(self, changes:SecurityChanges) -> None:
for security in changes.AddedSecurities:
    security.SetFeeModel(CustomFeeModel())
    security.SetLeverage(self.leverage)

def CoarseSelectionFunction(self, coarse:List[CoarseFundamental]) -> List[Symbol]:
if not self.selection_flag:
    return Universe.Unchanged

selected_symbols:List[CoarseFundamental] = [x.Symbol for x in coarse if x.Symbol.Value in self.tickers]

return selected_symbols

def FineSelectionFunction(self, fine:List[FineFundamental]) -> List[Symbol]:
curr_date:datetime.date = self.Time.date()
total_accurals:Dict[Symbol, float] = {}

for stock in fine:
    if stock.FinancialStatements.BalanceSheet.CurrentAssets.TwelveMonths != 0 and \
        stock.FinancialStatements.BalanceSheet.CurrentLiabilities.TwelveMonths != 0 and \
        stock.FinancialStatements.BalanceSheet.TotalAssets.ThreeMonths != 0:

        symbol:Symbol = stock.Symbol

        operating_assets:float = stock.FinancialStatements.BalanceSheet.CurrentAssets.TwelveMonths
        operating_liabilities:float = stock.FinancialStatements.BalanceSheet.CurrentLiabilities.TwelveMonths

        net_operating_assets:float = operating_assets - operating_liabilities

        if symbol not in self.data:
            self.data[symbol] = SymbolData()

        if not self.data[symbol].net_operating_assets_data_coming(curr_date, self.max_missing_days):
            self.data[symbol].reset()

        if self.data[symbol].net_operating_assets_ready():
            total_assets:float = stock.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths
            net_operating_assets_change:float = self.data[symbol].get_net_operating_assets_change(net_operating_assets)
            total_accurals_value:float = net_operating_assets_change / total_assets

            total_accurals[symbol] = total_accurals_value

        self.data[symbol].set_net_operating_assets(curr_date, net_operating_assets)

# make sure there are enough stocks for selection
if len(total_accurals) < self.quantile:
    return Universe.Unchanged

quantile:int = int(len(total_accurals) / self.quantile)
sorted_by_total_accs:List[Symbol] = [x[0] for x in sorted(total_accurals.items(), key=lambda item: item[1])]

# long companies with low total accurals and short companies with high total accs
long_leg:List[float] = sorted_by_total_accs[:quantile]
short_leg:List[float] = sorted_by_total_accs[-quantile:]

for symbol in long_leg:
    self.weights[symbol] = 1 / quantile

for symbol in short_leg:
    self.weights[symbol] = -1 / quantile

return list(self.weights.keys())  

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

# trade execution
invested:List[Symbol] = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
    if symbol not in self.weights:
        self.Liquidate(symbol)
        
for symbol, w in self.weights.items():
    self.SetHoldings(symbol, w)
        
self.weights.clear()

def Selection(self):
if self.selection_month == self.Time.month:
    self.selection_flag = True