“该策略投资于基于累计过往回报、经波动率调整后的表现最佳的行业ETF(PG6)。投资组合每月重新平衡,重点关注高回报、高波动率的ETF。”

I. 策略概要

投资范围包括49个行业投资组合,可通过ETF复制。每个月,投资组合根据累计过往回报分为六组。PG1(输家)包括底部六分之一,而PG6(赢家)包括顶部六分之一。投资者购买PG6的ETF,并根据其预期波动率进行加权,该波动率由上个月的已实现日波动率计算得出。投资组合每月重新平衡,重点关注表现最佳的ETF,并根据波动率调整敞口。

II. 策略合理性

该策略侧重于通过行业投资组合(如ETF)进行多元化投资,与个股相比,行业投资组合往往具有较低的事前风险。通过利用波动率聚集效应,可以根据历史数据预测未来波动率。利用这些信息,可以根据估计的未来波动率对投资组合进行加权,在保持回报的同时降低风险。这种方法产生更好的风险/回报比率,与依赖个股的策略相比,提供更稳定的结果。

III. 来源论文

Risk-managed industry momentum and momentum crashes [点击查看论文]

<摘要>

本文研究了Barosso和Santa-Clara(2015)在行业动量背景下的风险管理动量策略。我们研究了几种传统的动量策略,包括Novy-Marx(2012)最近提出的策略。此外,我们还研究了不同的方差预测期限对平均收益的影响,以及Daniel和Moskowitz(2016)的期权效应。我们的结果总体表明,无论是普通的行业动量策略还是风险管理的行业动量策略,都不受期权效应的影响,这意味着这些策略没有时变贝塔。此外,风险管理的好处在波动率估计器、动量策略和子样本中都是稳健的。最后,行业中的“回声效应”在子样本中并不稳健,因为该策略仅在最近的子样本中有效。

IV. 回测表现

年化回报25.64%
波动率32.15%
β值-0.137
夏普比率0.72
索提诺比率-0.297
最大回撤N/A
胜率49%

V. 完整的 Python 代码

from AlgorithmImports import *
class RiskManagedIndustryMomentum(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2010, 1, 1)
        self.SetCash(100000)
        self.symbols = ["XLY", # Consumer Discretionary Select Sector SPDR Fund
                        "PBS", # Invesco Dynamic Media ETF
                        "PEJ", # Invesco Dynamic Leisure and Entertainment ETF
                        "PMR", # Invesco Dynamic Retail ETF
                        
                        "XLP", # Consumer Staples Select Sector SPDR Fund
                        "PBJ", # Invesco Dynamic Food & Beverage ETF
                        
                        "XLE", # Energy Select Sector SPDR Fund
                        "PBW", # Invesco WilderHill Clean Energy ETF
                        "PXE", # Invesco Dynamic Energy Exploration & Production ETF
                        "NLR", # VanEck Vectors Uranium+Nuclear Energy ETF
                        "AMJ", # JPMorgan Alerian MLP Index ETN
                        
                        "XLF", # Financial Select Sector SPDR Fund
                        "KBE", # SPDR S&P Bank ETF
                        "KIE", # SPDR S&P Insurance ETF
                        "KRE", # SPDR S&P Regional Banking ETF
                        "PSP", # Invesco Global Listed Private Equity ETF
                        
                        "XLV", # Health Care Select Sector SPDR Fund
                        "IBB", # iShares Nasdaq Biotechnology ETF
                        "IHF", # iShares U.S. Healthcare Providers ETF
                        "IHE", # iShares U.S. Pharmaceuticals ETF
                        
                        "XLI", # Industrial Select Sector SPDR Fund
                        "ITA", # iShares U.S. Aerospace & Defense ETF
                        "IYT", # iShares Transportation Average ETF
                        "PHI", # Invesco Water Resources ETF
                        
                        "XLB", # Materials Select Sector SPDR ETF
                        "MOO", # VanEck Vectors Agribusiness ETF
                        "GDX", # VanEck Vectors Gold Miners ETF
                        "XHB", # SPDR S&P Homebuilders ETF
                        "IGE", # iShares North American Natural Resources ETF
                        
                        "XLK", # Technology Select Sector SPDR Fund
                        "FDN", # First Trust Dow Jones Internet Index
                        "SOXX", # iShares PHLX Semiconductor ETF
                        "IGV", # iShares Expanded Tech-Software Sector ET
                        "IYZ", # iShares U.S. Telecommunications ETF
                        
                        "XLU", # Utilities Select Sector SPDR Fund
                        "IGF", # iShares Global Infrastructure ETF
                        ]
        self.period = 21
        self.SetWarmUp(self.period)
        self.data = {}
        
        for symbol in self.symbols:
            data = self.AddEquity(symbol, Resolution.Daily)
            data.SetLeverage(10)
            
            self.data[symbol] = RollingWindow[float](self.period)
            
        self.Schedule.On(self.DateRules.MonthStart(self.symbols[0]), self.TimeRules.AfterMarketOpen(self.symbols[0]), self.Rebalance)
    def OnData(self, data):
        for symbol in self.data:
            if symbol in data and data[symbol]:
                price = data[symbol].Value
                self.data[symbol].Add(price)
                    
    def Rebalance(self):
        if self.IsWarmingUp: return
    
        # Return sorting
        return_volatility = {}
        for symbol in self.symbols:
            if self.data[symbol].IsReady:
                prices = np.array([x for x in self.data[symbol]])
                ret = prices[0] / prices[-1] - 1
                
                daily_returns = prices[:-1] / prices[1:] - 1
                vol = np.std(daily_returns) * np.sqrt(252)
                return_volatility[symbol] = (ret, vol)
        sorted_by_return = sorted(return_volatility.items(), key = lambda x: x[1][0], reverse = True)
        sixth = int(len(sorted_by_return) / 6)
        long = [x for x in sorted_by_return[:sixth]]
        short = [x for x in sorted_by_return[-sixth:]]
        # Volatility weighting
        total_vol_long = sum([1/x[1][1] for x in long])
        weight = {}
        for symbol, ret_vol in long:
            vol = ret_vol[1]
            if vol != 0:
                weight[symbol] = (1.0 / vol) / total_vol_long
            else: 
                weight[symbol] = 0
        total_vol_short = sum([1/x[1][1] for x in short])
        for symbol, ret_vol in short:
            vol = ret_vol[1]
            if vol != 0:
                weight[symbol] = -(1.0 / vol) / total_vol_short
            else: 
                weight[symbol] = 0
        
        # Trade execution.
        invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
        for symbol in invested:
            if symbol not in weight:
                self.Liquidate(symbol)
        
        for symbol, w in weight.items():
            self.SetHoldings(symbol, w)

发表评论

了解 Quant Buffet 的更多信息

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

继续阅读