Overall Statistics
Total Orders
414965
Average Win
0.01%
Average Loss
0.00%
Compounding Annual Return
-26.107%
Drawdown
77.400%
Expectancy
-0.204
Start Equity
10000000
End Equity
2258514.51
Net Profit
-77.415%
Sharpe Ratio
-2.68
Sortino Ratio
-4.052
Probabilistic Sharpe Ratio
0.000%
Loss Rate
83%
Win Rate
17%
Profit-Loss Ratio
3.58
Alpha
-0.211
Beta
-0.019
Annual Standard Deviation
0.079
Annual Variance
0.006
Information Ratio
-1.569
Tracking Error
0.195
Treynor Ratio
11.287
Total Fees
$1620969.78
Estimated Strategy Capacity
$24000000.00
Lowest Capacity Asset
PH R735QTJ8XC9X
Portfolio Turnover
151.65%
Drawdown Recovery
0
# from AlgorithmImports import *
# import numpy as np
# import pandas as pd
# from datetime import timedelta
# from collections import deque

# class KMRFGapTradingSimplified(QCAlgorithm):
#     """
#     Production-ready version combining KMRF Regime Detection with Gap Trading
#     Simplified for direct QuantConnect deployment
#     REVERSED: Longs become shorts and shorts become longs
#     """
    
#     def Initialize(self):
#         self.SetStartDate(2024, 1, 1)
#         self.SetEndDate(2024, 12, 1)
#         self.SetCash(10000000)
        
#         # === REGIME PARAMETERS ===
#         self.kama_period = 30
#         self.volatility_lookback = 20
        
#         # === GAP TRADING PARAMETERS ===
#         self.gap_threshold = 0.03  # 3% gap
#         self.holding_period = timedelta(days=8)
#         self.atr_period = 14
        
#         # === PORTFOLIO PARAMETERS ===
#         self.max_gross_exposure = 1.5  # 150% max
        
#         # === UNIVERSE ===
#         self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol
#         self.AddUniverseSelection(ETFConstituentsUniverseSelectionModel(self.spy))
        
#         # Add VIX for volatility regime
#         self.vix = self.AddData(CBOE, "VIX", Resolution.Daily).Symbol
        
#         # === DATA STORAGE ===
#         self.current_regime = "neutral"
#         self.regime_confidence = 0.5
#         self.kama_value = None
        
#         self.data = {}
#         self.atr_indicators = {}
#         self.long_positions = {}
#         self.short_positions = {}
        
#         # Schedule daily execution
#         self.Schedule.On(
#             self.DateRules.EveryDay(self.spy),
#             self.TimeRules.AfterMarketOpen(self.spy, 30),
#             self.ExecuteStrategy
#         )
        
#         self.SetWarmUp(60, Resolution.Daily)
        
#     def ExecuteStrategy(self):
#         """Main execution combining regime detection and gap trading"""
        
#         # Step 1: Detect Market Regime
#         self.UpdateRegime()
        
#         # Step 2: Scan for Gap Signals
#         self.ScanForGaps()
        
#         # Step 3: Execute Trades with Regime Bias
#         self.ExecuteTradesWithRegimeBias()
        
#         # Step 4: Manage Exits
#         self.ManageExits()
    
#     def UpdateRegime(self):
#         """Detect market regime using KAMA and volatility"""
        
#         # Get SPY history
#         history = self.History(self.spy, self.kama_period + 20, Resolution.Daily)
        
#         if history.empty:
#             return
        
#         closes = history['close'].values
        
#         # Calculate KAMA (trend detection)
#         kama = self.CalculateKAMA(closes)
        
#         # Determine trend
#         if kama is not None:
#             if closes[-1] > kama * 1.01:  # 1% above KAMA
#                 trend = "bullish"
#             elif closes[-1] < kama * 0.99:  # 1% below KAMA
#                 trend = "bearish"
#             else:
#                 trend = "neutral"
#         else:
#             trend = "neutral"
        
#         # Calculate volatility regime
#         returns = np.diff(closes) / closes[:-1]
#         recent_vol = np.std(returns[-10:]) if len(returns) >= 10 else 0
#         historical_vol = np.std(returns) if len(returns) > 20 else recent_vol
        
#         if recent_vol > historical_vol * 1.5:
#             vol_regime = "high"
#         else:
#             vol_regime = "low"
        
#         # Combine regimes with CONTRARIAN interpretation
#         if trend == "bullish" and vol_regime == "low":
#             # Overbought -> Bearish signal
#             self.current_regime = "bearish"
#             self.regime_confidence = 0.7
#         elif trend == "bearish" and vol_regime == "high":
#             # Oversold -> Bullish signal
#             self.current_regime = "bullish"
#             self.regime_confidence = 0.7
#         else:
#             self.current_regime = "neutral"
#             self.regime_confidence = 0.5
        
#         self.Debug(f"Regime: {self.current_regime} (Trend: {trend}, Vol: {vol_regime})")
    
#     def CalculateKAMA(self, prices):
#         """Kaufman's Adaptive Moving Average"""
#         if len(prices) < self.kama_period:
#             return None
        
#         # Efficiency Ratio
#         direction = abs(prices[-1] - prices[-self.kama_period])
#         volatility = sum(abs(prices[i] - prices[i-1]) for i in range(-self.kama_period+1, 0))
        
#         if volatility == 0:
#             er = 1
#         else:
#             er = direction / volatility
        
#         # Smoothing constant
#         fastest = 2 / (2 + 1)
#         slowest = 2 / (30 + 1)
#         sc = (er * (fastest - slowest) + slowest) ** 2
        
#         # KAMA calculation
#         if self.kama_value is None:
#             self.kama_value = prices[-1]
#         else:
#             self.kama_value = self.kama_value + sc * (prices[-1] - self.kama_value)
        
#         return self.kama_value
    
#     def ScanForGaps(self):
#         """Identify gap opportunities with regime adjustment - REVERSED"""
        
#         for kvp in self.CurrentSlice.Bars:
#             symbol = kvp.Key
#             bar = kvp.Value
            
#             # Skip SPY and other indices
#             if symbol == self.spy:
#                 continue
            
#             # Initialize data storage
#             if symbol not in self.data:
#                 self.data[symbol] = {'previous_close': None}
#                 self.atr_indicators[symbol] = self.ATR(symbol, self.atr_period)
            
#             # Check for gaps
#             if self.data[symbol]['previous_close'] is not None:
#                 prev_close = self.data[symbol]['previous_close']
#                 gap = (bar.Open - prev_close) / prev_close
                
#                 # Adjust thresholds based on regime
#                 if self.current_regime == "bullish":
#                     long_threshold = self.gap_threshold * 0.8
#                     short_threshold = self.gap_threshold * 1.2
#                 elif self.current_regime == "bearish":
#                     long_threshold = self.gap_threshold * 1.2
#                     short_threshold = self.gap_threshold * 0.8
#                 else:
#                     long_threshold = self.gap_threshold
#                     short_threshold = self.gap_threshold
                
#                 # REVERSED: Gap up -> SHORT signal
#                 if gap > long_threshold and symbol not in self.short_positions:
#                     self.short_positions[symbol] = {
#                         'entry_time': self.Time,
#                         'gap_size': gap,
#                         'entry_price': bar.Open
#                     }
#                     if symbol in self.long_positions:
#                         del self.long_positions[symbol]
                
#                 # REVERSED: Gap down -> LONG signal
#                 elif gap < -short_threshold and symbol not in self.long_positions:
#                     self.long_positions[symbol] = {
#                         'entry_time': self.Time,
#                         'gap_size': gap,
#                         'entry_price': bar.Open
#                     }
#                     if symbol in self.short_positions:
#                         del self.short_positions[symbol]
            
#             # Update previous close
#             self.data[symbol]['previous_close'] = bar.Close
    
#     def ExecuteTradesWithRegimeBias(self):
#         """Execute trades with directional portfolio bias - REVERSED"""
        
#         num_longs = len(self.long_positions)
#         num_shorts = len(self.short_positions)
#         total_positions = num_longs + num_shorts
        
#         if total_positions == 0:
#             return
        
#         # REVERSED: Calculate regime-based allocation
#         if self.current_regime == "bullish":
#             # Bullish: 30% long, 70% short (reversed)
#             long_allocation = 0.3 * self.max_gross_exposure
#             short_allocation = 0.7 * self.max_gross_exposure
#         elif self.current_regime == "bearish":
#             # Bearish: 70% long, 30% short (reversed)
#             long_allocation = 0.7 * self.max_gross_exposure
#             short_allocation = 0.3 * self.max_gross_exposure
#         else:
#             # Neutral: 50/50
#             long_allocation = 0.5 * self.max_gross_exposure
#             short_allocation = 0.5 * self.max_gross_exposure
        
#         # Execute long positions
#         if num_longs > 0:
#             weight_per_long = min(long_allocation / num_longs, 0.10)
#             for symbol in self.long_positions:
#                 self.SetHoldings(symbol, weight_per_long)
#                 self.SetStopLoss(symbol, True)
        
#         # Execute short positions
#         if num_shorts > 0:
#             weight_per_short = min(short_allocation / num_shorts, 0.10)
#             for symbol in self.short_positions:
#                 self.SetHoldings(symbol, -weight_per_short)
#                 self.SetStopLoss(symbol, False)
    
#     def SetStopLoss(self, symbol, is_long):
#         """Set ATR-based stop loss"""
#         if symbol in self.atr_indicators and self.atr_indicators[symbol].IsReady:
#             atr = self.atr_indicators[symbol].Current.Value
#             current_price = self.Securities[symbol].Price
            
#             if is_long:
#                 stop_price = current_price - 2 * atr
#                 self.StopMarketOrder(symbol, -self.Portfolio[symbol].Quantity, stop_price)
#             else:
#                 stop_price = current_price + 2 * atr
#                 self.StopMarketOrder(symbol, -self.Portfolio[symbol].Quantity, stop_price)
    
#     def ManageExits(self):
#         """Exit positions based on holding period or regime change"""
#         current_time = self.Time
        
#         # Check long positions
#         for symbol in list(self.long_positions.keys()):
#             position_info = self.long_positions[symbol]
            
#             exit = False
            
#             if current_time - position_info['entry_time'] >= self.holding_period:
#                 exit = True
                
#             if self.current_regime == "bearish" and self.regime_confidence > 0.65:
#                 exit = True
            
#             if exit:
#                 self.Liquidate(symbol)
#                 del self.long_positions[symbol]
#                 self.Debug(f"Exited long: {symbol}")
        
#         # Check short positions
#         for symbol in list(self.short_positions.keys()):
#             position_info = self.short_positions[symbol]
            
#             exit = False
            
#             if current_time - position_info['entry_time'] >= self.holding_period:
#                 exit = True
                
#             if self.current_regime == "bullish" and self.regime_confidence > 0.65:
#                 exit = True
            
#             if exit:
#                 self.Liquidate(symbol)
#                 del self.short_positions[symbol]
#                 self.Debug(f"Exited short: {symbol}")
    
#     def OnEndOfDay(self, symbol):
#         """Log daily summary"""
#         if symbol == self.spy:
#             self.Debug(f"""
#             Date: {self.Time.date()}
#             Regime: {self.current_regime} ({self.regime_confidence:.1%})
#             Longs: {len(self.long_positions)}
#             Shorts: {len(self.short_positions)}
#             """)


##########################################################################################################
from AlgorithmImports import *
import numpy as np
import pandas as pd
from datetime import timedelta
from collections import deque
from scipy import stats
from sklearn.mixture import GaussianMixture

class HMMGapTradingAlgorithm(QCAlgorithm):
    """
    Gap Trading with Hidden Markov Model Regime Detection
    Uses unsupervised learning to identify market regimes
    """
    
    def Initialize(self):
        self.SetStartDate(2020, 1, 1)
        self.SetEndDate(2024, 12, 1)
        self.SetCash(10000000)
        
        # === HMM PARAMETERS ===
        self.n_regimes = 3  # Bull, Bear, Neutral
        self.lookback_days = 100  # Days of history for HMM
        self.hmm_features_window = 20  # Window for feature calculation
        self.regime_confidence_threshold = 0.6
        
        # === GAP TRADING PARAMETERS ===
        self.gap_threshold = 0.03
        self.holding_period = timedelta(days=8)
        self.atr_period = 14
        self.max_gross_exposure = 1.5
        
        # === UNIVERSE ===
        self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol
        self.AddUniverseSelection(ETFConstituentsUniverseSelectionModel(self.spy))
        
        # === HMM MODEL STORAGE ===
        self.hmm_model = None
        self.current_regime = 1  # 0=Bear, 1=Neutral, 2=Bull
        self.regime_probabilities = np.array([0.33, 0.34, 0.33])
        self.regime_history = deque(maxlen=252)
        
        # === TRADING DATA ===
        self.data = {}
        self.atr_indicators = {}
        self.long_positions = {}
        self.short_positions = {}
        
        # === REGIME CHARACTERISTICS ===
        self.regime_stats = {
            0: {'name': 'bear', 'mean_return': -0.001, 'volatility': 0.02},
            1: {'name': 'neutral', 'mean_return': 0.0003, 'volatility': 0.01},
            2: {'name': 'bull', 'mean_return': 0.001, 'volatility': 0.015}
        }
        
        # Schedule functions
        self.Schedule.On(
            self.DateRules.EveryDay(self.spy),
            self.TimeRules.AfterMarketOpen(self.spy, 30),
            self.ExecuteStrategy
        )
        
        # Weekly HMM model update
        self.Schedule.On(
            self.DateRules.WeekStart(self.spy),
            self.TimeRules.AfterMarketOpen(self.spy, 60),
            self.TrainHMM
        )
        
        self.SetWarmUp(self.lookback_days, Resolution.Daily)
        self.Debug("HMM Gap Trading Algorithm initialized")
    
    # =====================================
    # HMM IMPLEMENTATION
    # =====================================
    
    def CalculateHMMFeatures(self, prices):
        """
        Calculate features for HMM from price data
        Returns array of [returns, log_returns, volatility, volume_ratio]
        """
        if len(prices) < 2:
            return None
        
        # Calculate returns
        returns = np.diff(prices) / prices[:-1]
        
        # Calculate log returns (more stable for HMM)
        log_returns = np.log(prices[1:] / prices[:-1])
        
        # Rolling volatility (standard deviation of returns)
        volatility = pd.Series(returns).rolling(5).std().fillna(method='bfill').values
        
        # Realized volatility (high-frequency measure)
        realized_vol = np.abs(returns)
        
        # Create feature matrix
        features = np.column_stack([returns, log_returns, volatility, realized_vol])
        
        return features
    
    def TrainHMM(self):
        """
        Train Hidden Markov Model on historical data
        Uses Gaussian Mixture Model as approximation (available in QC)
        """
        # Get historical data
        history = self.History(self.spy, self.lookback_days, Resolution.Daily)
        
        if history.empty or len(history) < self.lookback_days:
            self.Debug("Insufficient data for HMM training")
            return
        
        prices = history['close'].values
        features = self.CalculateHMMFeatures(prices)
        
        if features is None or len(features) < 20:
            return
        
        # Use Gaussian Mixture Model as HMM approximation
        # GMM can identify regimes based on return distributions
        self.hmm_model = GaussianMixture(
            n_components=self.n_regimes,
            covariance_type='full',
            max_iter=100,
            random_state=42
        )
        
        try:
            # Fit the model
            self.hmm_model.fit(features)
            
            # Get regime sequence
            regimes = self.hmm_model.predict(features)
            
            # Identify regime characteristics
            self.IdentifyRegimeCharacteristics(features, regimes)
            
            self.Debug("HMM model trained successfully")
            
        except Exception as e:
            self.Debug(f"HMM training failed: {str(e)}")
    
    def IdentifyRegimeCharacteristics(self, features, regimes):
        """
        Identify which regime is bull/bear/neutral based on returns
        """
        regime_returns = {}
        
        for i in range(self.n_regimes):
            regime_mask = (regimes == i)
            if regime_mask.any():
                # Get returns for this regime
                regime_data = features[regime_mask, 0]  # First column is returns
                mean_return = np.mean(regime_data)
                regime_returns[i] = mean_return
        
        # Sort regimes by mean return
        sorted_regimes = sorted(regime_returns.items(), key=lambda x: x[1])
        
        # Assign: lowest return = bear, highest = bull, middle = neutral
        if len(sorted_regimes) == 3:
            self.regime_mapping = {
                sorted_regimes[0][0]: 0,  # Bear
                sorted_regimes[1][0]: 1,  # Neutral
                sorted_regimes[2][0]: 2   # Bull
            }
        else:
            self.regime_mapping = {i: i for i in range(self.n_regimes)}
        
        self.Debug(f"Regime mapping: {self.regime_mapping}")
    
    def DetectCurrentRegime(self):
        """
        Detect current market regime using trained HMM
        """
        if self.hmm_model is None:
            return 1, np.array([0.33, 0.34, 0.33])  # Default to neutral
        
        # Get recent price data
        history = self.History(self.spy, self.hmm_features_window, Resolution.Daily)
        
        if history.empty:
            return self.current_regime, self.regime_probabilities
        
        prices = history['close'].values
        features = self.CalculateHMMFeatures(prices)
        
        if features is None or len(features) == 0:
            return self.current_regime, self.regime_probabilities
        
        try:
            # Predict current regime
            current_regime_raw = self.hmm_model.predict(features[-1:])
            
            # Map to our bull/bear/neutral classification
            current_regime = self.regime_mapping.get(current_regime_raw[0], 1)
            
            # Get probability distribution
            regime_probs = self.hmm_model.predict_proba(features[-1:])[0]
            
            # Reorder probabilities according to mapping
            ordered_probs = np.zeros(self.n_regimes)
            for raw_regime, mapped_regime in self.regime_mapping.items():
                ordered_probs[mapped_regime] = regime_probs[raw_regime]
            
            return current_regime, ordered_probs
            
        except Exception as e:
            self.Debug(f"Regime detection failed: {str(e)}")
            return self.current_regime, self.regime_probabilities
    
    def CalculateTransitionProbabilities(self):
        """
        Calculate regime transition probabilities from historical sequence
        """
        if len(self.regime_history) < 20:
            return None
        
        # Count transitions
        transitions = np.zeros((self.n_regimes, self.n_regimes))
        
        for i in range(len(self.regime_history) - 1):
            from_regime = self.regime_history[i]
            to_regime = self.regime_history[i + 1]
            transitions[from_regime, to_regime] += 1
        
        # Normalize to get probabilities
        for i in range(self.n_regimes):
            row_sum = transitions[i].sum()
            if row_sum > 0:
                transitions[i] /= row_sum
            else:
                transitions[i] = 1.0 / self.n_regimes
        
        return transitions
    
    # =====================================
    # MAIN STRATEGY EXECUTION
    # =====================================
    
    def ExecuteStrategy(self):
        """Main execution combining HMM regime detection with gap trading"""
        
        # Step 1: Detect current regime using HMM
        self.current_regime, self.regime_probabilities = self.DetectCurrentRegime()
        
        # Store regime history
        self.regime_history.append(self.current_regime)
        
        # Step 2: Scan for gap opportunities
        self.ScanForGaps()
        
        # Step 3: Execute trades with regime-based allocation
        self.ExecuteTradesWithRegimeBias()
        
        # Step 4: Manage existing positions
        self.ManageExits()
        
        # Log regime status
        regime_name = self.regime_stats[self.current_regime]['name']
        confidence = self.regime_probabilities[self.current_regime]
        self.Debug(f"Current regime: {regime_name} (confidence: {confidence:.2%})")
    
    def ScanForGaps(self):
        """
        Identify gap opportunities with HMM regime adjustment
        """
        for kvp in self.CurrentSlice.Bars:
            symbol = kvp.Key
            bar = kvp.Value
            
            if symbol == self.spy:
                continue
            
            # Initialize data storage
            if symbol not in self.data:
                self.data[symbol] = {'previous_close': None}
                self.atr_indicators[symbol] = self.ATR(symbol, self.atr_period)
            
            if self.data[symbol]['previous_close'] is not None:
                prev_close = self.data[symbol]['previous_close']
                gap = (bar.Open - prev_close) / prev_close
                
                # Adjust thresholds based on HMM regime
                if self.current_regime == 2:  # Bull regime
                    # More confident in upward gaps
                    long_threshold = self.gap_threshold * 0.7
                    short_threshold = self.gap_threshold * 1.3
                    
                elif self.current_regime == 0:  # Bear regime
                    # More confident in downward gaps
                    long_threshold = self.gap_threshold * 1.3
                    short_threshold = self.gap_threshold * 0.7
                    
                else:  # Neutral regime
                    long_threshold = self.gap_threshold
                    short_threshold = self.gap_threshold
                
                # Check for gap signals
                if gap > long_threshold and symbol not in self.long_positions:
                    # Additional filter: regime confidence
                    if self.current_regime != 0 or self.regime_probabilities[0] < 0.7:
                        self.long_positions[symbol] = {
                            'entry_time': self.Time,
                            'gap_size': gap,
                            'entry_price': bar.Open,
                            'entry_regime': self.current_regime
                        }
                        if symbol in self.short_positions:
                            del self.short_positions[symbol]
                
                elif gap < -short_threshold and symbol not in self.short_positions:
                    # Additional filter: regime confidence
                    if self.current_regime != 2 or self.regime_probabilities[2] < 0.7:
                        self.short_positions[symbol] = {
                            'entry_time': self.Time,
                            'gap_size': gap,
                            'entry_price': bar.Open,
                            'entry_regime': self.current_regime
                        }
                        if symbol in self.long_positions:
                            del self.long_positions[symbol]
            
            self.data[symbol]['previous_close'] = bar.Close
    
    def ExecuteTradesWithRegimeBias(self):
        """
        Execute trades with HMM regime-based allocation
        """
        num_longs = len(self.long_positions)
        num_shorts = len(self.short_positions)
        
        if num_longs == 0 and num_shorts == 0:
            return
        
        # HMM-based allocation using regime probabilities
        bull_prob = self.regime_probabilities[2]
        bear_prob = self.regime_probabilities[0]
        neutral_prob = self.regime_probabilities[1]
        
        # Dynamic allocation based on regime probabilities
        # More nuanced than simple regime classification
        long_allocation = self.max_gross_exposure * (0.3 + 0.5 * bull_prob)
        short_allocation = self.max_gross_exposure * (0.3 + 0.5 * bear_prob)
        
        # Ensure we don't exceed max exposure
        total_allocation = long_allocation + short_allocation
        if total_allocation > self.max_gross_exposure:
            scale = self.max_gross_exposure / total_allocation
            long_allocation *= scale
            short_allocation *= scale
        
        # Execute long positions
        if num_longs > 0:
            weight_per_long = min(long_allocation / num_longs, 0.10)
            for symbol in self.long_positions:
                self.SetHoldings(symbol, weight_per_long)
                self.SetStopLoss(symbol, True)
        
        # Execute short positions
        if num_shorts > 0:
            weight_per_short = min(short_allocation / num_shorts, 0.10)
            for symbol in self.short_positions:
                self.SetHoldings(symbol, -weight_per_short)
                self.SetStopLoss(symbol, False)
    
    def SetStopLoss(self, symbol, is_long):
        """Set volatility-adjusted stop loss"""
        if symbol in self.atr_indicators and self.atr_indicators[symbol].IsReady:
            atr = self.atr_indicators[symbol].Current.Value
            current_price = self.Securities[symbol].Price
            
            # Adjust stop distance based on regime volatility
            regime_vol_multiplier = 1.0 + self.regime_stats[self.current_regime]['volatility']
            
            if is_long:
                stop_price = current_price - (2 * atr * regime_vol_multiplier)
                self.StopMarketOrder(symbol, -self.Portfolio[symbol].Quantity, stop_price)
            else:
                stop_price = current_price + (2 * atr * regime_vol_multiplier)
                self.StopMarketOrder(symbol, -self.Portfolio[symbol].Quantity, stop_price)
    
    def ManageExits(self):
        """
        Exit positions based on:
        1. Holding period
        2. Regime changes
        3. Regime transition probabilities
        """
        current_time = self.Time
        
        # Get transition probabilities if available
        trans_probs = self.CalculateTransitionProbabilities()
        
        # Check long positions
        for symbol in list(self.long_positions.keys()):
            position_info = self.long_positions[symbol]
            
            exit = False
            
            # 1. Time-based exit
            if current_time - position_info['entry_time'] >= self.holding_period:
                exit = True
                self.Debug(f"Time exit for long: {symbol}")
            
            # 2. Regime-based exit
            if self.current_regime == 0 and self.regime_probabilities[0] > 0.7:
                exit = True
                self.Debug(f"Bear regime exit for long: {symbol}")
            
            # 3. Regime change exit
            if position_info['entry_regime'] == 2 and self.current_regime != 2:
                # Entered in bull, no longer bull
                exit = True
                self.Debug(f"Regime change exit for long: {symbol}")
            
            if exit:
                self.Liquidate(symbol)
                del self.long_positions[symbol]
        
        # Check short positions
        for symbol in list(self.short_positions.keys()):
            position_info = self.short_positions[symbol]
            
            exit = False
            
            # 1. Time-based exit
            if current_time - position_info['entry_time'] >= self.holding_period:
                exit = True
                self.Debug(f"Time exit for short: {symbol}")
            
            # 2. Regime-based exit
            if self.current_regime == 2 and self.regime_probabilities[2] > 0.7:
                exit = True
                self.Debug(f"Bull regime exit for short: {symbol}")
            
            # 3. Regime change exit
            if position_info['entry_regime'] == 0 and self.current_regime != 0:
                # Entered in bear, no longer bear
                exit = True
                self.Debug(f"Regime change exit for short: {symbol}")
            
            if exit:
                self.Liquidate(symbol)
                del self.short_positions[symbol]
    
    def OnEndOfAlgorithm(self):
        """Final analysis of regime detection accuracy"""
        if len(self.regime_history) > 0:
            # Calculate regime distribution
            regime_counts = np.bincount(list(self.regime_history), minlength=3)
            total = len(self.regime_history)
            
            self.Debug("=== HMM Regime Analysis ===")
            self.Debug(f"Bear days: {regime_counts[0]}/{total} ({regime_counts[0]/total:.1%})")
            self.Debug(f"Neutral days: {regime_counts[1]}/{total} ({regime_counts[1]/total:.1%})")
            self.Debug(f"Bull days: {regime_counts[2]}/{total} ({regime_counts[2]/total:.1%})")
            
            # Calculate transition probabilities
            trans_probs = self.CalculateTransitionProbabilities()
            
            # Display regime persistence if available
            if trans_probs is not None:
                self.Debug("\n=== Regime Persistence (diagonal of transition matrix) ===")
                self.Debug(f"Bear persistence: {trans_probs[0,0]:.1%}")
                self.Debug(f"Neutral persistence: {trans_probs[1,1]:.1%}")
                self.Debug(f"Bull persistence: {trans_probs[2,2]:.1%}")
                
                self.Debug("\n=== Full Transition Matrix ===")
                self.Debug("       To: Bear  Neutral  Bull")
                self.Debug(f"From Bear:    {trans_probs[0,0]:.2f}  {trans_probs[0,1]:.2f}  {trans_probs[0,2]:.2f}")
                self.Debug(f"From Neutral: {trans_probs[1,0]:.2f}  {trans_probs[1,1]:.2f}  {trans_probs[1,2]:.2f}")
                self.Debug(f"From Bull:    {trans_probs[2,0]:.2f}  {trans_probs[2,1]:.2f}  {trans_probs[2,2]:.2f}")