Quant Buffet放轻松,别过度思虑

股息公告效应

登录后收藏

学术论文

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

作者Boehme

机构
  • ?Sorescu, Wichita State University - 财务、房地产与决策科学系 (FREDS), 德州农工大学
  • ?Adam C. Sinn `00 财务系
论文摘要

我们分析了那些开始支付现金股息或恢复支付现金股息的公司,在公告后的1到5年股票表现。无论是事件窗口期还是长期异常股票回报都显著为正,表明市场最初对典型公司的股息公告反应不足。这个结果在1927到1998年间的子样本中也得到了验证,表明这一观察到的异常现象具有时间上的持续性,不仅仅是数据挖掘的结果,也不是某一特定时间段的短暂现象所驱动。在公告期内有正异常回报的公司,其公告后的异常回报最高。一个简单的投资策略,即分别在经历正异常回报和负异常回报的公司上建立多头和空头头寸,能够在整个1927-1998年的样本期内产生持续的正异常回报。

策略概要

该策略专注于在纽约证券交易所(NYSE)、美国证券交易所(AMEX)和纳斯达克(NASDAQ)上市的美国股票。每个月,投资者识别上个月开始或恢复分红的股票,并将其加入投资组合。每只股票持有一年,所有头寸均等加权。为了对冲市场风险,投资者在标普500期货上建立空头头寸,权重与多头股票头寸相同。

策略合理性

正如“简短描述”中所述,股息支付的启动被视为公司实力的标志。市场对这一新信息反应不足,因此出现了股息启动效应。

回测表现

波动率6.74%
夏普比率0.78
索提诺比率0.387
胜率67%

完整 Python 代码

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