Overall Statistics |
Total Orders 3 Average Win 0% Average Loss -7.96% Compounding Annual Return -99.941% Drawdown 19.500% Expectancy -1 Start Equity 100000 End Equity 90861.4 Net Profit -9.139% Sharpe Ratio 85.322 Sortino Ratio 0 Probabilistic Sharpe Ratio 83.314% Loss Rate 100% Win Rate 0% Profit-Loss Ratio 0 Alpha 37.489 Beta 1.817 Annual Standard Deviation 0.445 Annual Variance 0.198 Information Ratio 180.33 Tracking Error 0.209 Treynor Ratio 20.89 Total Fees $6.45 Estimated Strategy Capacity $460000000.00 Lowest Capacity Asset NQ YQYHC5L1GPA9 Portfolio Turnover 273.48% |
from AlgorithmImports import * import joblib import io import numpy as np from collections import deque from sklearn.preprocessing import StandardScaler from datetime import datetime class HMMNQRegimeStrategy(QCAlgorithm): def Initialize(self): # === SET BACKTEST PERIOD === self.SetStartDate(2025, 3, 10) # OUT-OF-SAMPLE (Week after training) self.SetEndDate(2025, 3, 14) self.SetCash(100000) # === TRAINING WINDOW for Model ID (must match QuantBook training period) === train_start = datetime(2025, 3, 3) train_end = datetime(2025, 3, 7) date_format = "%Y%m%d" self.model_id = f"hmm_qsl_nq_{train_start.strftime(date_format)}to{train_end.strftime(date_format)}_marco.pkl" # === SUBSCRIBE TO CONTINUOUS NQ FUTURES === future = self.AddFuture( Futures.Indices.NASDAQ100EMini, Resolution.Minute, extendedMarketHours=True, dataMappingMode=DataMappingMode.OpenInterest, dataNormalizationMode=DataNormalizationMode.BackwardsRatio, contractDepthOffset=0 ) future.SetFilter(timedelta(0), timedelta(days=60)) self.future_symbol = future.Symbol self.current_contract = None self.previous_contract = None # === LOAD HMM MODEL + SCALER === model_bytes = self.ObjectStore.ReadBytes(self.model_id) self.model, self.scaler = joblib.load(io.BytesIO(model_bytes)) self.Debug(f"✅ Loaded model: {self.model_id}") # === ROLLING WINDOWS FOR FEATURE ENGINEERING === self.window = deque(maxlen=31) self.close_window = deque(maxlen=11) self.high_window = deque(maxlen=1) self.low_window = deque(maxlen=1) # Track predictions for diagnostics self.regime_counts = {0: 0, 1: 0, 2: 0} self.SetWarmUp(TimeSpan.FromMinutes(31)) def OnData(self, slice: Slice): if self.IsWarmingUp: return # === HANDLE ROLLOVERS === for evt in slice.SymbolChangedEvents.Values: if evt.Symbol == self.future_symbol: self.previous_contract = evt.OldSymbol self.current_contract = evt.NewSymbol self.Debug(f"{self.Time} - Rollover: {self.previous_contract} → {self.current_contract}") self.HandleRollover() # === FIRST TIME CONTRACT SELECTION === if self.current_contract is None: if self.future_symbol not in slice.FutureChains: return chain = slice.FutureChains[self.future_symbol] contracts = sorted([c for c in chain if c.Expiry > self.Time], key=lambda c: c.Expiry) if contracts: self.current_contract = contracts[0].Symbol self.Debug(f"{self.Time} - Selected front-month: {self.current_contract}") else: self.Debug(f"{self.Time} - No valid contracts in chain") return # === GET BAR DATA === if not slice.Bars.ContainsKey(self.current_contract): self.Debug(f"{self.Time} - No bar data for {self.current_contract}") return bar = slice.Bars[self.current_contract] close = bar.Close high = bar.High low = bar.Low # === UPDATE ROLLING WINDOWS === self.window.append(close) self.close_window.append(close) self.high_window.append(high) self.low_window.append(low) if len(self.window) < 31: return # === FEATURE ENGINEERING === log_ret = np.log(self.window[-1] / self.window[-2]) log_returns = np.log(np.array(self.window)[1:] / np.array(self.window)[:-1]) vol = np.std(log_returns) slope = (self.close_window[-1] - self.close_window[0]) / 10 range_pct = (self.high_window[-1] - self.low_window[-1]) / self.close_window[-1] x = np.array([[log_ret, vol, slope, range_pct]]) x_scaled = self.scaler.transform(x) regime = self.model.predict(x_scaled)[0] self.regime_counts[regime] += 1 self.Debug(f"{self.Time} - Regime: {regime}") # === STRATEGY LOGIC: TRADE IN REGIME 0 ONLY === if regime == 0: if not self.Portfolio[self.current_contract].Invested: self.MarketOrder(self.current_contract, 1) self.Debug(f"{self.Time} - Entered LONG (Regime 0)") else: if self.Portfolio[self.current_contract].Invested: self.Liquidate(self.current_contract) self.Debug(f"{self.Time} - Exited position (Regime {regime})") def HandleRollover(self): if self.Portfolio[self.previous_contract].Invested: qty = self.Portfolio[self.previous_contract].Quantity self.Liquidate(self.previous_contract) self.MarketOrder(self.current_contract, qty) self.Debug(f"{self.Time} - Rolled over: {qty} → {self.current_contract}")