
“该策略投资于恢复或首次派发股息的纽约证券交易所(NYSE)、美国证券交易所(AMEX)和纳斯达克(NASDAQ)股票,持有一年,按等权重分配,并通过做空标准普尔期货头寸对冲市场风险。”
资产类别: 期货,股票 | 地区: 美国 | 周期: 每月 | 市场: 股票 | 关键词: 股息,公告
I. 策略概要
该策略专注于在纽约证券交易所(NYSE)、美国证券交易所(AMEX)和纳斯达克(NASDAQ)上市的美国股票。每个月,投资者识别上个月开始或恢复分红的股票,并将其加入投资组合。每只股票持有一年,所有头寸均等加权。为了对冲市场风险,投资者在标普500期货上建立空头头寸,权重与多头股票头寸相同。
II. 策略合理性
正如“简短描述”中所述,股息支付的启动被视为公司实力的标志。市场对这一新信息反应不足,因此出现了股息启动效应。
III. 来源论文
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年的样本期内产生持续的正异常回报。


IV. 回测表现
| 年化回报 | 9.27% |
| 波动率 | 6.74% |
| β值 | 0.273 |
| 夏普比率 | 0.78 |
| 索提诺比率 | 0.387 |
| 最大回撤 | N/A |
| 胜率 | 67% |
V. 完整的 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