Abstract
In this tutorial we implement a trend following strategy on commodities futures based on a 2014 paper "Two Centuries Of Trend Following" by Y. Lempérière, C. Deremble, P. Seager, M. Potters, and J. P. Bouchaud.
Introduction
The paper highlights the existence of trends as an anomaly that contradicts the efficient market hypothesis. If financial markets are completely efficient as the hypothesis suggests, then asset price changes should be totally unpredictable. In other words, no systematic excess return based on public information should exist since asset prices ought to reflect all public information available. However observationally, trend existence in the market do exist. They make it possible to use the simple trend following strategy which states, buy when prices goes up and sell when prices goes down. Numerous academic studies have demonstrated that trend following strategies generate persistent returns over long periods of time.
The paper extends the backtest period of trend following strategies to two centuries and demonstrates statistically significant systematic excess returns on four asset classes (commodities, currencies, stock indices, and bonds). It implements a risk managed strategy that buys or sells a quantity of \(\sigma_n^{-1}\) of the underlying contract depending on the sign of \(s_n\).
The signal \(s_n(t)\) at the beginning of month \(t\) is:
\[s_n(t) = \frac{p(t-1)-\text{<}p\text{>}_{n,t-1}}{\sigma_n(t-1)}\]
where \(\text{<}p\text{>}_{n,t-1}\) is last month's exponential moving average of past prices with a decay rate equal to \(n\) months, \(p(t-1)\) is the price of last month, and \(\sigma_n(t-1)\) is last month's volatility, estimated as the exponential moving average of the absolute monthly price changes, with a decay rate equal to \(n\) months. The decay rate was set to 5 months.
Below, we will implement the above monthly-rebalanced trend following strategy on commodities futures.
Method
Step 1: Subscribe to continuous futures data
The paper selected a well-balanced commodities pool to include 7 representative contracts: Crude oil, Henry Hub Natural Gas, Corn, Wheat, Super, Live Cattle and Copper. We will add continuous futures data of these contracts in daily resolution.
from QuantConnect.Python import PythonQuandl
class ImprovedCommodityMomentumTrading(QCAlgorithm):
def initialize(self):
tickers = [Futures.grains.wheat,
Futures.grains.corn,
Futures.meats.live_cattle,
Futures.energies.CRUDE_OIL_WTI,
Futures.energies.natural_gas,
Futures.softs.sugar11,
Futures.metals.copper]
for ticker in tickers:
# subscribe to continuous future data and set desired leverage
future = self.add_future(ticker,
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(3)
Step 2: Create a SymbolData class to store and update the number of contracts to trade for each security
In Initialize, we create a dictionary to store the SymbolData
object for each security. The strategy is designed to trade monthly, so we will create a monthly consolidator for each security as well. When a new monthly data becomes available, the consolidator calls the consolidator event handler CalendarHandler
. Within this event handler, we will update the SymbolData
object with the freshly received monthly data.
def initialize(self):
# Container to store the SymbolData object for each security
self.data = {}
for ticker in tickers:
# subscribe to continuous future data and set desired leverage
future = self.add_future(ticker,
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(3)
# Create a monthly consolidator for each security
self.consolidate(future.symbol, CalendarType.MONTHLY, self.calendar_handler)
# Create a SymbolData object for each security to store relevant indicators
# and calculate quantity of contracts to Buy/Sell
self.data[future.symbol] = SymbolData(future)
def calendar_handler(self, bar):
'''
Event Handler that updates the SymbolData object for each security when a new monthly bar becomes available
'''
self.data[bar.symbol].update(bar)
The SymbolData
class is designed to contain everything we need for calculating how many contracts to buy/sell at the beginning of each month. LEAN provides helpful indicators to get the exponential moving average and momentum. We also hold the pointer of mapped contract and contract multiplier as properties of the object for later ordering use. The Introduction section above detailed the formula for calculating the number of contracts to buy/sell. We implement the formula in the Update
function.
class SymbolData:
'''
Contains the relevant indicators used to calculate number of contracts to Buy/Sell
'''
def __init__(self, future):
self.future = future
self.ema = ExponentialMovingAverage("MonthEMA", 5)
# Volatility estimation is defined as the EMA of absolute monthly price changes
# Use Momentum indicator to get absolute monthly price changes
# Then use the IndicatorExtensions.EMA and pass the momentum indicator values to get the volatility
self.mom = Momentum("MonthMOM", 1)
# Note: self.vol will automatically be updated with self.mom
self.vol = IndicatorExtensions.EMA(self.mom, 5)
self.quantity = 0
@property
def mapped(self):
return self.future.mapped
@property
def contract_multiplier(self):
return self.future.symbol_properties.contract_multiplier
def update(self, bar):
self.ema.update(bar.time, bar.value)
self.mom.update(bar.time, bar.value)
if self.ema.is_ready and self.vol.is_ready:
# Equation 1 in [1]
signal = (bar.value - self.ema.current.value) / self.vol.current.value
# Equation 2 in [1], assuming price change is the same next step
self.quantity = np.sign(signal) * self.mom.current.value / abs(self.vol.current.value)
return self.quantity != 0
Step 3: Buy and Sell at the beginning of each month
Now we’ll place orders based on the quantity of contracts calculated from previous month stored in the SymbolData
object. Note that we warm up the algorithm with 150 days of data to allow the algorithm to execute trades on the start date.
def on_data(self, data):
'''
Buy/Sell security every month
'''
if self.is_warming_up:
return
for symbol, symbolData in self.data.items():
if not data.bars.contains_key(symbol) or self.time < self.next_rebalance[symbol]:
continue
if symbolData.quantity != 0:
# divided by the contract multiplier
self.market_order(symbolData.mapped, self.leverage * symbolData.quantity // symbolData.contract_multiplier)
self.next_rebalance[symbol] = Expiry.end_of_month(self.time)
Summary
For the backtest period (January 2010 to January 2020), the trend following strategy produced a Sharpe ratio of -0.131, compared to SPY’s Sharpe ratio of 0.805, while information ratio is -0.767 and alpha is 0.003. The performance of suggests that the trend information no longer provide extra information for alpha profit generation, unlike the period of the paper tested on.
Trend following was considered one of the best information source of excess return, especially via Futures assets, in the 60s-70s. However, by the technological progression particularly from the last decade, this simple information is absorbed in miliseconds by the more efficient market.
Reference
- Y. Lempérière, C. Deremble, P. Seager, M. Potters, J. P. Bouchaud (2014). Two centuries of trend following. Online Copy
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.
Alethea Lin
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!