
“该策略投资于基于累计过往回报、经波动率调整后的表现最佳的行业ETF(PG6)。投资组合每月重新平衡,重点关注高回报、高波动率的ETF。”
资产类别: ETF | 地区: 全球 | 周期: 每月 | 市场: 股票 | 关键词: 风险管理、动量
I. 策略概要
投资范围包括49个行业投资组合,可通过ETF复制。每个月,投资组合根据累计过往回报分为六组。PG1(输家)包括底部六分之一,而PG6(赢家)包括顶部六分之一。投资者购买PG6的ETF,并根据其预期波动率进行加权,该波动率由上个月的已实现日波动率计算得出。投资组合每月重新平衡,重点关注表现最佳的ETF,并根据波动率调整敞口。
II. 策略合理性
该策略侧重于通过行业投资组合(如ETF)进行多元化投资,与个股相比,行业投资组合往往具有较低的事前风险。通过利用波动率聚集效应,可以根据历史数据预测未来波动率。利用这些信息,可以根据估计的未来波动率对投资组合进行加权,在保持回报的同时降低风险。这种方法产生更好的风险/回报比率,与依赖个股的策略相比,提供更稳定的结果。
III. 来源论文
Risk-managed industry momentum and momentum crashes [点击查看论文]
- 克劳斯·格罗比斯、约尼·鲁奥察莱宁和扬内·艾约。瓦萨大学;于韦斯屈莱大学。Inderes Oy。瓦萨大学会计与金融系
<摘要>
本文研究了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)