Overall Statistics |
Total Orders 719 Average Win 0.26% Average Loss -0.18% Compounding Annual Return 0.837% Drawdown 5.700% Expectancy 0.147 Start Equity 1000000 End Equity 1087041.18 Net Profit 8.704% Sharpe Ratio -0.251 Sortino Ratio -0.26 Probabilistic Sharpe Ratio 0.203% Loss Rate 53% Win Rate 47% Profit-Loss Ratio 1.43 Alpha -0.002 Beta -0.046 Annual Standard Deviation 0.025 Annual Variance 0.001 Information Ratio -0.714 Tracking Error 0.13 Treynor Ratio 0.134 Total Fees $2079.74 Estimated Strategy Capacity $1800000.00 Lowest Capacity Asset NG XBLBSKM1RSN5 Portfolio Turnover 0.89% |
#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.set_start_date(2010, 1, 1) self.set_end_date(2020, 1, 1) self.set_cash(1000000) self._leverage = 3 tickers = [Futures.Grains.WHEAT, Futures.Grains.CORN, Futures.Meats.LIVE_CATTLE, Futures.Energies.CRUDE_OIL_WTI, Futures.Energies.NATURAL_GAS, Futures.Softs.SUGAR_11, Futures.Metals.COPPER] # Container to store the SymbolData object and rebalancing timer for each security self._data = {} self._next_rebalance = {} 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(self._leverage) # 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) # Set monthly rebalance self._next_rebalance[future.symbol] = self.time # Set decay rate equal to 5 months (150 days) and warm up period self.set_warm_up(150) 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) def on_data(self, data): ''' Buy/Sell security every month ''' if self.is_warming_up: return for symbol, changed_event in data.symbol_changed_events.items(): old_symbol = changed_event.old_symbol if self.portfolio[old_symbol].invested: self.liquidate(old_symbol) for symbol, symbol_data in self._data.items(): if self.time < self._next_rebalance[symbol] or not data.bars.contains_key(symbol): continue quantity = self._leverage * symbol_data.quantity // symbol_data.contract_multiplier if quantity != 0: self.market_order(symbol_data.mapped, quantity) self._next_rebalance[symbol] = Expiry.end_of_month(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 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