Overall Statistics |
Total Trades 1267 Average Win 1.44% Average Loss -1.33% Compounding Annual Return 1.815% Drawdown 24.800% Expectancy 0.041 Net Profit 23.717% Sharpe Ratio 0.209 Probabilistic Sharpe Ratio 0.051% Loss Rate 50% Win Rate 50% Profit-Loss Ratio 1.09 Alpha 0.016 Beta -0.004 Annual Standard Deviation 0.073 Annual Variance 0.005 Information Ratio -0.605 Tracking Error 0.159 Treynor Ratio -4.001 Total Fees $10483.96 Estimated Strategy Capacity $0 Lowest Capacity Asset CHRIS/CME_LC2.QuandlFutures 2S |
# https://quantpedia.com/strategies/trading-commodity-calendar-spreads/ # # The investment universe consists of 20 commodity futures (first 12 months for each commodity are used for signal generation and trading). # Each month, the investor determines the shape of the futures curve for each commodity by taking the difference of the first five contracts, # sums the differences and then takes an average of the sum (if the result is positive, then the curve is in backwardation, and if negative, # then the curve is in contango). # In the case of backwardation, the investor takes a long position into the contract ‘most’ backwarded (he does it by taking the largest value # of differenced contracts, and once the contracts have been determined with the largest difference in price, in the case of backwardation a # long position is taken onto the further contract). The short position is determined by taking the smallest value of differenced contracts. # When the curve is in contango, the process is the same but reversed. The investor takes a short position into the largest difference and # a long position into the smallest difference. The portfolio is equally weighted between 20 commodities and rebalanced once a month. import numpy as np class TradingCommodityCalendarSpreads(QCAlgorithm): def Initialize(self): self.SetStartDate(2010, 1, 1) self.SetCash(500000) # 1st contract and the number of contracts to approximately 12 months away. self.contracts = { # "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_NG" : 12, # Natural Gas (Henry Hub) Physical Futures, Continuous Contract # "CHRIS/CME_PA" : 7, # Palladium Futures, Continuous Contract # "CHRIS/CME_RR" : 7, # Rough Rice Futures, Continuous Contract # "CHRIS/CME_CU" : 13, # Chicago Ethanol (Platts) Futures # "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" : 13, # Heating Oil Futures, Continuous Contract # "CHRIS/ICE_OJ" : 7, # Orange Juice Futures, Continuous Contract # "CHRIS/ICE_SB" : 5 # Sugar No. 11 Futures, Continuous Contract } # CONTRACT_VALUE = {} # CONTRACT_VALUE['CHRIS/CME_PL1'] = ["PLATIUM", 50,0.10,1] # CONTRACT_VALUE['CHRIS/CME_PL2'] = ["PLATIUM", 50,0.10,1] # CONTRACT_VALUE['CHRIS/CME_PL3'] = ["PLATIUM", 50,0.10,1] # CONTRACT_VALUE['CHRIS/CME_PL4'] = ["PLATIUM", 50,0.10,1] # CONTRACT_VALUE['CHRIS/CME_PL5'] = ["PLATIUM", 50,0.10,1] # CONTRACT_VALUE['CHRIS/CME_PL6'] = ["PLATIUM", 50,0.10,1] # CONTRACT_VALUE['CHRIS/CME_PL7'] = ["PLATIUM", 50,0.10,1] self.rebalance_flag = True for future, future_count in self.contracts.items(): for index in range(1, future_count + 1): contract = future + str(index) data = self.AddData(QuandlFutures, contract, Resolution.Daily) data.SetFeeModel(CustomFeeModel(self)) data.SetLeverage(5) self.Schedule.On(self.DateRules.MonthStart('CHRIS/CME_LC1'), self.TimeRules.AfterMarketOpen('CHRIS/CME_LC1'), self.Rebalance) def Rebalance(self): self.rebalance_flag = True def OnData(self, data): if not self.rebalance_flag: return self.rebalance_flag = False long = [] short = [] for future, future_count in self.contracts.items(): # curve_shape = sum( np.diff( [ self.Securities[future + str(index)].Price for index in range(1, 6) if self.Securities.ContainsKey(future + str(index)) ] ) ) curve_shape = sum( np.diff( [ data[future + str(index)].Value for index in range(1, 6) if (future + str(index)) in data ] ) ) curve_shape /= future_count if curve_shape != 0: # diff = np.diff( [ self.Securities[future + str(index)].Price for index in range(1, future_count + 1) if self.Securities.ContainsKey(future + str(index)) ] ) diff = np.diff( [ data[future + str(index)].Value for index in range(1, future_count + 1) if (future + str(index)) in data ] ) abs_diff = [abs(x) for x in diff] max_diff_index = abs_diff.index(max(abs_diff)) min_diff_index = abs_diff.index(min(abs_diff)) # Offset index by 2 to get right symbols to trade. max_diff_index += 2 min_diff_index += 2 if curve_shape > 0: # Backwardation. long.append(future + str(max_diff_index)) short.append(future + str(min_diff_index)) else: # Contango. long.append(future + str(min_diff_index)) short.append(future + str(max_diff_index)) # Trade execution. invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested] for symbol in invested: if symbol not in long + short: self.Liquidate(symbol) weight = 1 / len(self.contracts) for symbol in long: if self.Securities[symbol].Price != 0: self.SetHoldings(symbol, weight) for symbol in short: if self.Securities[symbol].Price != 0: self.SetHoldings(symbol, -weight) # 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"))