何时持有股票,何时持有黄金
登录后收藏学术论文
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