Quant Buffet放轻松,别过度思虑

何时持有股票,何时持有黄金

登录后收藏

学术论文

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

策略概要

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

II. 策略合理性

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

回测表现

波动率14%
夏普比率0.43
索提诺比率0.178
最大回撤-28%
胜率56%

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