Original paper
Abstract
This paper develops a long-short portfolio construction technique that captures the fundamentals of backwardation and contango and simultaneously deviates from the equal-weighting scheme traditionally employed in the commodity literature. We find that the sophisticated weighting schemes based on risk minimization and risk timing dominate the traditional naive equal-weight allocation and the schemes based on utility maximization. Our findings apply to the consideration of long-short portfolios based on momentum, term structure, hedging pressure, and speculative pressure. The conclusions robustly persist after accounting for transaction costs, illiquidity, data mining, various model specifications, and different sub-periods.
Keywords:Â long-short portfolios, equal weights, optimized weights, risk-timing weights
Trading rules
- Establish a selection of commodity futures for trading
- Evaluate performance of each future over the past 12 months
- Separate futures into quintiles based on performance
- Go long on top-performing quintile (highest momentum)
- Go short on bottom-performing quintile (lowest momentum)
- Rebalance positions monthly
Python code
Backtrader
import backtrader as bt
import numpy as np
class CommodityMomentum(bt.Strategy):
params = (
('lookback', 12),
('rebalance_period', 30),
)
def __init__(self):
self.future_data = {}
self.inds = {}
def prenext(self):
self.next()
def nextstart(self):
self.next()
def next(self):
if len(self.data) % self.params.rebalance_period == 0:
self.rebalance_portfolio()
def add_future_data(self, future_name, future_data):
self.future_data[future_name] = future_data
self.inds[future_name] = {}
self.inds[future_name]['momentum'] = bt.indicators.Momentum(future_data.close, period=self.params.lookback)
def rebalance_portfolio(self):
momentums = []
for future_name, future_data in self.future_data.items():
if len(future_data) < self.params.lookback:
continue
momentum_value = self.inds[future_name]['momentum'][0]
momentums.append((future_name, momentum_value))
sorted_momentums = sorted(momentums, key=lambda x: x[1])
quintile_size = len(sorted_momentums) // 5
long_futures = [future_name for future_name, _ in sorted_momentums[-quintile_size:]]
short_futures = [future_name for future_name, _ in sorted_momentums[:quintile_size]]
for future_name, future_data in self.future_data.items():
if future_name in long_futures:
self.order_target_percent(future_data, target=1.0/len(long_futures))
elif future_name in short_futures:
self.order_target_percent(future_data, target=-1.0/len(short_futures))
else:
self.order_target_percent(future_data, target=0)
cerebro = bt.Cerebro()
# Add your data feeds and set the respective names
# Example: data_feed = bt.feeds.YourDataFeed(dataname="your_data_feed")
# cerebro.adddata(data_feed, name="future_name")
strategy = cerebro.addstrategy(CommodityMomentum)
for future_name, future_data in strategy.future_data.items():
strategy.add_future_data(future_name, future_data)
cerebro.run()
Please note that you need to add your data feeds and set the respective names before running the code.