“该策略投资于恢复或首次派发股息的纽约证券交易所(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 [点击查看论文]

<摘要>

我们分析了那些开始支付现金股息或恢复支付现金股息的公司,在公告后的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

发表评论

了解 Quant Buffet 的更多信息

立即订阅以继续阅读并访问完整档案。

继续阅读