
The investment universe consists of family firms from 34 international markets. To identify family firms, investor can use firms available in the NRG Metrics family ownership database. NRG Metrics is a unique database that provides a wide range of refined measures of family ownership sourced by publicly available documents such as annual reports, corporate governance reports, firm presentations, SEC filings, and press releases.
ASSET CLASS: stocks | REGION: Global | FREQUENCY:
Yearly | MARKET: equities | KEYWORD: Accrual Effect, Family Firms
I. 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.
II. 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.
III. SOURCE PAPER
Papanastasopoulos: Family Ownership and the Accrual Anomaly [Click to Open PDF]
Adam Aoun, Leonidas C. Doukakis, and Georgios A. Papanastasopoulos
EHL Lausanne; HES-SO, University of Applied Sciences Western Switzerland; Athens University of Economics and Business; University of Piraeus – Department of Business Administration
<Abstract>
Motivated by the unique nature of family firms and the puzzling persistence of the accrual anomaly worldwide, we study the presence and economic significance of the accrual anomaly separately for family and non-family firms using a sample of 27,117 observations from 34 capital markets. At an individual stock level of analysis, we show that the negative relation of accruals with future earnings performance and stock returns is more pronounced within family firms, while it is highly attenuated within non-family firms. Evidence from portfolio-level analysis summarizes the economic significance of this finding. Overall, we conclude that agency problems, information uncertainty, and barriers to arbitrage could be key explanatory factors regarding the accrual anomaly’s occurrence and persistence within family firms.


IV. BACKTEST PERFORMANCE
| Annualised Return | 18.7% |
| Volatility | N/A |
| Beta | -0.001 |
| Sharpe Ratio | N/A |
| Sortino Ratio | N/A |
| Maximum Drawdown | N/A |
| Win Rate | 50% |
V. 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