“世俗市场指标(SMI)将席勒市盈率与黄金现货价格进行比较。该策略根据SMI跨越+1和-1阈值的情况,在超配黄金和股票之间切换。”

I. 策略概要

该策略的投资范围包括标普500指数和黄金。世俗市场指标(SMI)的构建方法是:席勒市盈率(C)除以黄金现货价格(A)。基于SMI的资产配置策略如下:当SMI向上穿过+1阈值时,超配黄金。保持超配黄金,直到SMI向下穿过-1阈值,此时切换到超配股票。保持超配股票,直到SMI再次向上穿过+1阈值,这标志着切换回黄金。该策略利用SMI,根据市场状况动态地在黄金和股票之间进行资产配置。

II. 策略合理性

该论文讨论了股票和黄金回报分布的差异,这些差异受到长期经济条件的影响。它批评了CAPE(周期性调整市盈率)作为长期市场指标,认为它未能提供及时信号,并存在参考和同质性问题。将黄金纳入分析是基于其长期的通胀对冲特性、其经通胀调整后的价格的均值回归,以及其与经济政策不确定性的相关性。为了解决这些问题,该论文引入了长期市场指标(SMI),该指标将CAPE比率与黄金现货价格进行比较。SMI提供了金融资产(股票)与实物资产(黄金)的相对比较,其中分子反映经济实力,分母反映经济疲软和不确定性。这种方法旨在更有效地为投资决策提供信息。

III. 来源论文

When to Own Stocks and When to Own Gold [点击查看论文]

<摘要>

我们表明,基于长期市场周期的动态投资组合资产配置在长期内优于股票的买入并持有投资组合,也优于黄金的买入并持有投资组合。对长期市场的客观定义能够识别投资组合适当的事前风险偏好或风险规避立场。我们使用修改后的席勒周期性调整市盈率(CAPE)并以黄金作为参考点,构建了一个我们称之为“长期市场指标(SMI)”的客观衡量标准。该SMI比席勒的CAPE比率具有略强的预测能力,因为它为长期宏观经济逆转提供了持续的阈值信号。最后,我们使用SMI创建了一个简单的决策规则,根据长期市场周期在股票和黄金之间调整资产配置。由此产生的投资组合在10年以上的持有期内,约70%的时间优于全股票投资组合和全黄金投资组合,并且约80%的时间产生更优的风险调整后表现。

IV. 回测表现

年化回报10%
波动率14%
β值0.186
夏普比率0.43
索提诺比率0.178
最大回撤-28%
胜率56%

V. 完整的 Python 代码

import numpy as np
from AlgorithmImports import *
from scipy import stats
from typing import List, Dict
import data_tools
class WhentoOwnStocksandWhentoOwnGold(QCAlgorithm):
    def Initialize(self) -> None:
        self.SetStartDate(1996, 1, 1)
        self.SetCash(100000)
        self.warmpup_period: int = 100*12*21
        data: Securities = self.AddEquity('SPY', Resolution.Daily)
        self.spy_symbol: Symbol = data.Symbol
        
        self.SetWarmUp(timedelta(self.warmpup_period), Resolution.Daily)    # Load data from 100 years ago.
        
        self.smi: List[float] = []       # SMI indicator historical values.
        self.cape_symbol: Symbol = self.AddData(data_tools.QuantpediaMonthlyData, 'SHILLER_PE_RATIO_MONTH', Resolution.Daily).Symbol
        
        data: Security = self.AddData(data_tools.QuantpediaFutures, 'CME_GC1', Resolution.Daily)
        data.SetFeeModel(data_tools.CustomFeeModel())
        self.gold_symbol: Symbol = data.Symbol
        
        stockPlot: Chart = Chart('SMI')
        stockPlot.AddSeries(Series('SMI', SeriesType.Line, 0))
        
        # self.last_month = 0
        self.rebalance_flag: bool = False
        self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
        self.Schedule.On(self.DateRules.MonthStart(self.spy_symbol), self.TimeRules.AfterMarketOpen(self.spy_symbol), self.Rebalance)
        self.settings.daily_precise_end_time = False
    def OnData(self, data: Slice) -> None:
        custom_data_last_update_date: Dict[Symbol, datetime.date] = data_tools.LastDateHandler.get_last_update_date()
        if (self.Securities[self.cape_symbol].GetLastData() and self.Time.date() > custom_data_last_update_date[self.cape_symbol]) or \
            (self.Securities[self.gold_symbol].GetLastData() and self.Time.date() > custom_data_last_update_date[self.gold_symbol]):
            self.Liquidate()
            return
        # Cape value comes at the start of the month
        if self.cape_symbol in data and data[self.cape_symbol] and self.gold_symbol in data and data[self.gold_symbol]:
            gold_price: float = data[self.gold_symbol].Value
            cape: float = data[self.cape_symbol].Value
            
            if cape == 0:
                return
            
            if gold_price == 0:
                gold_price = 19.25
                
            smi: float = cape / np.log(gold_price)
            self.Plot('SMI', 'SMI', smi)
            self.smi.append(smi)
        if not self.rebalance_flag:
            return
        # if self.spy_symbol in data and data[self.spy_symbol] and self.gold_symbol in data and data[self.gold_symbol]:
        self.rebalance_flag = False 
        if len(self.smi) != 0:
            data_points = [x for x in range(0, len(self.smi))]
            slope, intercept, r_value, p_value, std_err = stats.linregress(data_points, self.smi)
            # Linear regression - X = independent, Y = dependent
            # y = alpha + beta.x
            alpha = intercept
            beta = slope
            x = data_points[-1]
            y = alpha + (beta*x)
            
            smi = self.smi[-1]
            if self.spy_symbol in data and data[self.spy_symbol] and self.gold_symbol in data and data[self.gold_symbol]:
                if smi > y + 1*std_err:
                    self.Liquidate(self.spy_symbol)
                    self.SetHoldings(self.gold_symbol, 1)
                elif smi < y - 1*std_err:
                    self.Liquidate(self.gold_symbol)
                    self.SetHoldings(self.spy_symbol, 1)
    def Rebalance(self) -> None:
        self.rebalance_flag = True
            

发表评论

了解 Quant Buffet 的更多信息

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

继续阅读