
“该策略涉及做空标普GSCI指数中权重降低的商品,投资组合权重与变化成正比。空头头寸在1月份调整后的第10个工作日之前平仓。”
资产类别: 差价合约、期货 | 地区: 美国 | 周期: 每日 | 市场: 大宗商品 | 关键词: GSCI指数
I. 策略概要
投资范围包括标普GSCI指数中的商品期货。11月,确定新的权重,到1月份的第5个工作日,投资者做空权重降低的商品。投资组合权重与标普GSCI指数权重的变化成正比。空头头寸在第10个工作日之前平仓。该策略基于指数的年度权重调整执行。
II. 策略合理性
标普GSCI指数重新平衡期间权重的变化导致商品期货市场出现非信息性订单流。如果市场没有摩擦且合约供应具有弹性,价格将保持不变。然而,由于套利限制,期货合约的供应是向上倾斜的。这导致重新平衡产生的非信息性订单流产生价格压力,因为市场无法立即吸收这些订单。这种价格扭曲是由于套利限制造成的,即合约供应并非完全具有弹性,并且价格受到重新平衡过程的影响。
III. 来源论文
Is the Supply Curve for Commodity Futures Contracts Upward Sloping? [点击查看论文]
- 闫(Yan)、欧文(Irwin)、桑德斯(Sanders),耶鲁大学、伊利诺伊大学厄本那-香槟分校、南伊利诺伊大学农业经济系
<摘要>
标普GSCI指数的年度重新平衡为估计商品期货合约的供应曲线形状提供了一种新颖而有力的识别方法。使用2004年至2017年标普GSCI指数中包含的24种商品,我们显示累计异常回报(CAR)在重新平衡期后一周的中期达到59个基点的峰值,但这种影响是暂时的,因为在接下来的一周内它会下降到接近于零。这些发现提供了明确的证据,表明商品期货合约的供应曲线在短期内是向上倾斜的,但在长期内几乎是平坦的。


IV. 回测表现
| 年化回报 | 11.1% |
| 波动率 | 18.05% |
| β值 | -0.005 |
| 夏普比率 | 0.61 |
| 索提诺比率 | N/A |
| 最大回撤 | N/A |
| 胜率 | 48% |
V. 完整的 Python 代码
from AlgorithmImports import *
from io import StringIO
import pandas as pd
#endregion
class FrontRunningSAndPGSCIIndex(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetCash(100000)
self.symbols = {}
self.trading_day = 0
self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
csv_string_file = self.Download(f'data.quantpedia.com/backtesting_data/economic/future_comodities_RPWD.csv')
# does not take first row as a header
separated_file = pd.read_csv(StringIO(csv_string_file), sep=';', header=None)
first_row = True
for row in separated_file.itertuples():
if first_row: # first row includes symbols of future comodities
first_row = False
for symbol in row[2:]:
data = self.AddData(QuantpediaFutures, symbol, Resolution.Daily)
data.SetFeeModel(CustomFeeModel())
self.symbols[symbol] = {}
else:
for (value), (symbol) in zip(row[2:], self.symbols):
self.symbols[symbol][int(row[1])] = value # second element in row is year
def OnData(self, data):
if self.Time.month == 1:
if self.trading_day == 5:
short = {}
total_weight_changed = 0
for symbol in self.symbols:
year = self.Time.year # get current year
# check if future comodity has weight for current year and year before
if (year in self.symbols[symbol]) and ((year - 1) in self.symbols[symbol]):
weight_change = float(self.symbols[symbol][year]) - float(self.symbols[symbol][year - 1])
if weight_change < 0: # if weight drops from last year, then go short on this future comodity
total_weight_changed += (-weight_change) # calculating weight change for short weighting
short[symbol] = weight_change
if len(short) != 0:
for symbol, weight_change in short.items():
w = weight_change / total_weight_changed
self.SetHoldings(symbol, w) # weight_change is already minus, so this will go short
elif self.trading_day == 10:
self.Liquidate()
self.trading_day += 1
else:
self.trading_day = 0
# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
# Quantpedia data
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/futures/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaFutures()
data.Symbol = config.Symbol
if not line[0].isdigit(): return None
split = line.split(';')
data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
data['settle'] = float(split[1])
data.Value = float(split[1])
return data