Quant Buffet放轻松,别过度思虑

股息公告效应

登录后收藏

学术论文

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

作者Sorin M. Sorescu; Rodney D Boehme

机构
  • 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)

策略概要

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

II. 策略合理性

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

回测表现

波动率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