“该策略通过滚动一年期股息期货来捕捉股息风险溢价,通过做空SX5E来对冲股票风险敞口,使用基于每日回归的对冲比率进行有效的风险管理。”

I. 策略概要

该策略旨在通过模拟假设的1年期股息期货来保持对隐含股息的恒定敞口。这涉及每日在近月和远月股息期货之间滚动头寸,逐步将近月期货的权重降低至零,同时将远月期货的权重增加至100%。为了捕捉股息风险溢价,投资者做多“1年期恒定到期日”股息期货,并通过做空SX5E指数来对冲股票风险敞口。每日对冲比率通过过去两周股息期货回报与SX5E回报的线性回归确定,确保有效的风险管理。

II. 策略合理性

股息风险溢价的存在归因于几个因素。投资者高估了单只股票的股息削减风险,尽管年股息削减超过60%的情况很少见(自1991年以来为4%)。他们还低估了股息指数的多元化收益。流动性风险也是一个因素,因为股息期货的流动性较差。此外,系统性错误定价是由银行交易台持有大量SX5E股息多头头寸引起的。这些因素,包括对低概率、高影响力事件的风险厌恶和流动性挑战,在股息期货市场中创造了持续的溢价。

III. 来源论文

提取股息风险溢价 [点击查看论文]

<摘要>

在过去的几年里,股息不再被视为股权投资的副产品,它们构成独立资产类别的观点越来越普遍。虽然这主要与近期成熟、流动性强的股息期货市场的发展有关,但也有基本面和技术方面的原因将股息与股票或公司债券区分开来;特别是股息有时与股票的相关性较弱,且波动性较低。在本报告中,我们得出以下结论:股息遵循其自身的动态,股息风险溢价丰厚,构建有利可图的系统性股息策略是可能的。

IV. 回测表现

年化回报12.9%
波动率11.6%
β值0.227
夏普比率1.11
索提诺比率N/A
最大回撤-28.8%
胜率55%

V. 完整的 Python 代码

from AlgorithmImports import *
from scipy import stats
import numpy as np
#endregion
class DividendRiskPremiumStrategy(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2010, 1, 1)
        self.SetCash(100000)
        
        self.dividend_futures = ['FEXD1', 'FEXD2', 'FEXD3']
        
        self.fexd1 = None
        for i, div_symbol in enumerate(self.dividend_futures):
            data = self.AddData(QuantpediaFutures, div_symbol, Resolution.Daily)
            data.SetLeverage(10)
            data.SetFeeModel(CustomFeeModel())
            
            # store nearby contract
            if i == 0:
                self.fexd1 = data.Symbol
        
        # STOXX 50 future
        data = self.AddData(QuantpediaFutures, "EUREX_FSTX1", Resolution.Daily)
        data.SetLeverage(10)
        data.SetFeeModel(CustomFeeModel())
        self.SX5E = data.Symbol
        
        # daily price
        self.data = {}
        self.period = 10
        self.max_missing_days = 5
        for symbol in [self.fexd1, self.SX5E]:
            self.data[symbol] = RollingWindow[float](self.period)
        
        self.selection_flag = False
        self.recent_month:int = -1
    
    def OnData(self, data):
        # store daily prices
        if self.fexd1 in data and data[self.fexd1] and self.SX5E in data and data[self.SX5E]:
            self.data[self.fexd1].Add(data[self.fexd1].Value)
            self.data[self.SX5E].Add(data[self.SX5E].Value)
        else:
            if (self.Securities[self.fexd1].GetLastData() and (self.Time.date() - self.Securities[self.fexd1].GetLastData().Time.date()).days > self.max_missing_days) or \
                (self.Securities[self.SX5E].GetLastData() and (self.Time.date() - self.Securities[self.SX5E].GetLastData().Time.date()).days > self.max_missing_days):
                self.data[self.fexd1].Reset()
                self.data[self.SX5E].Reset()
                self.Liquidate()
        if self.recent_month == self.Time.month:
            return
        self.recent_month = self.Time.month
        if self.Time.month in [7, 1]:
            self.Liquidate()
            self.weights = [1, 0]   # divident future #2 and #3 weights
            self.selection_flag = True
        
        if self.Portfolio.Invested or self.selection_flag:
            if self.data[self.fexd1].IsReady and self.data[self.SX5E].IsReady:
                dividend_future_prices = np.array([x for x in self.data[self.fexd1]])
                market_prices = np.array([x for x in self.data[self.SX5E]])
                
                dividend_future_returns = dividend_future_prices[:-1] / dividend_future_prices[1:] - 1
                market_returns = market_prices[:-1] / market_prices[1:] - 1
                if not all(x==0 for x in dividend_future_returns):
                    slope, intercept, r_value, p_value, std_err = stats.linregress(market_returns, dividend_future_returns)
                    
                    # dividend futures exposure
                    self.SetHoldings(self.dividend_futures[1], self.weights[0])
                    self.SetHoldings(self.dividend_futures[2], self.weights[1])
                    
                    # adjust weights every invested day
                    if self.weights[0] >= 0.008:
                        self.weights[0] -= 0.008
                    
                    if self.weights[0] <= 0.992:
                        self.weights[1] += 0.008
                    
                    # hedge
                    self.SetHoldings(self.SX5E, -abs(slope))
                    
                    self.selection_flag = False
                else:
                    self.Liquidate()
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
    def GetSource(self, config, date, isLiveMode):
        return SubscriptionDataSource("data.quantpedia.com/backtesting_data/futures/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
    def Reader(self, config, line, date, isLiveMode):
        data = QuantpediaFutures()
        data.Symbol = config.Symbol
        
        if not line[0].isdigit(): return None
        split = line.split(';')
        
        data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
        data['back_adjusted'] = float(split[1])
        data['spliced'] = float(split[2])
        data.Value = float(split[1])
        return data
# Custom fee model.
class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))

发表评论

了解 Quant Buffet 的更多信息

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

继续阅读