
“该策略根据美联储政策调整投资组合:在紧缩阶段增加商品配置,在扩张阶段持有股票和债券的均值-方差高效组合,灵活适应经济条件的变化。”
资产类别:交易所交易基金(ETFs)、期货 | 地区:美国 | 频率:每日 | 市场:商品 | 关键词:商品
I. 策略概述
该策略通过追踪GSCI指数的ETF获取商品敞口。根据美联储的利率政策,将市场分为扩张阶段(降息)和紧缩阶段(加息)。在扩张阶段,投资者持有包括美国股票、EAFE(欧洲、澳大利亚和远东地区)股票、国债和公司债券的均值-方差高效组合,这些资产通过ETF追踪。在紧缩阶段,增加商品配置,并重新计算均值-方差高效组合的权重。风险和收益指标专门针对紧缩时期设计,以反映投资组合对美联储货币政策收紧的调整能力。该策略通过动态资产配置适应当前经济条件。
II. 策略合理性
学术研究表明,商品期货是有效的通胀对冲工具,而在通胀上升时期,股票和债券通常表现不佳。美联储提高利率表明其在努力抑制通胀,这为投资者提供了增加商品期货配置的信号。在这种情况下,商品期货可以通过提高投资组合的风险调整回报率,帮助投资者应对市场变化。
III. 论文来源
Tactical Asset Allocation and Commodity Futures [点击浏览原文]
- 作者:Jensen, Mercer
- 机构:克雷顿大学(Creighton University),德克萨斯理工大学(Texas Tech University)
<摘要>
本文研究了在传统投资组合(包括美国股票、外国股票、公司债券和国库券)中添加管理型和非管理型商品期货的分散化收益。在1973年至1999年期间,研究发现商品期货显著提升了投资组合的表现,其中管理型期货的收益最大。这些收益几乎完全出现在美联储采取紧缩货币政策的时期。研究表明,金属和农产品期货合约为投资者提供了最显著的分散化收益。总体而言,研究结果表明,投资者应根据货币条件评估商品期货在投资组合中的最优配置,并判断是否应在某种特定合约上建立多头或空头头寸。


IV. 回测表现
| 年化收益率 | 20.46% |
| 波动率 | 13.86% |
| Beta | 0.453 |
| 夏普比率 | 1.48 |
| 索提诺比率 | N/A |
| 最大回撤 | N/A |
| 胜率 | 51% |
V. 完整python代码
from math import isnan
from AlgorithmImports import *
import pandas as pd
import numpy as np
from scipy.optimize import minimize
class CommoditiesTimingbasedonaMonetaryConditions(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2004, 1, 1)
self.SetCash(100000)
self.data = {} # daily closes
period = 12 * 21
self.SetWarmUp(period)
self.symbols = ["SPY", "EFA", "IEF", "LQD"]
self.commodities = ["DBC"]
for symbol in self.symbols + self.commodities:
self.AddEquity(symbol, Resolution.Daily)
self.data[symbol] = RollingWindow[float](period)
# changes in FED policy from restrictive to expansive
dates_str = ["24.08.1999", "03.01.2001", "30.06.2004", "18.09.2007", "14.12.2016", "31.7.2019"]
self.dates = [datetime.strptime(x, "%d.%m.%Y").date() for x in dates_str] # datetime type
# import quandl federal rate data
self.target_rate = self.AddData(QuandlValue, 'FRED/DFEDTARU', Resolution.Daily).Symbol
self.restrictive_flag = True # from start of the algorithm
self.last_target_rate = None
self.external_restrictive_flag = None
self.recent_month = -1
def OnData(self, data):
if self.target_rate in data and data[self.target_rate]:
curr_target_rate = data[self.target_rate].Value
restrictive_flag = None
# switch to external data source, when self.dates ends
if self.Time.date() > self.dates[-1]:
if self.last_target_rate:
if curr_target_rate > self.last_target_rate:
restrictive_flag = True
elif curr_target_rate < self.last_target_rate:
restrictive_flag = False
if restrictive_flag is not None:
self.external_restrictive_flag = restrictive_flag
self.last_target_rate = curr_target_rate
# store daily price data
for symbol in self.symbols + self.commodities:
if symbol in data and data[symbol]:
price = data[symbol].Value
self.data[symbol].Add(price)
if self.recent_month == self.Time.month:
return
self.recent_month = self.Time.month
self.Liquidate()
# if external data is present, trade out of it
if self.external_restrictive_flag is not None:
restrictive_flag = self.external_restrictive_flag
# stop backtest once quandl target rate data stops
if self.Securities[self.target_rate].GetLastData() and (self.Time.date() - self.Securities[self.target_rate].GetLastData().Time.date()).days > 5:
self.Liquidate()
return
else:
restrictive_flag = self.restrictive_flag
symbols = [x for x in self.symbols]
if restrictive_flag:
symbols += self.commodities
# construct dataframe
data = {}
for symbol in symbols:
if self.data[symbol].IsReady:
data[symbol] = [x for x in self.data[symbol]][::-1]
if len(data) != 0:
df_price = pd.dataframe(data,columns=data.keys())
daily_return = (df_price / df_price.shift(1) - 1).dropna()
a = PortfolioOptimization(daily_return, 0, len(data))
opt_weight = a.opt_portfolio()
if isnan(sum(opt_weight)): return
for i in range(len(data)):
if opt_weight[i] >= 0.001:
self.SetHoldings(df_price.columns[i], opt_weight[i])
else:
if self.Portfolio.Invested:
self.Liquidate()
class PortfolioOptimization(object):
def __init__(self, df_return, risk_free_rate, num_assets):
self.daily_return = df_return
self.risk_free_rate = risk_free_rate
self.n = num_assets # numbers of risk assets in portfolio
self.target_vol = 0.05
def annual_port_return(self, weights):
# calculate the annual return of portfolio
return np.sum(self.daily_return.mean() * weights) * 252
def annual_port_vol(self, weights):
# calculate the annual volatility of portfolio
return np.sqrt(np.dot(weights.T, np.dot(self.daily_return.cov() * 252, weights)))
def min_func(self, weights):
# method 1: maximize sharp ratio
return - self.annual_port_return(weights) / self.annual_port_vol(weights)
# method 2: maximize the return with target volatility
#return - self.annual_port_return(weights) / self.target_vol
def opt_portfolio(self):
# maximize the sharpe ratio to find the optimal weights
cons = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
bnds = tuple((0, 1) for x in range(2)) + tuple((0, 0.25) for x in range(self.n - 2))
opt = minimize(self.min_func, # object function
np.array(self.n * [1. / self.n]), # initial value
method='SLSQP', # optimization method
bounds=bnds, # bounds for variables
constraints=cons) # constraint conditions
opt_weights = opt['x']
return opt_weights
# Quandl "value" data
class QuandlValue(PythonQuandl):
def __init__(self):
self.ValueColumnName = 'Value'