“该策略投资于发达市场货币,利用美国股票尾部风险估计尾部贝塔。它买入低贝塔货币并卖出高贝塔货币,每月重新平衡并等权重。”

I. 策略概要

该策略投资于发达市场货币,包括澳大利亚、加拿大、丹麦、欧元区、香港、以色列、日本、新西兰、挪威、新加坡、韩国、瑞典、瑞士和英国。它通过构建零投资策略来衡量美国尾部风险,该策略做多CBOE PPut指数并做空标准普尔500指数,其对数回报代表美国股票尾部风险。尾部贝塔通过每个货币的月度超额回报对标准普尔500和美国股票尾部风险进行60个月滚动窗口回归来估计。货币根据尾部贝塔分为五个投资组合,买入最低贝塔的货币并卖出最高贝塔的货币。该策略等权重并每月重新平衡。

II. 策略合理性

美国股票市场显著影响国际资产定价,其中美国股票回报在预测全球股票回报方面发挥主导作用。非美国股票回报提供的额外可预测性微乎其微。美国股票的跳涨迅速反映在全球市场中,而反之则不那么突出。在美元尾部风险增加期间对美元升值的货币被视为对冲工具,使其更具吸引力并降低预期回报。该研究强调期权隐含尾部风险因子更优越,具有公开数据、高频测量以及可交易期权价格的前瞻性洞察等优势。该因子不仅反映了左尾事件的概率,还包含了对潜在跳涨幅度的信念。

III. 来源论文

US Equity Tail Risk and Currency Risk Premia [点击查看论文]

<摘要>

我们发现,从虚值标准普尔500看跌期权价格构建的美国股票尾部风险因子解释了货币超额回报的横截面变异。高度暴露于该因子的货币提供较低的货币风险溢价,因为它们在美国尾部风险增加时升值。在简化模型中,我们表明,只有当国家特定尾部风险因子包含全球风险成分时,它们才在货币回报的横截面中定价。受模型直觉和我们的实证结果的启发,我们通过买入具有高美国股票尾部贝塔的货币并卖空具有低美国尾部贝塔的货币,构建了一个新颖的全球尾部风险因子代理。该因子与美元风险因子一起,解释了货币套利和动量投资组合中横截面变异的很大一部分,并且优于文献中广泛使用的其他模型。

IV. 回测表现

年化回报4.57%
波动率8.16%
β值-0.029
夏普比率0.56
索提诺比率N/A
最大回撤N/A
胜率45%

V. 完整的 Python 代码

from AlgorithmImports import *
from collections import deque
import numpy as np
class USEquityTailRiskandCurrencyRiskPremia(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetCash(100000)
        
        self.symbols = {
                        "CME_AD1",   # Australian Dollar Futures, Continuous Contract #1
                        "CME_CD1",   # Canadian Dollar Futures, Continuous Contract #1
                        "CME_EC1",   # Euro FX Futures, Continuous Contract #1
                        "CME_JY1",   # Japanese Yen Futures, Continuous Contract #1
                        "CME_NE1",   # New Zealand Dollar Futures, Continuous Contract #1
                        "CME_SF1",    # Swiss Franc Futures, Continuous Contract #1
                        "CME_BP1"   # British Pound Futures, Continuous Contract #1
        }
        
        # Daily ROC data.
        self.data = {}
        self.period = 21
        self.SetWarmUp(self.period)
        # Regression data rolling window.
        self.regression_period = 60
        self.regression_data = {}
        self.pput_index = self.AddData(PPUT, 'PPUT', Resolution.Daily).Symbol
        self.data[self.pput_index] = self.ROC(self.pput_index, self.period, Resolution.Daily)
        
        self.market = self.AddEquity('SPY', Resolution.Daily).Symbol
        self.data[self.market] = self.ROC(self.market, self.period, Resolution.Daily)
        
        for symbol in self.symbols:
            data = self.AddData(data_tools.QuantpediaFutures, symbol, Resolution.Daily)
            data.SetFeeModel(data_tools.CustomFeeModel())
            data.SetLeverage(5)
            
            self.data[symbol] = self.ROC(symbol, self.period, Resolution.Daily)
            
            self.regression_data[symbol] = deque(maxlen = self.regression_period)
        
        self.Schedule.On(self.DateRules.MonthStart(self.market), self.TimeRules.AfterMarketOpen(self.market), self.Rebalance)
    def Rebalance(self):
        # Regression data.
        # Source: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3399980
        dol_factor_return = None
        mkt_factor_return = None
        tail_factor_return = None
        
        if self.data[self.market].IsReady and self.data[self.pput_index].IsReady:
            # To measure the US tail risk, construct a zero-investment strategy that longs the CBOE PPut index and shorts the S&P 500 index.
            tail_factor_return = (self.data[self.pput_index].Current.Value - self.data[self.market].Current.Value) / 2
            mkt_factor_return = self.data[self.market].Current.Value
        
        currency_returns = {x : self.data[x].Current.Value for x in self.symbols if self.data[x].IsReady}
        if len(currency_returns) != 0:
            dol_factor_return = np.average([x[1] for x in currency_returns.items()])
        
        # Factors for regression are ready.
        if dol_factor_return and mkt_factor_return and tail_factor_return:
            
            tail_beta = {}
            
            for symbol, symbol_return in currency_returns.items():
                reg_data = (symbol_return, dol_factor_return, mkt_factor_return, tail_factor_return)
                self.regression_data[symbol].append(reg_data)
                
                if len(self.regression_data[symbol]) == self.regression_data[symbol].maxlen:
                    symbol_returns = [x[0] for x in self.regression_data[symbol]]
                    dols = [x[1] for x in self.regression_data[symbol]]
                    mkts = [x[2] for x in self.regression_data[symbol]]
                    tails = [x[3] for x in self.regression_data[symbol]]
                    
                    # Regression.
                    x = [dols[:-1], mkts[:-1], tails[:-1]]
                    regression_model = data_tools.MultipleLinearRegression(x, symbol_returns[1:])
                    tail_beta[symbol] = regression_model.params[3]
                    
            sorted_by_beta = sorted(tail_beta.items(), key = lambda x:x[1], reverse = True)
            quintile = int(len(sorted_by_beta) / 5)
            long = [x[0] for x in sorted_by_beta[-quintile:]]
            short = [x[0] for x in sorted_by_beta[:quintile]]
        
            # Trade execution.
            long_count = len(long)
            short_count = len(short)
            
            invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
            for symbol in invested:
                if symbol not in long + short:
                    self.Liquidate(symbol)
            
            for symbol in long:
                self.SetHoldings(symbol, 1 / long_count)
            for symbol in short:
                self.SetHoldings(symbol, -1 / short_count)
                
# PPUT index.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
# Data source: http://www.cboe.com/products/strategy-benchmark-indexes/put-protection-index
class PPUT(PythonData):
    def GetSource(self, config, date, isLiveMode):
        return SubscriptionDataSource("data.quantpedia.com/backtesting_data/index/pput.csv", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
    def Reader(self, config, line, date, isLiveMode):
        data = PPUT()
        data.Symbol = config.Symbol
        
        if not line[0].isdigit(): return None
        split = line.split(';')
        
        data.Time = datetime.strptime(split[0], "%m/%d/%Y") + timedelta(days=1)
        data['close'] = float(split[1])
        data.Value = float(split[1])
        return data

发表评论

了解 Quant Buffet 的更多信息

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

继续阅读