| 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