Introduction
Commodity Futures are excellent portfolio diversifiers and some of them are an effective hedge against inflation. This algorithm will explore the momentum effect in commodity Futures with the momentum return.
Method
As the strategy needs the continuous Futures contract, we import the custom data from Quandl. We create a universe of tradable commodity Futures from all available commodity Futures traded on CME. They are all liquid and active continuous contracts #1. The Futures are mapped by largest open interest contract, while adjusted by backward ratio. The data resolution is daily.
The first step is importing the data.
self.symbols = [
Futures.dairy.cash_settled_butter,
Futures.dairy.cash_settled_cheese,
Futures.dairy.class_i_i_i_milk,
Futures.dairy.dry_whey,
Futures.dairy.class_i_v_milk,
Futures.dairy.nonfat_dry_milk,
Futures.meats.live_cattle,
Futures.meats.feeder_cattle,
Futures.meats.lean_hogs,
Futures.forestry.random_length_lumber
]
for symbol in self.symbols:
future = self.add_future(symbol,
resolution = Resolution.DAILY,
extended_market_hours = True,
data_normalization_mode = DataNormalizationMode.BACKWARDS_RATIO,
data_mapping_mode = DataMappingMode.OPEN_INTEREST,
contract_depth_offset = 0
)
future.set_leverage(1)
We use the indicator RateOfChange(period)
to simulate the momentum return with a period of 12 months. Then it is warmed up by the method WarmUpIndicator(Symbol, Indicator, Resolution)
. All Futures' indicators, and their respective informations (e.g. mapped contract, contract multiplier, updating method of ROC indicator) are stored in a SymbolData
class object, and save within a dictionary self.data
.
self.data = {}
for symbol in self.symbols:
self.data[future.symbol] = SymbolData(self, future, period)
class SymbolData:
def __init__(self, algorithm, future, period):
self._future = future
self.symbol = future.symbol
self.ROC = RateOfChange(period)
# warm up indicator
algorithm.warm_up_indicator(self.symbol, self.ROC, Resolution.DAILY)
@property
def is_ready(self):
return self.ROC.is_ready and self._future.mapped is not None
@property
def value(self):
return self.ROC.current.value
@property
def mapped(self):
return self._future.mapped
@property
def multiplier(self):
return self._future.symbol_properties.contract_multiplier
def update(self, bar):
self.ROC.update(bar.end_time, bar.close)
In OnData(self, data)
, indicators for all Futures contracts are updated every day with the close price.
def on_data(self, slice):
# Update the indicator value every day
for symbol, symbol_data in self.data.items():
if slice.bars.contains_key(symbol):
symbol_data.update(slice.bars[symbol])
We rank the contracts by the last 12-month return and divide them into quintiles. In the trading part, the algorithm goes long on the quintile with the highest momentum return and goes short on the quintile with the lowest momentum return.
def initialize(self):
# Rebalance the portfolio every month
self.rebalance = self.time
def on_data(self, slice):
if self.rebalance <= self.time:
# sorted futures by 12-month return reversely
self.sorted_roc = sorted([x for x in self.data.values() if x.is_ready], key = lambda x: x.value, reverse=True)
number_futures = int(0.25*len(self.sorted_roc))
if number_futures == 0: return
self.long = [x for x in self.sorted_roc[:number_futures]]
self.short = [x for x in self.sorted_roc[-number_futures:]]
for symbol in self.portfolio.keys:
# liquidate the futures which is no longer in the trading list
if self.portfolio[symbol].invested and symbol not in [x.mapped for x in self.long + self.short]:
self.liquidate(symbol)
for long in self.long:
qty = self.calculate_order_quantity(long.mapped, 0.5/number_futures)
self.market_order(long.mapped, qty // long.multiplier)
for short in self.short:
qty = self.calculate_order_quantity(short.mapped, -0.5/number_futures)
self.market_order(short.mapped, qty // short.multiplier)
self.rebalance = Expiry.end_of_month(self.time)
Derek Melchin
See the attached backtest for an updated version of the algorithm with the following changes:
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
Jing Wu
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!