
“该策略涉及根据12个月回顾期内的正回归结果投资于9只精选行业SPDR ETF,在选定的行业中平均分配资金,每月重新平衡。”
资产类别: ETF | 地区: 美国 | 周期: 每月 | 市场: 股票 | 关键词: 行业轮动、回归分析
I. 策略概要
投资范围包括9只精选行业SPDR ETF,代表标准普尔500指数的各个行业。投资者使用12个月的回顾期,通过多元回归计算自变量。根据回归结果,资金平均分配给结果为正的行业ETF,其余资金保留为现金。投资组合每月重新平衡,投资者持仓一个月。所包含的行业有医疗保健、工业、非必需消费品、必需消费品、材料、科技、公用事业、能源和金融。
II. 策略合理性
标准普尔500指数按市值加权,因此被动投资优先考虑的是规模最大的公司,而不一定是表现最好的公司。将指数按行业划分,表现最好的行业通常仅占3%。基于规则的策略可以将更多资金分配给表现最好的行业,从而提高回报。
III. 来源论文
Multivariate Regression Analysis: Considering the Relevance of Past Performance [点击查看论文]
- Seggebruch,R.T. Jones 资本股票管理公司
<摘要>
投资者通常将过往业绩作为了解资产类别或特定投资经理的主要信息来源。过往业绩可以告诉我们很多关于资产类别和经理倾向的信息,但其含义应谨慎评估。简单地比较任意时间段内的业绩可能会导致追逐回报的模式,从而严重损害业绩。事实上,基于过往回报改变投资的情绪化行为一直是许多出版物和数小时研究的主题。本文将提供关于过往业绩相关性的统计视角。具体而言,我将展示多元回归分析可以成功识别各种过往业绩统计数据与未来回报之间的数学关系。
多元回归分析对于金融行业来说并不陌生。技术和量化分析师已经使用它有一段时间了。然而,构建和解释这种类型的统计分析对于没有技术背景的投资者来说可能是一个障碍。考虑到这一点,我将详细描述分析中的每个变量和结果,并告知读者一个统计显著的预测模型所需的条件。我认为,这种见解将使更多类型的投资者能够受益于多元回归分析。
我首先简要描述了用于代表标准普尔500指数各行业的资产。随后,我解释了用于计算回报的方法和计算频率。计算和频率这两个变量通常是争论的焦点,并且可能对分析结果产生重大影响。在本文中,我将使用月度回报频率来计算对数回报。这些决定是多元回归分析的关键,必须在完成进一步分析之前做出,这使得比较使用不同频率和回报计算的结果相当耗时。然而,它确实迫使这个决定成为一个预先考虑,并减少了事后考虑可能存在的偏差。
投资者通常通过比较不同的回报并选择当时看起来最好的回报,以事后偏见选择计算时间段。为了更好地回答“应该使用什么时间段进行绩效计算?”和“预测的有效期是多久?”这两个问题,我将为四个不同的回顾时间段和四个不同的未来时间段附加统计显著性,并就哪个组合具有最高的预测能力做出明智的决定。通过对这些不同时间段组合中的每一个进行建模,我们可以深入了解分析对时间段变量的敏感性。我将展示不同时间段组合之间存在的不同显著性水平,并选择一对作为最佳时间段组合。我将讨论每次回顾分析的结果,但为了简洁起见,这不会是一个详尽的阐述。
最后,我将展示在积极管理的行业轮动交易系统中,采用最佳时间段组合的多元回归模型的预测能力。根据几个公认的绩效指标,该交易系统产生的绩效在风险调整基础上优于标准普尔500指数。这一成功主要归功于通过基于规则的决策过程轮动美国股票行业所带来的下行保护,同时仍然参与上行。为了进一步的分析严谨性,我向前测试了该交易策略36个月,以使用样本外数据验证分析。我将展示多元回归分析发现的趋势也存在于回测中排除的数据中。最终,我将展示一个以多元回归为补充的基于规则的交易系统,是投资于被动指数的可行替代方案。


IV. 回测表现
| 年化回报 | 7.04% |
| 波动率 | 10.06% |
| β值 | 0.474 |
| 夏普比率 | 0.55 |
| 索提诺比率 | 0.028 |
| 最大回撤 | -28.9% |
| 胜率 | 59% |
V. 完整的 Python 代码
from collections import deque
from AlgorithmImports import *
from scipy import stats
import numpy as np
import statsmodels.api as sm
class SectorRotationStrategyBasedonMultivariateRegressionAnalysis(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbols = [
'XLV',
'XLI',
'XLY',
'XLP',
'XLB',
'XLK',
'XLU',
'XLE',
'XLF'
]
# Daily close data.
self.data = {}
self.period = 21
# Regression data.
self.regression_period = 13
self.regression_data = {}
self.market = self.AddEquity('SPY', Resolution.Daily).Symbol
self.data[self.market] = deque(maxlen = self.period)
self.cash = self.AddEquity('SHY', Resolution.Daily).Symbol
for symbol in self.symbols:
self.AddEquity(symbol, Resolution.Daily)
self.regression_data[symbol] = deque(maxlen = self.regression_period)
self.data[symbol] = deque(maxlen = self.period)
self.Schedule.On(self.DateRules.MonthStart(self.symbols[0]), self.TimeRules.BeforeMarketClose(self.symbols[0]), self.Rebalance)
def OnData(self, data):
for symbol in self.symbols + [self.market]:
if symbol in data and data[symbol]:
price = data[symbol].Value
self.data[symbol].append(price)
def Rebalance(self):
long = []
for symbol in self.symbols:
# Data is ready.
if len(self.data[symbol]) == self.data[symbol].maxlen and len(self.data[self.market]) == self.data[self.market].maxlen:
# Calculate regression independent variables.
# Sector return.
sector_return = np.log( self.data[symbol][-1] / self.data[symbol][0] )
# Volatility.
volatility = Volatility(self.data[symbol])
# Drawdown.
drawdown = min(0, sector_return)
# The regression slope.
x = range(1, len(self.data[symbol]) + 1)
y = [x for x in self.data[symbol]]
slope, intercept, r_value, p_value, std_err = stats.linregress(x, y)
# Excess return.
market_return = np.log( self.data[self.market][-1] / self.data[self.market][0] )
ex_return = sector_return - market_return
self.regression_data[symbol].append( (sector_return, volatility, drawdown, slope, ex_return) )
# Regression data is ready.
if len(self.regression_data[symbol]) == self.regression_data[symbol].maxlen:
regression_data = [x for x in self.regression_data[symbol]]
returns = [x[0] for x in self.regression_data[symbol]]
volatilities = [x[1] for x in self.regression_data[symbol]]
drawdowns = [x[2] for x in self.regression_data[symbol]]
slopes = [x[3] for x in self.regression_data[symbol]]
ex_returns = [x[4] for x in self.regression_data[symbol]]
# Predict return for sector.
x = [ returns[:-1], volatilities[:-1], drawdowns[:-1], slopes[:-1], ex_returns[:-1] ]
y = returns[1:]
regression_model = MultipleLinearRegression(x, y)
alpha = regression_model.params[0]
return_predict = np.array([returns[-1], volatilities[-1], drawdowns[-1], slopes[-1], ex_returns[-1]])
betas = np.array(regression_model.params[1:])
Y = alpha + sum(np.multiply(betas, return_predict))
if Y > 0:
long.append(symbol)
# Trade execution.
self.Liquidate()
weight = 1 / len(self.symbols)
for symbol in long:
if self.Securities[symbol].Price != 0:
self.SetHoldings(symbol, weight)
cash_weight = (len(self.symbols) - len(long)) / len(self.symbols)
if self.Securities[self.cash].Price != 0:
self.SetHoldings(self.cash, cash_weight)
def Volatility(values):
values = np.array(values)
returns = (values[1:] - values[:-1]) / values[:-1]
return np.std(returns)
def MultipleLinearRegression(x, y):
x = np.array(x).T
x = sm.add_constant(x)
result = sm.OLS(endog=y, exog=x).fit()
return result