Overall Statistics
Total Orders
1235
Average Win
0.27%
Average Loss
-0.07%
Compounding Annual Return
3.774%
Drawdown
2.100%
Expectancy
0.107
Start Equity
100000
End Equity
104152.02
Net Profit
4.152%
Sharpe Ratio
-0.771
Sortino Ratio
-1.549
Probabilistic Sharpe Ratio
35.235%
Loss Rate
77%
Win Rate
23%
Profit-Loss Ratio
3.73
Alpha
-0.01
Beta
-0.155
Annual Standard Deviation
0.037
Annual Variance
0.001
Information Ratio
-1.191
Tracking Error
0.125
Treynor Ratio
0.182
Total Fees
$0.00
Estimated Strategy Capacity
$16000000.00
Lowest Capacity Asset
QQQ RIWIV7K5Z9LX
Portfolio Turnover
125.00%
from AlgorithmImports import *

class OpeningRangeBreakoutAlgorithm(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2024, 1, 1)
        self.SetEndDate(2025, 2, 4)
        self.SetCash(100000)
        
        # Define symbol and resolution
        self.symbol = self.AddEquity("QQQ", Resolution.Minute, extendedMarketHours=True).Symbol
        
        self.SetBrokerageModel(BrokerageName.ALPACA, AccountType.Margin)
        
        self.trailing_stop = 0.75  # trailing stop amount
        self.range = 5  # opening range in minutes
        self.volume_lookback = 15  # number of bars for volume EMA
        self.volume_factor = 5.0 # threshold for determining volume breakout
        self.short = True # whether to short or not

        # Signal tracking
        self.opening_high = 0
        self.opening_low = float('inf')  # Use infinity so first price sets the low
        self.breakout_up = False
        self.breakout_down = False
        self.open_position = False
        self.stop_ticket = None
        
        self.volume_ema = self.ema(self.symbol, self.volume_lookback, Resolution.Minute, Field.Volume)
        
        self.Schedule.On(self.DateRules.EveryDay(), 
                         self.time_rules.before_market_close(self.symbol, minutes_before_close=15),
                         self.ResetDailyValues)
    
    def ResetDailyValues(self):
        """Reset daily tracking variables"""
        self.opening_high = 0
        self.opening_low = float('inf')
        self.breakout_up = False
        self.breakout_down = False
        self.liquidate()
        self.open_position = False
        self.stop_ticket = None
    
    def OnData(self, data):
        if not data.ContainsKey(self.symbol):
            return
        
        current_bar = data[self.symbol]
        if current_bar is None:
            return
        
        current_price = current_bar.Close
        current_volume = current_bar.Volume
        avg_volume = self.volume_ema.Current.Value if self.volume_ema.IsReady else 0
        
        # Update opening range during first X minutes
        if self.time.hour == 9 and 30 <= self.time.minute < 30 + self.range:
            self.opening_high = max(self.opening_high, current_bar.High)
            self.opening_low = min(self.opening_low, current_bar.Low)
            return
        
        # Ensure we have an opening range
        if self.opening_high == 0 or self.opening_low == float('inf'):
            return
        
        # Breakout logic with volume confirmation
        if not self.open_position:
            
            if current_price > self.opening_high and current_volume > avg_volume * self.volume_factor:
                self.breakout_up = True
                self.market_order(self.symbol, 100)
                stop_price = current_price - self.trailing_stop
                # Place stop-loss order
                self.stop_ticket = self.StopMarketOrder(
                    self.symbol, 
                    -100, 
                    stop_price
                )
                self.open_position = True
                self.Debug(f"Long Entry: {current_price} with Volume {current_volume}")
            
            elif current_price < self.opening_low and current_volume < avg_volume: # breakdown with weak volume confirmation
                self.breakout_down = True
                if self.short:
                    self.market_order(self.symbol, -100)
                    stop_price = current_price + self.trailing_stop
                    self.stop_ticket = self.StopMarketOrder(self.symbol, 100, stop_price)  
                    self.open_position = True
                    self.Debug(f"Short Entry: {current_price} with Volume {current_volume}")
        

    
    def OnOrderEvent(self, orderEvent):
        if orderEvent.Status == OrderStatus.Filled:
            self.Debug(f"Order Filled: {orderEvent}")
            
            # Check if the order is a stop-loss execution
            if self.stop_ticket:
                if orderEvent.OrderId == self.stop_ticket.OrderId:
                    self.Debug(f"Stop-Loss Triggered at {orderEvent.FillPrice}")
                    self.open_position = False  # Reset position flag