| Overall Statistics |
|
Total Orders 4033 Average Win 0.28% Average Loss -0.04% Compounding Annual Return -1.119% Drawdown 3.700% Expectancy -0.016 Start Equity 100000 End Equity 98771.34 Net Profit -1.229% Sharpe Ratio -1.316 Sortino Ratio -2.084 Probabilistic Sharpe Ratio 7.583% Loss Rate 89% Win Rate 11% Profit-Loss Ratio 7.71 Alpha -0.053 Beta -0.071 Annual Standard Deviation 0.047 Annual Variance 0.002 Information Ratio -1.506 Tracking Error 0.121 Treynor Ratio 0.872 Total Fees $0.00 Estimated Strategy Capacity $8900000.00 Lowest Capacity Asset QQQ RIWIV7K5Z9LX Portfolio Turnover 448.83% |
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.35 # 0.03% trailing stop amount
self.range = 5 # opening range in minutes
self.volume_lookback = 10 # number of bars for volume SMA
# 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_sma = self.SMA(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_sma.Current.Value if self.volume_sma.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.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:
self.breakout_down = True
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