商品日历价差的趋势跟踪
登录后收藏学术论文
策略概要
该策略交易25个商品期货市场,使用“信号价差”(近期合约与次月合约)来指导“交易价差”(近期合约与一年期合约)的头寸。信号价差的50日移动平均线确定交易信号。如果信号价差超过其移动平均线,则投资者做多交易价差,否则做空,在延迟一天后建仓。头寸等权重,无杠杆,回报来自价差的两个部分。
当前端合约接近到期时,合约会被展期,确保投资组合始终持有当月年度价差。投资者持续监控和跟踪所有日历价差的移动平均线,即使不积极交易,也能在展期合约时预测未来的信号。这种方法系统地利用价差动态,利用信号趋势识别机会,同时保持严格的展期计划并避免使用杠杆来有效管理风险。
II. 策略合理性
回测表现
波动率4.09%
夏普比率0.91
胜率49%
完整 Python 代码
import numpy as np
class TrendFollowinginCommodityCalendarSpreads(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetCash(100000)
# quandl contract symbol and contract number approximately 12 months away
self.futures_last_contract = {
"CHRIS/CME_S" : "8", # Soybean Futures, Continuous Contract
"CHRIS/CME_W" : "6", # Wheat Futures, Continuous Contract
"CHRIS/CME_SM" : "9", # Soybean Meal Futures, Continuous Contract
"CHRIS/CME_BO" : "9", # Soybean Oil Futures, Continuous Contract
"CHRIS/CME_C" : "6", # Corn Futures, Continuous Contract
# "CHRIS/CME_O" : "6", # Oats Futures, Continuous Contract
"CHRIS/CME_LC" : "7", # Live Cattle Futures, Continuous Contract
"CHRIS/CME_FC" : "7", # Feeder Cattle Futures, Continuous Contract
"CHRIS/CME_LN" : "9", # Lean Hog Futures, Continuous Contract
"CHRIS/CME_GC" : "9", # Gold Futures, Continuous Contract
"CHRIS/CME_SI" : "9", # Silver Futures, Continuous Contract
# "CHRIS/CME_PL" : "7", # Platinum Futures, Continuous Contract
"CHRIS/CME_CL" : "12", # Crude Oil Futures, Continuous Contract
"CHRIS/CME_HG" : "13", # Copper Futures, Continuous Contract
# "CHRIS/CME_LB" : "7", # Random Length Lumber Futures, Continuous Contract
# "CHRIS/CME_PA" : "7", # Palladium Futures, Continuous Contract
# "CHRIS/CME_RR" : "7", # Rough Rice Futures, Continuous Contract
# "CHRIS/CME_DA" : "13", # Class III Milk Futures
"CHRIS/ICE_CC" : "6", # Cocoa Futures, Continuous Contract
"CHRIS/ICE_CT" : "6", # Cotton No. 2 Futures, Continuous Contract
"CHRIS/ICE_KC" : "6", # Coffee C Futures, Continuous Contract
"CHRIS/ICE_O" : "3", # Heating Oil Futures, Continuous Contract
"CHRIS/ICE_OJ" : "7", # Orange Juice Futures, Continuous Contract
"CHRIS/ICE_SB" : "5" # Sugar No. 11 Futures, Continuous Contract
}
self.period:int = 50
self.SetWarmUp(self.period, Resolution.Daily)
# spread data - MA is calculated out of signal spread
self.signal_spread:dict = {}
for c_sym, last_c_num in self.futures_last_contract.items():
# add #1 and #2 and one approx. year away contracts
for c_num in [1, 2, last_c_num]:
data = self.AddData(QuandlFutures, c_sym+str(c_num), Resolution.Daily)
data.SetFeeModel(CustomFeeModel(self))
data.SetLeverage(5)
self.signal_spread[c_sym] = RollingWindow[float](self.period)
def OnData(self, data):
for c_sym, last_c_num in self.futures_last_contract.items():
front_contract_sym:str = c_sym + '1'
further_contract_sym:str = c_sym + '2'
contract_1y_away_sym:str = c_sym + last_c_num
# calculate #1 and #2 spread
if front_contract_sym in data and further_contract_sym in data and contract_1y_away_sym in data and \
data[front_contract_sym] and data[further_contract_sym] and data[contract_1y_away_sym]:
front_price:float = data[front_contract_sym].Value
further_price:float = data[further_contract_sym].Value
if front_price > 0 and further_price > 0:
current_spread:float = front_price - further_price
self.signal_spread[c_sym].Add(current_spread)
if self.signal_spread[c_sym].IsReady:
spread_ma:float = np.mean([x for x in self.signal_spread[c_sym]])
weight:float = 1. / len(self.futures_last_contract)
if current_spread > spread_ma:
# long trading spread
self.SetHoldings(contract_1y_away_sym, weight)
self.SetHoldings(front_contract_sym, -weight)
else:
# short trading spread
self.SetHoldings(front_contract_sym, weight)
self.SetHoldings(contract_1y_away_sym, -weight)
else:
self.Debug(f"Price bellow 0: {front_contract_sym}:{front_price}, {further_contract_sym}:{further_price}")
else:
# check if quandl data is still comming in
if not all(self.Securities[symbol].GetLastData() and (self.Time.date() - self.Securities[symbol].GetLastData().Time.date()).days <= 5 for symbol in [front_contract_sym, further_contract_sym, contract_1y_away_sym] ):
# liquidate spread position once quandl data stopped comming in
if self.Portfolio[front_contract_sym].Invested and self.Portfolio[further_contract_sym].Invested:
self.Liquidate(front_contract_sym)
self.Liquidate(contract_1y_away_sym)
# Quandl free data
class QuandlFutures(PythonQuandl):
def __init__(self):
self.ValueColumnName = "settle"
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))