“该策略根据利率变化交易美国股票,目标是股息收益率十分位,在高股息和低股息股票之间切换,并每月重新平衡以获得最佳表现。”

I. 策略概要

该策略侧重于CRSP数据库中的美国股票,根据联邦基金利率的一年期变化来识别利率上升或下降的时期。股票按股息收益率分为十等份。在利率下降时期,该策略做多高股息股票(前十等份)并做空低股息股票(后十等份)。在利率上升时期,该策略反向操作,做多低股息股票并做空高股息股票。投资组合采用等权重,每月重新平衡,以适应利率变化,利用不同经济条件下的股息收益率动态。

II. 策略合理性

该研究探讨了投资者行为偏差如何影响投资组合选择,特别是在低利率时期。理性投资者对收入和资本利得应该无动于衷,但许多人遵循一种经验法则,即靠投资收入生活,同时保留本金。这导致了“追求收入”现象,即当存款和债券的利息收入不足以满足消费需求时,投资者转向高收益资产。通过股票持有量和共同基金流向的数据,研究发现在降息期间高收益资产有显著流入,而在加息期间有流出,这影响了基于股息政策的公司估值。一个理论模型演示了这种行为如何影响货币政策、消费、投资组合选择和资产价格。低利率导致高估的股息股票,突出了金融部门在传导货币政策效应方面的作用及其对经济动态的更广泛影响。

III. 来源论文

Monetary Policy and Reaching for Income [点击查看论文]

<摘要>

我们利用个人投资组合持有数据和共同基金流向数据发现,低利率导致对高股息股票和高收益债券等创收资产的需求显著增加。我们认为这种“追求收入”现象是由遵循“靠收入生活”经验法则的投资者驱动的。我们的实证分析表明,这种对当前收入的偏好影响着家庭投资组合选择和创收资产的价格。此外,我们还探讨了追求收入对资本配置和货币政策有效性的影响。

IV. 回测表现

年化回报7.34%
波动率22.23%
β值-0.014
夏普比率0.33
索提诺比率-0.284
最大回撤N/A
胜率50%

V. 完整的 Python 代码

from AlgorithmImports import *
import numpy as np
#endregion
class DividendStocksAndRisingOrFallingInterestRates(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetCash(100000)
        self.long:List[Symbol] = []
        self.short:List[Symbol] = []
        self.symbol:Symbol = self.AddData(FEDFUNDS, 'FEDFUNDS', Resolution.Daily).Symbol
        self.period:int = 12
        self.quantile:int = 10
        self.leverage:int = 5
        self.min_share_price:float = 5.
        self.exchange_codes:List[str] = ['NYS', 'NAS', 'ASE']
        self.fed_fund_rate:RollingWindow = RollingWindow[float](self.period)
        self.fundamental_count:int = 3000
        self.fundamental_sorting_key = lambda x: x.MarketCap
        self.recent_month:int = -1
        self.selection_flag:bool = False
        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.FundamentalSelectionFunction)
        self.settings.daily_precise_end_time = False
        self.settings.minimum_order_margin_portfolio_percentage = 0.
        
    def OnSecuritiesChanged(self, changes: SecurityChanges) -> None:
        for security in changes.AddedSecurities:
            security.SetFeeModel(CustomFeeModel())
            security.SetLeverage(self.leverage)
                
    def FundamentalSelectionFunction(self, fundamental: List[Fundamental]) -> List[Symbol]:
        if not self.selection_flag:
            return Universe.Unchanged
        # FED rate data is still comming in
        last_update_date:datetime.date = FEDFUNDS.get_last_update_date()
        if last_update_date <= self.Time.date():
            return Universe.Unchanged
        if self.fed_fund_rate.IsReady:
            selected:List[Fundamental] = [
                x for x in fundamental if x.HasFundamentalData and x.Market == 'usa' and \
                x.SecurityReference.ExchangeId in self.exchange_codes and x.Price >= self.min_share_price and \
                not np.isnan(x.EarningReports.DividendPerShare.ThreeMonths) and x.EarningReports.DividendPerShare.ThreeMonths != 0
            ]
            
            if len(selected) > self.fundamental_count:
                selected = [x for x in sorted(selected, key=self.fundamental_sorting_key, reverse=True)[:self.fundamental_count]]
            dividend_yield:Dict[Symbol, float] = {x.Symbol : x.EarningReports.DividendPerShare.ThreeMonths for x in selected}
            if len(dividend_yield) >= self.quantile:
                # Stocks are sorted descending by dividend yield
                sorted_by_dividend_yield:List[Symbol] = sorted(dividend_yield, key=dividend_yield.get, reverse=True)
                quantile:int = len(sorted_by_dividend_yield) // self.quantile
                high_dividend_stocks:List[Symbol] = sorted_by_dividend_yield[:quantile] # top decile
                low_dividend_stocks:List[Symbol] = sorted_by_dividend_yield[-quantile:] # bottom decile
                
                # identify rising or declining interest rate periods, based on the one-year change in the fed funds rate 
                interest_rate_rising:bool = self.fed_fund_rate[0] > self.fed_fund_rate[self.period-1]
                
                if interest_rate_rising: # rising period
                    self.long = low_dividend_stocks
                    self.short = high_dividend_stocks
                else: # declining period
                    self.long = high_dividend_stocks
                    self.short = low_dividend_stocks
        
        return self.long + self.short    
    def OnData(self, data: Slice) -> None:
        # Add fed-rates to object in self.data
        if self.symbol in data and data[self.symbol]:
            price:float = data[self.symbol].Value
            self.fed_fund_rate.Add(price)
            
            self.selection_flag = True
            return
        
        if not self.selection_flag:
            return
        self.selection_flag = False
        
        # Trade execution
        count_long:int = len(self.long)
        count_short:int = len(self.short)
        
        self.Liquidate()
        
        if not (self.Time.year == 2008 and self.Time.month == 6):
            for symbol in self.long:
                if symbol in data and data[symbol]:
                    self.SetHoldings(symbol, 1 / count_long)
                
            for symbol in self.short:
                if symbol in data and data[symbol]:
                    self.SetHoldings(symbol, - 1 / count_short)
        self.short.clear()
        self.long.clear()
            
# Source: https://fred.stlouisfed.org/series/T10Y3M
class FEDFUNDS(PythonData):
    def GetSource(self, config, date, isLiveMode):
        return SubscriptionDataSource('data.quantpedia.com/backtesting_data/economic/FEDFUNDS.csv', SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
    _last_update_date:datetime.date = datetime(1,1,1).date()
    @staticmethod
    def get_last_update_date() -> datetime.date:
       return FEDFUNDS._last_update_date
    def Reader(self, config, line, date, isLiveMode):
        data = FEDFUNDS()
        data.Symbol = config.Symbol
        if not line[0].isdigit(): return None
        split = line.split(';')
        
        # Parse the CSV file's columns into the custom data class
        data.Time = datetime.strptime(split[0], "%Y-%m-%d") + timedelta(days=1)
        if split[1] != '.':
            data.Value = float(split[1])
        if data.Time.date() > FEDFUNDS._last_update_date:
            FEDFUNDS._last_update_date = data.Time.date()
        
        return data
class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))

发表评论

了解 Quant Buffet 的更多信息

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

继续阅读