Overall Statistics |
Total Orders 184 Average Win 2.27% Average Loss -0.72% Compounding Annual Return 10.835% Drawdown 18.700% Expectancy 0.424 Start Equity 100000 End Equity 181633.05 Net Profit 81.633% Sharpe Ratio 0.53 Sortino Ratio 0.541 Probabilistic Sharpe Ratio 24.899% Loss Rate 66% Win Rate 34% Profit-Loss Ratio 3.17 Alpha 0.021 Beta 0.293 Annual Standard Deviation 0.1 Annual Variance 0.01 Information Ratio -0.377 Tracking Error 0.146 Treynor Ratio 0.18 Total Fees $99.73 Estimated Strategy Capacity $0 Lowest Capacity Asset CME_EC1.QuantpediaFutures 2S Portfolio Turnover 0.72% |
# https://quantpedia.com/strategies/trendfollowing-in-futures-markets/ # # The investment universe consists of 20 futures markets (4 currencies, 4 financials, 2 metals, 3 softs, 3 grains, 2 meats, and 2 energies). # The initial position size is based on the maximum amount of risk the investor is willing to take for each position. The system described # (and all simulation in the source paper) has an initial risk of 2%, i.e. if the market works against the investor, he will lose a maximum # of 2% of his equity in this position. The exposure of each position is additionally restricted to 10% of current equity. # # The selected strategy is a Donchian Channel Breakout 100 (chosen due to its lower probability of curve-fitting as it depends only on 3 parameters: # the number of days used to calculate the Donchian channel and 2 parameters in the ATR stop). The Donchian channel is formed by taking the highest # high and the lowest low of the last n (100) periods. # # Rules: # – The investor goes long/short at Stop as soon as the price crosses the upper/lower Donchian band of 100 days. # – The investor exits and reverses position as soon as the opposite Donchian band is crossed. # – He/she uses 4 times the ATR of 10 days as a parameter for the maximum risk to calculate the position size. # # Position sizing: # 2% based on the initial stop loss (based on 4 times the 10-day-ATR). # # QC implementation: # - Daily close prices are traded only. from math import ceil from AlgorithmImports import * class ReturnsSignalMomentum(QCAlgorithm): def Initialize(self): self.SetStartDate(2019, 1, 1) self.SetCash(100000) self.symbols = [ "CME_AD1", # Australian Dollar Futures, Continuous Contract #1 "CME_BP1", # British Pound Futures, Continuous Contract #1 "CME_EC1", # Euro FX Futures, Continuous Contract #1 "CME_JY1", # Japanese Yen Futures, Continuous Contract #1 "ICE_DX1", # US Dollar Index Futures, Continuous Contract #1 "CME_NQ1", # E-mini NASDAQ 100 Futures, Continuous Contract #1 "EUREX_FDAX1", # DAX Futures, Continuous Contract #1 "CME_ES1", # E-mini S&P 500 Futures, Continuous Contract #1 "CME_GC1", # Gold Futures, Continuous Contract "CME_SI1", # Silver Futures, Continuous Contract "ICE_CC1", # Cocoa Futures, Continuous Contract "ICE_KC1", # Coffee C Futures, Continuous Contract "ICE_SB1", # Sugar No. 11 Futures, Continuous Contract "CME_S1", # Soybean Futures, Continuous Contract "CME_W1", # Wheat Futures, Continuous Contract "CME_C1", # Corn Futures, Continuous Contract "CME_LC1", # Live Cattle Futures, Continuous Contract "CME_FC1", # Feeder Cattle Futures, Continuous Contract "CME_NG1", # Natural Gas (Henry Hub) Physical Futures, Continuous Contract "ICE_O1" # Heating Oil Futures, Continuous Contract ] period = 100 self.SetWarmUp(period) self.dch = {} self.atr = {} for symbol in self.symbols: data = self.AddData(QuantpediaFutures, symbol, Resolution.Daily) data.SetFeeModel(CustomFeeModel()) data.SetLeverage(10) self.dch[symbol] = self.DCH(symbol, period, Resolution.Daily) self.atr[symbol] = self.ATR(symbol, 10, Resolution.Daily) def OnData(self, data): if self.IsWarmingUp: return dollar_pos_size = {} equity = self.Portfolio.TotalPortfolioValue long = [] short = [] for symbol in self.symbols: if self.Securities[symbol].GetLastData() and (self.Time.date() - self.Securities[symbol].GetLastData().Time.date()).days >= 5: continue if symbol not in data or not data[symbol]: continue if not self.dch[symbol].IsReady or not self.atr[symbol].IsReady: continue price = data[symbol].Price atr = self.atr[symbol].Current.Value upper_band = self.dch[symbol].UpperBand.Current.Value lower_band = self.dch[symbol].LowerBand.Current.Value # Close position. if self.Portfolio[symbol].IsLong: if price < lower_band: self.Liquidate(symbol) if self.Portfolio[symbol].IsShort: if price > upper_band: self.Liquidate(symbol) # Calculate positon size. unit_size = (equity * 0.1 * 0.02) / (atr*4) if price > upper_band: if not self.Portfolio[symbol].IsLong: long.append(symbol) dollar_pos_size[symbol] = unit_size * price elif price < lower_band: if not self.Portfolio[symbol].IsShort: short.append(symbol) dollar_pos_size[symbol] = unit_size * price # Rebalance opened positions and open new ones. for symbol in long + short: percentage = dollar_pos_size[symbol] / equity if symbol in long: self.SetHoldings(symbol, percentage) if symbol in short: self.SetHoldings(symbol, -percentage) # Quantpedia data. # NOTE: IMPORTANT: Data order must be ascending (datewise) class QuantpediaFutures(PythonData): def GetSource(self, config, date, isLiveMode): return SubscriptionDataSource("data.quantpedia.com/backtesting_data/futures/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv) def Reader(self, config, line, date, isLiveMode): data = QuantpediaFutures() data.Symbol = config.Symbol if not line[0].isdigit(): return None split = line.split(';') data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1) data['back_adjusted'] = float(split[1]) data['spliced'] = float(split[2]) data.Value = float(split[1]) return data # Custom fee model. class CustomFeeModel(FeeModel): def GetOrderFee(self, parameters): fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005 return OrderFee(CashAmount(fee, "USD"))