Quant Buffet放轻松,别过度思虑

商品日历价差的趋势跟踪

登录后收藏

学术论文

作者商品市场中的趋势跟踪策略与金融化的影响 [点击查看论文]

策略概要

该策略交易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"))