Overall Statistics |
Total Orders 941 Average Win 1.50% Average Loss -1.28% Compounding Annual Return 1.525% Drawdown 38.300% Expectancy 0.088 Start Equity 10000000 End Equity 10256420.45 Net Profit 2.564% Sharpe Ratio 0.12 Sortino Ratio 0.175 Probabilistic Sharpe Ratio 9.821% Loss Rate 50% Win Rate 50% Profit-Loss Ratio 1.16 Alpha 0.041 Beta -0.028 Annual Standard Deviation 0.303 Annual Variance 0.092 Information Ratio -0.368 Tracking Error 0.377 Treynor Ratio -1.321 Total Fees $37405.90 Estimated Strategy Capacity $510000000.00 Lowest Capacity Asset RTY XHYQYCUDLM9T Portfolio Turnover 45.63% |
#region imports from AlgorithmImports import * #endregion class ShortTermReversalWithFutures(QCAlgorithm): def initialize(self): self.set_start_date(2019, 1, 1) self.set_end_date(2020, 9, 1) self.set_cash(10000000) self.set_security_initializer(BrokerageModelSecurityInitializer( self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices))) tickers = [Futures.Currencies.CHF, Futures.Currencies.GBP, Futures.Currencies.CAD, Futures.Currencies.EUR, Futures.Indices.NASDAQ_100_E_MINI, Futures.Indices.RUSSELL_2000_E_MINI, Futures.Indices.SP_500_E_MINI, Futures.Indices.DOW_30_E_MINI] self._length = len(tickers) self._symbol_data = {} for ticker in tickers: 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(1) self._symbol_data[future.symbol] = SymbolData(self, future) def on_data(self, data): for symbol, symbol_data in self._symbol_data.items(): # Update SymbolData symbol_data.update(data) # Rollover if data.symbol_changed_events.contains_key(symbol): changed_event = data.symbol_changed_events[symbol] old_symbol = changed_event.old_symbol new_symbol = changed_event.new_symbol tag = f"Rollover - Symbol changed at {self.time}: {old_symbol} -> {new_symbol}" quantity = self.portfolio[old_symbol].quantity // self.securities[new_symbol].symbol_properties.contract_multiplier # Rolling over: to liquidate any position of the old mapped contract and switch to the newly mapped contract self.liquidate(old_symbol, tag=tag) if quantity: self.market_order(new_symbol, quantity, tag=tag) # Check if weekly consolidated bars are at their updatest if not all([symbol_data.is_ready 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.volume_return)[:int(self._length*0.5)] + sorted(self._symbol_data.values(), key=lambda x: x.open_interest_return)[-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.calculate_order_quantity(short_symbol, -0.3) // self.securities[short_symbol].symbol_properties.contract_multiplier if qty: self.market_order(short_symbol, qty) qty = self.calculate_order_quantity(long_symbol, 0.3) // self.securities[long_symbol].symbol_properties.contract_multiplier if qty: self.market_order(long_symbol, qty) class SymbolData: def __init__(self, algorithm, future): self.is_volume_ready = False self.is_oi_ready = False self.is_return_ready = False self._future = future self._symbol = future.symbol # 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._on_volume_roc_updated self._oi_roc.updated += self._on_oi_roc_updated self._return.updated += self._on_return_updated # 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.data_consolidated += self._on_trade_bar_consolidated self._oi_consolidator.data_consolidated += 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 is_ready(self): return ( self._volume_roc.is_ready and self._oi_roc.is_ready 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 volume_return(self): return self._volume_roc.current.value @property def open_interest_return(self): return self._oi_roc.current.value @property def return_(self): return self._return.current.value def update(self, data): if data.bars.contains_key(self._symbol): self._consolidator.update(data.bars[self._symbol]) oi = OpenInterest(data.time, self._symbol, self._future.open_interest) self._oi_consolidator.update(oi) def _on_volume_roc_updated(self, sender, updated): self._is_volume_ready = True def _on_oi_roc_updated(self, sender, updated): self._is_oi_ready = True def _on_return_updated(self, sender, updated): self._is_return_ready = True def _on_trade_bar_consolidated(self, sender, bar): self._volume_roc.update(bar.end_time, bar.volume) self._return.update(bar.end_time, bar.close) def _consolidation_period(self, dt): # Define a consolidation period method 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)