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