Overall Statistics |
Total Trades 210 Average Win 0.84% Average Loss -0.47% Compounding Annual Return 4.709% Drawdown 9.600% Expectancy 0.245 Net Profit 8.004% Sharpe Ratio 0.402 Probabilistic Sharpe Ratio 16.781% Loss Rate 56% Win Rate 44% Profit-Loss Ratio 1.82 Alpha 0.043 Beta -0.032 Annual Standard Deviation 0.091 Annual Variance 0.008 Information Ratio -0.652 Tracking Error 0.243 Treynor Ratio -1.156 Total Fees $9338.81 Estimated Strategy Capacity $18000000000.00 Lowest Capacity Asset RTY XHYQYCUDLM9T |
#region imports from AlgorithmImports import * #endregion class ShortTermReversalWithFutures(QCAlgorithm): def Initialize(self): self.SetStartDate(2019, 1, 1) self.SetEndDate(2020, 9, 1) self.SetCash(10000000) self.tickers = [Futures.Currencies.CHF, Futures.Currencies.GBP, Futures.Currencies.CAD, Futures.Currencies.EUR, Futures.Indices.NASDAQ100EMini, Futures.Indices.Russell2000EMini, Futures.Indices.SP500EMini, Futures.Indices.Dow30EMini] self.length = len(self.tickers) self.symbol_data = {} for ticker in self.tickers: future = self.AddFuture(ticker, resolution = Resolution.Daily, extendedMarketHours = True, dataNormalizationMode = DataNormalizationMode.BackwardsRatio, dataMappingMode = DataMappingMode.OpenInterest, contractDepthOffset = 0 ) future.SetLeverage(1) self.symbol_data[future.Symbol] = SymbolData(self, future) def OnData(self, slice): for symbol, symbol_data in self.symbol_data.items(): # Update SymbolData symbol_data.Update(slice) # Rollover if slice.SymbolChangedEvents.ContainsKey(symbol): changed_event = slice.SymbolChangedEvents[symbol] old_symbol = changed_event.OldSymbol new_symbol = changed_event.NewSymbol tag = f"Rollover - Symbol changed at {self.Time}: {old_symbol} -> {new_symbol}" quantity = self.Portfolio[old_symbol].Quantity # Rolling over: to liquidate any position of the old mapped contract and switch to the newly mapped contract self.Liquidate(old_symbol, tag = tag) self.MarketOrder(new_symbol, quantity // self.Securities[new_symbol].SymbolProperties.ContractMultiplier, tag = tag) # Check if weekly consolidated bars are at their updatest if not all([symbol_data.IsReady for symbol_data in self.symbol_data.values()]): return # Flag to avoid undesired rebalance for symbol_data in self.symbol_data.values(): symbol_data._is_volume_ready = False symbol_data._is_oi_ready = False symbol_data._is_return_ready = False # Select stocks with most weekly extreme return out of lowest volume change and highest OI change trade_group = set(sorted(self.symbol_data.values(), key=lambda x: x.VolumeReturn)[:int(self.length*0.5)] + sorted(self.symbol_data.values(), key=lambda x: x.OpenInterestReturn)[-int(self.length*0.5):]) sorted_by_returns = sorted(trade_group, key=lambda x: x.Return) short_symbol = sorted_by_returns[-1].Mapped long_symbol = sorted_by_returns[0].Mapped for symbol in self.Portfolio.Keys: if self.Portfolio[symbol].Invested and symbol not in [short_symbol, long_symbol]: self.Liquidate(symbol) # Adjust for contract mulitplier for order size qty = self.CalculateOrderQuantity(short_symbol, -0.3) multiplier = self.Securities[short_symbol].SymbolProperties.ContractMultiplier self.MarketOrder(short_symbol, qty // multiplier) qty = self.CalculateOrderQuantity(long_symbol, 0.3) multiplier = self.Securities[long_symbol].SymbolProperties.ContractMultiplier self.MarketOrder(long_symbol, qty // multiplier) class SymbolData: def __init__(self, algorithm, future): self._future = future self.Symbol = future.Symbol self._is_volume_ready = False self._is_oi_ready = False self._is_return_ready = False # create ROC(1) indicator to get the volume and open interest return, and handler to update state self._volume_roc = RateOfChange(1) self._oi_roc = RateOfChange(1) self._return = RateOfChange(1) self._volume_roc.Updated += self.OnVolumeRocUpdated self._oi_roc.Updated += self.OnOiRocUpdated self._return.Updated += self.OnReturnUpdated # Create the consolidator with the consolidation period method, and handler to update ROC indicators self.consolidator = TradeBarConsolidator(self.consolidation_period) self.oi_consolidator = OpenInterestConsolidator(self.consolidation_period) self.consolidator.DataConsolidated += self.OnTradeBarConsolidated self.oi_consolidator.DataConsolidated += lambda sender, oi: self._oi_roc.Update(oi.Time, oi.Value) # warm up history = algorithm.History[TradeBar](future.Symbol, 14, Resolution.Daily) oi_history = algorithm.History[OpenInterest](future.Symbol, 14, Resolution.Daily) for bar, oi in zip(history, oi_history): self.consolidator.Update(bar) self.oi_consolidator.Update(oi) @property def IsReady(self): return self._volume_roc.IsReady and self._oi_roc.IsReady \ and self._is_volume_ready and self._is_oi_ready and self._is_return_ready @property def Mapped(self): return self._future.Mapped @property def VolumeReturn(self): return self._volume_roc.Current.Value @property def OpenInterestReturn(self): return self._oi_roc.Current.Value @property def Return(self): return self._return.Current.Value def Update(self, slice): if slice.Bars.ContainsKey(self.Symbol): self.consolidator.Update(slice.Bars[self.Symbol]) oi = OpenInterest(slice.Time, self.Symbol, self._future.OpenInterest) self.oi_consolidator.Update(oi) def OnVolumeRocUpdated(self, sender, updated): self._is_volume_ready = True def OnOiRocUpdated(self, sender, updated): self._is_oi_ready = True def OnReturnUpdated(self, sender, updated): self._is_return_ready = True def OnTradeBarConsolidated(self, sender, bar): self._volume_roc.Update(bar.EndTime, bar.Volume) self._return.Update(bar.EndTime, bar.Close) # Define a consolidation period method def consolidation_period(self, dt): period = timedelta(7) dt = dt.replace(hour=0, minute=0, second=0, microsecond=0) weekday = dt.weekday() if weekday > 2: delta = weekday - 2 elif weekday < 2: delta = weekday + 5 else: delta = 0 start = dt - timedelta(delta) return CalendarInfo(start, period)