投资池包括所有国家的ETF,Beta值根据MSCI美国股票指数使用1年滚动窗口计算。ETF按Beta值升序排序,分为低Beta和高Beta两个投资组合。证券按Beta排名加权,每月重新平衡,保持零Beta。策略通过做多低Beta组合并做空高Beta组合实现零成本收益。简单修改,如对Beta最低和最高十分位进行操作,可能提升表现。

策略概述

投资池包括所有国家ETF。每个国家的Beta值根据MSCI美国股票指数,使用1年滚动窗口计算。然后根据Beta值将ETF按升序排序,并分配到低Beta和高Beta两个投资组合中。证券按照Beta排名进行加权,投资组合每月重新平衡。在组合形成时,两个投资组合的Beta值均调整为1。“反向押注Beta”策略是一个零成本、零Beta的组合,通过做多低Beta组合并做空高Beta组合来获取收益。还有一些简单的修改方法(如对Beta值最低的十分位做多,最高的十分位做空),可能会提高策略表现。

策略合理性

该反常现象的原因在简述中已经提到——许多投资者由于限制无法使用杠杆,唯一能实现更高回报的方式是购买更高风险的股票,这导致这些股票被高估。没有这些限制的投资者可以利用这一现象,赚取高于平均水平的回报。

论文来源

Betting Against Beta [点击浏览原文]

<摘要>

我们提出了一个包含杠杆和保证金限制的模型,这些限制因投资者和时间而异。我们的研究结果与模型的五个核心预测一致:(1) 由于受限的投资者推高了高Beta资产,高Beta资产与低Alpha相关,我们在美国股票、20个国际股票市场、国债、公司债券和期货中发现了这一点的实证证据;(2) 反向押注Beta(BAB)因子,通过杠杆做多低Beta资产和做空高Beta资产,能够产生显著的风险调整后的回报;(3) 当融资限制收紧时,BAB因子的回报较低;(4) 增加的流动性风险会压缩Beta值向1靠拢;(5) 受限较多的投资者持有风险较高的资产。

回测表现

年化收益率6.8%
波动率13.08%
Beta-0.079
夏普比率-0.159
索提诺比率-0.189
最大回撤44.7%
胜率51%

完整python代码

import numpy as np
from AlgoLib import *
from collections import deque

class BettingAgainstBetaFactorinInternationalEquities(XXX):

    def Initialize(self):
        self.SetStartDate(2002, 2, 1)
        self.SetCash(100000)

        self.countries = [
                        "EWA",  # iShares MSCI Australia Index ETF
                        "EWO",  # iShares MSCI Austria Investable Mkt Index ETF
                        "EWK",  # iShares MSCI Belgium Investable Market Index ETF
                        "EWZ",  # iShares MSCI Brazil Index ETF
                        "EWC",  # iShares MSCI Canada Index ETF
                        "FXI",  # iShares China Large-Cap ETF
                        "EWQ",  # iShares MSCI France Index ETF
                        "EWG",  # iShares MSCI Germany ETF 
                        "EWH",  # iShares MSCI Hong Kong Index ETF
                        "EWI",  # iShares MSCI Italy Index ETF
                        "EWJ",  # iShares MSCI Japan Index ETF
                        "EWM",  # iShares MSCI Malaysia Index ETF
                        "EWW",  # iShares MSCI Mexico Inv. Mt. Idx
                        "EWN",  # iShares MSCI Netherlands Index ETF
                        "EWS",  # iShares MSCI Singapore Index ETF
                        "EZA",  # iShares MSCI South Africe Index ETF
                        "EWY",  # iShares MSCI South Korea ETF
                        "EWP",  # iShares MSCI Spain Index ETF
                        "EWD",  # iShares MSCI Sweden Index ETF
                        "EWL",  # iShares MSCI Switzerland Index ETF
                        "EWT",  # iShares MSCI Taiwan Index ETF
                        "THD",  # iShares MSCI Thailand Index ETF
                        "EWU",  # iShares MSCI United Kingdom Index ETF
            ]
        
        self.leverage_cap = 5
        
        # Daily price data.
        self.data = {}
        self.period = 12 * 21
        
        self.symbol = 'SPY'
        
        for symbol in self.countries + [self.symbol]:
            data = self.AddEquity(symbol, Resolution.Daily)
            data.SetFeeModel(CustomFeeModel())
            data.SetLeverage(15)
            
            self.data[symbol] = RollingWindow[float](self.period)
        
        self.recent_month = -1

    def OnData(self, data):
        for symbol in self.data:
            symbol_obj = self.Symbol(symbol)
            if symbol_obj in data.Keys:
                if data[symbol_obj]:
                    price = data[symbol_obj].Value
                    if price != 0:
                        self.data[symbol].Add(price)
        
        if self.recent_month == self.Time.month:
            return
        self.recent_month = self.Time.month
        
        beta = {}
        for symbol in self.countries:
            # Data is ready.
            if self.data[self.symbol].IsReady and self.data[symbol].IsReady and self.symbol in data and symbol in data:
                market_closes = np.array([x for x in self.data[self.symbol]])
                asset_closes = np.array([x for x in self.data[symbol]])
                    
                market_returns = (market_closes[1:] - market_closes[:-1]) / market_closes[:-1]
                asset_returns = (asset_closes[1:] - asset_closes[:-1]) / asset_closes[:-1]
                
                cov = np.cov(asset_returns, market_returns)[0][1]
                market_variance = np.var(market_returns)
                beta[symbol] = cov / market_variance
                    
        weight = {}
        if len(beta) != 0: 
            # Beta diff calc.
            beta_median = np.median([x[1] for x in beta.items()])
            
            long_diff = [(x[0], abs(beta_median - x[1])) for x in beta.items() if x[1] < beta_median]
            short_diff = [(x[0], abs(beta_median - x[1])) for x in beta.items() if x[1] > beta_median]
            
            # Beta rescale.
            long_portfolio_beta = np.mean([beta[x[0]] for x in long_diff])
            long_leverage = 1 / long_portfolio_beta

            short_portfolio_beta = np.mean([beta[x[0]] for x in short_diff])
            short_leverage = 1 / short_portfolio_beta
            
            # Cap long and short leverage.
            long_leverage = max(-self.leverage_cap, min(self.leverage_cap, long_leverage))
            short_leverage = max(-self.leverage_cap, min(self.leverage_cap, short_leverage))
            
            # self.Log(f"long: {long_leverage}; short: {short_leverage}")
            
            total_long_diff = sum([x[1] for x in long_diff])
            total_short_diff = sum([x[1] for x in short_diff])
            
            # Beta diff weighting.
            weight = {}
            for symbol, diff in long_diff:
                weight[symbol] = (diff / total_long_diff) * long_leverage
            for symbol, diff in short_diff:
                weight[symbol] = - (diff / total_short_diff) * short_leverage
        
        # Trade execution.
        invested = [x.Key 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)
                
# Custom fee model.
class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))

Leave a Reply

Discover more from Quant Buffet

Subscribe now to keep reading and get access to the full archive.

Continue reading