Quant Buffet放轻松,别过度思虑

基于多元回归分析的板块轮动策略

登录后收藏

学术论文

策略概要

投资范围包括9只精选行业SPDR ETF,代表标准普尔500指数的各个行业。投资者使用12个月的回顾期,通过多元回归计算自变量。根据回归结果,资金平均分配给结果为正的行业ETF,其余资金保留为现金。投资组合每月重新平衡,投资者持仓一个月。所包含的行业有医疗保健、工业、非必需消费品、必需消费品、材料、科技、公用事业、能源和金融。

II. 策略合理性

标准普尔500指数按市值加权,因此被动投资优先考虑的是规模最大的公司,而不一定是表现最好的公司。将指数按行业划分,表现最好的行业通常仅占3%。基于规则的策略可以将更多资金分配给表现最好的行业,从而提高回报。

回测表现

波动率10.06%
夏普比率0.55
索提诺比率0.028
最大回撤-28.9%
胜率59%

完整 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