“该策略涉及做空标普GSCI指数中权重降低的商品,投资组合权重与变化成正比。空头头寸在1月份调整后的第10个工作日之前平仓。”

I. 策略概要

投资范围包括标普GSCI指数中的商品期货。11月,确定新的权重,到1月份的第5个工作日,投资者做空权重降低的商品。投资组合权重与标普GSCI指数权重的变化成正比。空头头寸在第10个工作日之前平仓。该策略基于指数的年度权重调整执行。

II. 策略合理性

标普GSCI指数重新平衡期间权重的变化导致商品期货市场出现非信息性订单流。如果市场没有摩擦且合约供应具有弹性,价格将保持不变。然而,由于套利限制,期货合约的供应是向上倾斜的。这导致重新平衡产生的非信息性订单流产生价格压力,因为市场无法立即吸收这些订单。这种价格扭曲是由于套利限制造成的,即合约供应并非完全具有弹性,并且价格受到重新平衡过程的影响。

III. 来源论文

 Is the Supply Curve for Commodity Futures Contracts Upward Sloping? [点击查看论文]

<摘要>

标普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

发表评论

了解 Quant Buffet 的更多信息

立即订阅以继续阅读并访问完整档案。

继续阅读