| Overall Statistics |
|
Total Trades 636 Average Win 0.36% Average Loss -0.30% Compounding Annual Return -0.683% Drawdown 11.800% Expectancy 0.169 Net Profit -6.626% Sharpe Ratio -0.131 Probabilistic Sharpe Ratio 0.001% Loss Rate 47% Win Rate 53% Profit-Loss Ratio 1.19 Alpha 0.003 Beta -0.071 Annual Standard Deviation 0.032 Annual Variance 0.001 Information Ratio -0.767 Tracking Error 0.135 Treynor Ratio 0.059 Total Fees $1025.05 Estimated Strategy Capacity $0 Lowest Capacity Asset HG VDOL5LMM7FKT |
#region imports
from AlgorithmImports import *
#endregion
class CommodityFutureTrendFollowing(QCAlgorithm):
'''
Two Centuries of Trend Following
This paper demonstrates the existence of anomalous excess returns based on trend following strategies
across commodities, currencies, stock indices, and bonds, and over very long time scales.
Reference:
[1] Y. Lempérière, C. Deremble, P. Seager, M. Potters, J. P. Bouchaud, "Two centuries of trend following", April 15, 2014.
URL: https://arxiv.org/pdf/1404.3274.pdf
'''
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetEndDate(2020, 1, 1)
self.SetCash(1000000)
self.leverage = 3
tickers = [Futures.Grains.Wheat,
Futures.Grains.Corn,
Futures.Meats.LiveCattle,
Futures.Energies.CrudeOilWTI,
Futures.Energies.NaturalGas,
Futures.Softs.Sugar11,
Futures.Metals.Copper]
# Container to store the SymbolData object and rebalancing timer for each security
self.Data = {}
self.nextRebalance = {}
for ticker in tickers:
# subscribe to continuous future data and set desired leverage
future = self.AddFuture(ticker,
resolution = Resolution.Daily,
extendedMarketHours = True,
dataNormalizationMode = DataNormalizationMode.BackwardsRatio,
dataMappingMode = DataMappingMode.OpenInterest,
contractDepthOffset = 0
)
future.SetLeverage(self.leverage)
# Create a monthly consolidator for each security
self.Consolidate(future.Symbol, CalendarType.Monthly, self.CalendarHandler)
# 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)
# Set monthly rebalance
self.nextRebalance[future.Symbol] = self.Time
# Set decay rate equal to 5 months (150 days) and warm up period
self.SetWarmUp(150)
def CalendarHandler(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)
def OnData(self, data):
'''
Buy/Sell security every month
'''
if self.IsWarmingUp:
return
for symbol, symbolData in self.Data.items():
if self.Time < self.nextRebalance[symbol] or not data.Bars.ContainsKey(symbol):
continue
if symbolData.Quantity != 0:
# divided by the contract multiplier
self.MarketOrder(symbolData.Mapped, self.leverage * symbolData.Quantity // symbolData.ContractMultiplier)
self.nextRebalance[symbol] = Expiry.EndOfMonth(self.Time)
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 ContractMultiplier(self):
return self.future.SymbolProperties.ContractMultiplier
def Update(self, bar):
self.ema.Update(bar.Time, bar.Value)
self.mom.Update(bar.Time, bar.Value)
if self.ema.IsReady and self.vol.IsReady:
# 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