投资池由纽约证券交易所(NYSE)、美国证券交易所(AMEX)和纳斯达克(NASDAQ)的股票组成。每年6月根据净派息收益率排名形成10个投资组合,买入最高收益率组合,持有一年后重新平衡。

策略概述

该策略的投资范围包括纽约证券交易所、美国证券交易所和纳斯达克的股票。每年6月,依据净派息收益率(Net Payout Yield)对股票进行排名,并根据排名构建10个投资组合。净派息收益率定义为股息加上股票回购减去股票发行,然后除以年末的市值。我们使用基于现金流表的股票回购指标来计算净派息收益率。买入净派息收益率最高的组合,持有一年并在次年6月重新平衡。

策略合理性

传统上,股息被认为是衡量股票健康状况的可靠指标,但近期研究表明,需要更广泛的预测指标。过去,高股息收益率常被视为股票相对低估的信号,因为它表明公司向股东返还了更多现金。调整这一逻辑,净派息收益率考虑了多种现金分配方式,包括股息、股票回购和股票发行。研究显示,与股息收益率相比,总派息收益率与股票回报之间的关联更强。Fama-MacBeth回归分析表明,股息收益率与回报之间的关联并不显著,而派息收益率则具有显著关联。派息收益率在不同时间段内具有一致的预测能力,并且在样本外的测试中也表现出显著的可预测性。基于派息收益率的交易策略能够生成盈利的投资组合,这些组合通常具有负市场贝塔和规模因子负荷,表明其回报超出了传统风险度量指标的解释。

论文来源

On the Importance of Measuring Payout Yield: Implications for Empirical Asset Pricing [点击浏览原文]

<摘要>

之前的研究表明,股息价格比率在1980年代和1990年代发生了显著变化,而总派息比率(股息加回购与价格的比值)变化很小。我们探讨了这一差异对资产定价模型的影响。特别是,时间序列回归中股息对超额股票回报的预测能力下降的现象被大大夸大了。当使用总派息收益率而非股息收益率时,在短期和长期内均能发现统计上和经济上显著的可预测性。我们还提供了证据,表明总派息收益率在跨期预期股票回报方面的信息量超过了股息收益率,并且高低派息收益率组合是一个定价因子。

回测表现

年化收益率22.13%
波动率N/A
Beta0.885
夏普比率0.355
索提诺比率0.371
最大回撤52.6%
胜率82%

完整python代码

from AlgoLib import *
import numpy as np

class NetPayoutYieldEffect(XXX):

    def Initialize(self):
        self.SetStartDate(2000, 1, 1)  
        self.SetCash(100_000) 

        self.UniverseSettings.Leverage = 5
        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.FundamentalFunction)
        self.Settings.MinimumOrderMarginPortfolioPercentage = 0.0
        
        # Fundamental Filter Parameters
        self.exchange_codes = ['NYS', 'NAS', 'ASE']
        self.fundamental_count = 500
        self.quantile = 10

        self.long_symbols = []
        
        self.rebalancing_month = 6
        self.selection_flag = True

        self.exchange = self.AddEquity('SPY', Resolution.Daily).Symbol
        self.Schedule.On(self.DateRules.MonthEnd(self.exchange), 
                        self.TimeRules.AfterMarketOpen(self.exchange), 
                        self.Selection)

    def FundamentalFunction(self, fundamental):
        if not self.selection_flag:
            return Universe.Unchanged

        filtered = [f for f in fundamental if f.HasFundamentalData
                    and f.SecurityReference.ExchangeId in self.exchange_codes
                    and not np.isnan(f.MarketCap)
                    and f.MarketCap !=0
                    and not np.isnan(f.ValuationRatios.TotalYield)
                    and f.ValuationRatios.TotalYield != 0
                    and not np.isnan(f.FinancialStatements.CashFlowStatement.CommonStockIssuance.TwelveMonths)
                    and f.FinancialStatements.CashFlowStatement.CommonStockIssuance.TwelveMonths != 0]
        
        top_by_dollar_volume = sorted(filtered, 
                                        key=lambda x: x.DollarVolume, 
                                        reverse=True)[:self.fundamental_count]

        payout_yield = lambda x: ((x.ValuationRatios.TotalYield * (x.MarketCap)) - \
                    (x.FinancialStatements.CashFlowStatement.CommonStockIssuance.TwelveMonths / (x.MarketCap)))
        
        sorted_by_payout = sorted(top_by_dollar_volume, key = payout_yield, reverse=True)

        if len(sorted_by_payout) >= self.quantile:
            quantile = int(len(sorted_by_payout) / self.quantile)
            self.long_symbols = [x.Symbol for x in sorted_by_payout[:quantile]]

        return self.long_symbols
    
    def OnData(self, slice):
        if not self.selection_flag:
            return
        self.selection_flag = False

        # Trade Execution
        portfolio = [PortfolioTarget(symbol, 1 / len(self.long_symbols)) 
                                            for symbol in self.long_symbols 
                                            if slice.ContainsKey(symbol) and slice[symbol] is not None]

        self.SetHoldings(portfolio, True)
        self.long_symbols.clear()
    
    def Selection(self):
        if self.Time.month == self.rebalancing_month:
            self.selection_flag = True

    def OnSecuritiesChanged(self, changes):
        for security in changes.AddedSecurities:
            security.SetFeeModel(CustomFeeModel())

# Custom fee model
class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))  

Leave a Reply

Discover more from Quant Buffet

Subscribe now to keep reading and get access to the full archive.

Continue reading