“该策略涵盖S&P 500指数和一个月期国库券。首先,构建黄金-石油价格比(GO)预测器,将黄金价格除以石油价格并取自然对数。然后,用过去240个月数据进行回归分析,预测S&P 500的超额回报率。根据回归预测和方差,计算最优S&P 500分配比例(0%至150%),剩余资金投资国库券。投资比例每月根据新预测调整,风险厌恶系数为3。”
资产类别:债券,ETF,期货 | 区域:美国 | 频率:每月 | 市场:债券,股权 | 关键词:黄金,石油,股票回报
策略概述
投资范围包括S&P 500指数和一个月期国库券。首先,构建黄金-石油价格比(GO)预测器,方法是将黄金价格除以石油价格并取自然对数。其次,使用最近240个月的月度观察样本,将S&P 500指数(包括股息)的对数回报率(相对于一个月期国库券的超额回报率,t+1月的因变量)对t月的GO值(自变量)进行回归分析。第三,在t月末,使用估算的回归模型预测S&P 500在t+1月的超额回报率。第四,在t月末,计算t+1月期间S&P 500指数的最优投资组合分配,计算公式为:S&P 500分配比例 = (1 / 风险厌恶系数) * (S&P 500超额回报率的回归预测值 / S&P 500超额回报率方差的预测值)。S&P 500超额回报率的方差预测基于过去十年的滚动回报窗口计算,使用风险厌恶系数为3。S&P 500的投资比例限制在0%到150%之间。剩余的资金则分配到一个月期国库券。每月计算新的最优投资组合权重。
策略合理性
黄金-石油比(GO)预测未来整体股票回报的经济解释有两个方面。首先,估值模型表明,资产价格应等于预期贴现现金流,这意味着资产价格由未来预期现金流和折现率决定(Cochrane,2011)。因此,GO预测整体回报的能力可能源于现金流渠道、折现率渠道或两者兼具。通过Campbell(1991)和Campbell及Ammer(1993)的向量自回归(VAR)框架进行的股票回报分解显示,GO的预测能力主要来自于对整体现金流新闻的预期。其次,GO已被证明是经济变量的有力预测指标。具体而言,GO负相关且显著预测违约利差、金融压力以及宏观和金融不确定性。因此,较高的GO表明更好的经济状况。
论文来源
Gold price ratios and aggregate stock returns [点击浏览原文]
- 方彤,山东大学经济学院
<摘要>
我们发现大多数黄金价格比,代表黄金的相对估值,能够正向且显著地预测整体股票回报。然而,在控制Welch和Goyal(2008)描述的一系列回报预测因子后,这些比率未能表现出显著的预测能力,除黄金-石油价格比(GO)外。GO是最强的预测指标。一标准差的增加与下个月年化超额回报率增加6.60%相关。GO在样本外的R^2和效用方面表现出最显著的预测能力。


回测表现
| 年化收益率 | 7.02% |
| 波动率 | 9.7% |
| Beta | 0.498 |
| 夏普比率 | 0.72 |
| 索提诺比率 | 0.14 |
| 最大回撤 | N/A |
| 胜率 | 62% |
完整python代码
from AlgorithmImports import *
import statsmodels.api as sm
import data_tools
# endregion
class GoldToOilRatioPredictsAggregateStockReturns(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.min_monthly_prices:int = 15
self.regression_period:int = 5 * 12 + 1 # need n months of data
self.min_market_alloc:float = 0.
self.max_market_alloc:float = 1.5
self.risk_aversion_coefficient:int = 3
self.spy_daily_prices:list[float] = []
self.latest_go_predictor:float = None
self.regression_data:RegressionData = data_tools.RegressionData(self.regression_period)
security = self.AddEquity('SPY', Resolution.Daily)
security.SetLeverage(5)
self.spy_symbol:Symbol = security.Symbol
security = self.AddEquity('BIL', Resolution.Daily)
security.SetLeverage(5)
self.bil_symbol:Symbol = security.Symbol
self.oil_symbol:Symbol = self.AddCfd('WTICOUSD', Resolution.Daily).Symbol
self.gold_symbol:Symbol = self.AddCfd('XAUUSD', Resolution.Daily).Symbol
self.recent_month:int = -1
def OnData(self, data: Slice):
# rebalance monthly
if self.recent_month != self.Time.month:
self.recent_month = self.Time.month
if len(self.spy_daily_prices) >= self.min_monthly_prices and self.latest_go_predictor:
monthly_return:float = (self.spy_daily_prices[-1] - self.spy_daily_prices[0]) / self.spy_daily_prices[0]
self.regression_data.update(monthly_return, self.latest_go_predictor)
if self.regression_data.is_ready():
x_train, x_predict = self.regression_data.get_x_data()
y_train:list[float] = self.regression_data.get_y_data()
regression_model = self.MultipleLinearRegression(x_train, y_train)
market_return_prediction:float = regression_model.predict([1, x_predict])[0]
sse:float = np.sum(regression_model.resid ** 2) # regression_model.ssr
variance:float = sse / ((self.regression_period - 1) - 2)
market_allocation:float = (1 / self.risk_aversion_coefficient) * (market_return_prediction / variance)
market_allocation:float = max(self.min_market_alloc, min(market_allocation, self.max_market_alloc))
if self.bil_symbol in data and self.spy_symbol in data and data[self.bil_symbol] and data[self.spy_symbol]:
self.SetHoldings(self.spy_symbol, market_allocation)
self.SetHoldings(self.bil_symbol, 1 - market_allocation)
else:
# reset regresion data, because they stopped being consecutive
self.regression_data.reset_data()
self.Liquidate()
# reset
self.latest_go_predictor = None
self.spy_daily_prices.clear()
# update GO predictor
if self.oil_symbol in data and self.gold_symbol in data and data[self.oil_symbol] and data[self.gold_symbol]:
oil_price:float = data[self.oil_symbol].Value
gold_price:float = data[self.gold_symbol].Value
go_predictor:float = np.log(gold_price / oil_price)
self.latest_go_predictor = go_predictor
# update spy daily prices
if self.spy_symbol in data and data[self.spy_symbol]:
price:float = data[self.spy_symbol].Value
self.spy_daily_prices.append(price)
def MultipleLinearRegression(self, x:list, y:list):
x:np.array = np.array(x).T
x = sm.add_constant(x)
result = sm.OLS(endog=y, exog=x).fit()
return result
