Net Payout Yield Strategy
Log in to collectAcademic paper
Strategy in a nutshell
The investment universe comprises NYSE, AMEX, and NASDAQ stocks. Annually in June, ten portfolios are created based on net payout yield rankings. Net payout yield is dividends plus repurchases minus share issuances divided by year-end market capitalization. Two measures of payout yield are used: one from cash flow statements, the other from Treasury stock changes. We employ the cash flow-based repurchase measure for net payout yield. The top net payout yield portfolio is purchased and held for a year, then rebalanced.
Economic rationale
Traditionally, dividends were seen as reliable indicators of a stock's health, but recent research challenges this notion, indicating the need for alternative predictors. Previously, high dividend yields signaled undervaluation relative to cash flows returned to shareholders. Adjusting this logic to current conditions, net payout yield incorporates various cash distribution methods. Studies show a stronger correlation between total payout yield and returns compared to dividend yield and returns. Fama-MacBeth regressions confirm this, revealing insignificant associations between dividend yields and returns, while payout measures exhibit significant associations. Payout yields maintain consistent predictive power across time periods and demonstrate significant out-of-sample predictability, unlike dividend yields. Trading strategies based on payout yields generate profitable portfolios with negative market betas and size factor loadings, indicating unexplained returns beyond conventional risk metrics.
Backtest performance
Full Python code
from AlgoLib import *
import numpy as np
class NetPayoutYieldEffect(XXX):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100_000)
self.UniverseSettings.Leverage = 5
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.FundamentalFunction)
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.0
# Fundamental Filter Parameters
self.exchange_codes = ['NYS', 'NAS', 'ASE']
self.fundamental_count = 500
self.quantile = 10
self.long_symbols = []
self.rebalancing_month = 6
self.selection_flag = True
self.exchange = self.AddEquity('SPY', Resolution.Daily).Symbol
self.Schedule.On(self.DateRules.MonthEnd(self.exchange),
self.TimeRules.AfterMarketOpen(self.exchange),
self.Selection)
def FundamentalFunction(self, fundamental):
if not self.selection_flag:
return Universe.Unchanged
filtered = [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.ValuationRatios.TotalYield)
and f.ValuationRatios.TotalYield != 0
and not np.isnan(f.FinancialStatements.CashFlowStatement.CommonStockIssuance.TwelveMonths)
and f.FinancialStatements.CashFlowStatement.CommonStockIssuance.TwelveMonths != 0]
top_by_dollar_volume = sorted(filtered,
key=lambda x: x.DollarVolume,
reverse=True)[:self.fundamental_count]
payout_yield = lambda x: ((x.ValuationRatios.TotalYield * (x.MarketCap)) - \
(x.FinancialStatements.CashFlowStatement.CommonStockIssuance.TwelveMonths / (x.MarketCap)))
sorted_by_payout = sorted(top_by_dollar_volume, key = payout_yield, reverse=True)
if len(sorted_by_payout) >= self.quantile:
quantile = int(len(sorted_by_payout) / self.quantile)
self.long_symbols = [x.Symbol for x in sorted_by_payout[:quantile]]
return self.long_symbols
def OnData(self, slice):
if not self.selection_flag:
return
self.selection_flag = False
# Trade Execution
portfolio = [PortfolioTarget(symbol, 1 / len(self.long_symbols))
for symbol in self.long_symbols
if slice.ContainsKey(symbol) and slice[symbol] is not None]
self.SetHoldings(portfolio, True)
self.long_symbols.clear()
def Selection(self):
if self.Time.month == self.rebalancing_month:
self.selection_flag = True
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))