Quant BuffetRelax, Not Over Thinking

Dividend Announcement Effect

Log in to collect

Academic paper

Seven Decades of Long Term Abnormal Return Persistence: The Case of Dividend Initiations and Resumptions

AuthorsSorin M. Sorescu; Rodney D Boehme

Institute
  • Wichita State University
  • Decision Sciences (United States)
  • Texas A&M University
  • ?Texas A&M University - Department of Finance
  • ?Wichita State University - Department of Finance, Real Estate & Decision Sciences (FREDS)

Strategy in a nutshell

: Monthly U.S. Equity Dividend Announcement Hedged Strategy

The strategy focuses on US stocks listed on NYSE, AMEX, and NASDAQ. Each month, the investor identifies stocks that initiated or resumed dividends in the previous month and adds them to the portfolio. Each stock is held for one year, with positions equally weighted. To hedge market risk, the investor takes a short position in S&P futures, weighted equally to the long stock positions.

Economic rationale

As it is stated in a “Short Description” – the initiation of dividend payments is seen as a sign of the company’s strength. Market underreacts to this new information, and the post-dividend initiation effect emerges.

Backtest performance

Annualised return9.27%
Volatility6.74%
Beta0.273
Sharpe ratio0.78
Sortino ratio0.387
Win rate67%

Full Python code

import trade_manager
from AlgorithmImports import *
from typing import Dict, List
import data_tools
class DividendAnnouncementEffect(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)

self.exchange_codes:List[str] = ['NYS', 'NAS', 'ASE']
self.universe_assets:List[Symbol] = []
self.fundamental_sorting_key = lambda x: x.DollarVolume
self.fundamental_count:int = 500
self.leverage:int = 10

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

# self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
spy_data = self.AddData(data_tools.QuantpediaFutures, 'CME_ES1', Resolution.Daily)
spy_data.SetLeverage(self.leverage)
self.symbol:Symbol = spy_data.Symbol

self.period:int = 12
self.holding_period:int = 12 * 30
self.long_count:int = 40
self.short_count:int = 0

# 40 symbols long, one year of holding.
self.trade_manager:TradeManager = trade_manager.TradeManager(self, self.long_count, self.short_count, self.holding_period)

self.selection_flag:bool = False
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
self.settings.daily_precise_end_time = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.FundamentalSelectionFunction)
self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.At(0, 0), self.Selection)

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

for security in changes.RemovedSecurities:
    symbol:Symbol = security.Symbol
    if symbol in self.data:
        del self.data[symbol]
    
def FundamentalSelectionFunction(self, fundamental: List[Fundamental]) -> List[Symbol]:
if not self.selection_flag:
    return Universe.Unchanged

selected:List[Fundamental] = [x for x in fundamental if x.HasFundamentalData and x.Market == 'usa' and x.SecurityReference.ExchangeId in self.exchange_codes]

if len(selected) > self.fundamental_count:
    selected = [x for x in sorted(selected, key=self.fundamental_sorting_key, reverse=True)[:self.fundamental_count]]
self.universe_assets = [x.Symbol for x in selected]
for symbol in self.universe_assets:
    if symbol not in self.data:
        self.data[symbol] = data_tools.SymbolData(self.period)

return self.universe_assets
def OnData(self, data: Slice):
if self.Securities[self.symbol].GetLastData() and self.Time.date() >= data_tools.QuantpediaFutures.get_last_update_date()[self.symbol]:
    self.Liquidate()
    return
# Liquidate opened symbols after one year.
self.trade_manager.TryLiquidate()

# Update dividends data
for kvp in data.Dividends:
    div_ticker:str = kvp.Key
    if div_ticker in self.data:
        # Storing dividends data
        month_year:str = self.Time.strftime("%m-%Y")
        self.data[div_ticker].update_dividends(month_year)

# If only SPY is invested, we liquidate it        
if self.Portfolio.Count == 1 and self.Portfolio[self.symbol].Invested is True:
    self.Liquidate(self.symbol)

if not self.selection_flag:
    return
self.selection_flag = False

# Check stocks for trades
for symbol in self.universe_assets:
    if symbol in self.data:
        if symbol in data and data[symbol]:
            if self.data[symbol].is_included_long_enough():
                current_date:datetime.date = self.Time.date()
                
                # Trade execution
                if self.data[symbol].check_dividends_paying(current_date) and self.Securities[symbol].Invested is False:
                    # We short SPY if we invest into at least one stock from universe.
                    if not self.Portfolio[self.symbol].Invested:
                        self.SetHoldings(self.symbol, -1)
                    
                    self.trade_manager.Add(symbol, True)
                
            self.data[symbol].increment_inclusion_period()
        
def Selection(self) -> None:
self.selection_flag = True