| 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"))