Chiến lược dừng lỗ theo sau ATR đột phá phạm vi mở

ATR OR SMC 量化交易 追踪止损 开盘区间突破 风险管理 交易自动化
Ngày tạo: 2025-07-31 10:51:36 sửa đổi lần cuối: 2025-07-31 10:51:36
sao chép: 0 Số nhấp chuột: 266
2
tập trung vào
319
Người theo dõi

Chiến lược dừng lỗ theo sau ATR đột phá phạm vi mở Chiến lược dừng lỗ theo sau ATR đột phá phạm vi mở

Tổng quan về chiến lược

Open Range Breakout (ATR) là một chiến lược giao dịch định lượng kết hợp Open Range Breakout (Opening Range Breakout) và phân tích thị trường thông minh (Smart Money Concepts). Chiến lược này tập trung vào việc nắm bắt cơ hội phá vỡ khu vực giá được hình thành 5 phút sau khi thị trường chứng khoán mở cửa (09:30-09:35 EST) và kết hợp nhiều điều kiện lọc để đảm bảo chất lượng tín hiệu giao dịch. Hệ thống hỗ trợ ngay lập tức hoặc quay trở lại, sử dụng cơ chế điều chỉnh động cơ so sánh lợi nhuận với rủi ro và có thể chọn ATR (Average True Range) theo dõi dừng để tối ưu hóa lợi nhuận.

Nguyên tắc chiến lược

Logic cốt lõi của chiến lược theo dõi ATR phá vỡ lỗ hổng trong khoảng mở cửa được xây dựng dựa trên tầm quan trọng của khoảng giá ban đầu sau khi thị trường mở cửa. Chiến lược này bắt đầu bằng việc thu thập và ghi lại các điểm cao nhất và thấp nhất của giá trong một cửa sổ thời gian nhất định (09:30-09:35 EST), tạo thành “phạm vi mở cửa” (Opening Range). Sau đó, hệ thống giám sát giá cho hành vi phá vỡ trong khoảng đó, kết hợp các cơ chế quan trọng sau để đảm bảo chất lượng giao dịch:

  1. Nhận dạng và xác minh đột phá trong vùng mở: Hệ thống ghi lại các điểm cao nhất và thấp nhất trong cửa sổ thời gian được chỉ định, sau đó giám sát sự phá vỡ. Việc phá vỡ phải được xác minh thông qua cơ chế lọc kép:

    • Bộ lọc tỷ lệ phần trăm của phím hình ảnh: đảm bảo phím hình ảnh trên / dưới của phím không vượt quá tỷ lệ phần trăm được chỉ định của thực thể phím, tránh phím phím giả.
    • Bộ lọc khoảng cách phá vỡ: đảm bảo giá phá vỡ hợp lý, không quá nhỏ (để tránh phá vỡ nhỏ) và không quá lớn (để tránh kéo dài quá mức).
  2. Cơ chế nhập họcChính sách này hỗ trợ hai cách nhập cảnh:

    • Tham gia ngay lập tức: Tham gia trực tiếp vào giá đóng cửa cùng một biểu đồ xác nhận phá vỡ có hiệu lực.
    • Khởi động quay trở lại: Chờ cho giá quay trở lại đến vị trí phần trăm được chỉ định của thực thể phá vỡ nón và quay trở lại, thường có thể được đặt ở vị trí quay trở lại 50%.
  3. Cài đặt Stop LossHệ thống cung cấp hai loại dừng lỗ:

    • Hạn chế phá vỡ thùng: thiết lập dừng lỗ bên ngoài điểm cực của thùng phá vỡ.
    • Giới hạn dừng đối diện: đặt lệnh dừng bên ngoài giới hạn đối diện của vùng mở, cho phép giá có nhiều không gian dao động hơn.
  4. Quản lý rủi roHệ thống sử dụng RRM để tự động tính toán vị trí dừng và thực hiện quản lý rủi ro động. Ví dụ: thiết lập RRM 2:1 có nghĩa là lợi nhuận tiềm năng gấp đôi tổn thất tiềm năng.

  5. ATR theo dõi dừng lỗ: Một khi lợi nhuận đạt được tỷ lệ lợi nhuận rủi ro dự kiến, hệ thống có thể kích hoạt theo dõi dừng lỗ dựa trên ATR, khóa một phần lợi nhuận và cho phép xu hướng tiếp tục.

  6. Giao dịch cơ hội thứ hai: Khi giao dịch ban đầu bị dừng hoặc thất bại, hệ thống có thể tự động tìm kiếm cơ hội phá vỡ trong khoảng mở để thực hiện giao dịch hai chiều trong ngày.

Lợi thế chiến lược

  1. Tập trung vào các cơ hội giao dịch chất lượngChiến lược này giúp giảm đáng kể các giao dịch phá vỡ giả mạo và tăng tỷ lệ chiến thắng.

  2. Cơ chế nhập học linh hoạt: hỗ trợ ngay lập tức hoặc thả vào, thích ứng với phong cách giao dịch và điều kiện thị trường khác nhau. Thả vào ngay lập tức phù hợp với xu hướng mạnh mẽ, trong khi thả vào có thể nhận được giá thả vào tốt hơn.

  3. Quản lý rủi ro thích nghiThiết lập dừng động dựa trên tỷ lệ lợi nhuận so với rủi ro đảm bảo mỗi giao dịch có đặc điểm rủi ro nhất quán, quản lý tiền tiêu chuẩn hóa.

  4. Tối đa hóa lợi nhuậnATR có tính năng theo dõi lỗ hổng trong khi bảo vệ lợi nhuận đã đạt được, cho phép thị trường mạnh mẽ tiếp tục phát triển và tránh ra đi sớm.

  5. Tầm nhìn caoHệ thống cung cấp các tính năng hỗ trợ trực quan toàn diện, bao gồm các dấu hiệu khoảng cách, thẻ xác nhận đột phá, chỉ dẫn trạng thái giao dịch, dấu hiệu nhập / dừng / dừng, để nâng cao tính trực quan của quyết định giao dịch.

  6. Thiết kế hậu kỳ không thiên vịChiến lược được áp dụng toàn diệnbarstate.isconfirmedĐảm bảo tất cả các quyết định được dựa trên dữ liệu giá đã xác nhận, tránh sự sai lệch dự báo và phù hợp với môi trường giao dịch thực tế.

  7. Cơ chế cơ hội thứ haiBằng cách kích hoạt chức năng giao dịch cơ hội thứ hai, chiến lược có thể nhanh chóng thích ứng với sự thay đổi của thị trường khi phán đoán sai hướng ban đầu, nắm bắt cơ hội đảo ngược, và cải thiện hiệu quả sử dụng vốn.

  8. Tối ưu hóa quản lý phiênChức năng tự động kết thúc phiên giao dịch đảm bảo không giữ giao dịch qua đêm, giảm rủi ro qua đêm.

Rủi ro chiến lược

  1. Rủi ro biến động trong giai đoạn hình thànhTrong giai đoạn hình thành khoảng mở (09:30-09:35), thị trường có thể có biến động bất thường, dẫn đến khoảng quá rộng hoặc quá hẹp. Phạm vi quá rộng có thể dẫn đến lỗ hổng quá lớn, trong khi khoảng quá hẹp có thể thường xuyên gây ra phá vỡ giả. Giải phápBạn có thể xem xét thêm các điều kiện lọc kích thước khoảng mở để loại bỏ các khoảng bất thường; hoặc điều chỉnh bộ lọc ngày giao dịch để tránh các ngày đặc biệt có biến động cao (như ngày công bố dữ liệu kinh tế quan trọng).

  2. Rủi ro rút lui mạnh mẽ sau vụ đột phá: Thị trường có thể bị rút lui mạnh sau khi phá vỡ hiệu quả, dẫn đến thị trường tiếp tục di chuyển theo hướng cũ sau khi dừng lỗ được kích hoạt. Giải phápCân nhắc sử dụng các thiết lập dừng lỗ lỏng lẻo hơn, chẳng hạn như dừng lỗ theo chiều ngang; hoặc điều chỉnh cơ chế nhập cảnh để quay trở lại nhập cảnh để có được giá nhập cảnh tốt hơn và ít tiếp xúc với rủi ro hơn.

  3. Chất lượng tín hiệu phụ thuộc vào cài đặt bộ lọc: Phép lọc đường bóng và thiết lập tham số lọc khoảng cách đã được xác minh đột phá có ảnh hưởng đáng kể đến chất lượng tín hiệu, các tham số không phù hợp có thể lọc ra các cơ hội giao dịch tốt hoặc nhận quá nhiều tín hiệu chất lượng thấp. Giải pháp: Tối ưu hóa các tham số bộ lọc bằng cách truy lại lịch sử để tìm các thiết lập tốt nhất cho thị trường và giống cụ thể; xem xét sử dụng các tham số thích ứng để điều chỉnh tiêu chuẩn lọc theo động lực biến động của thị trường.

  4. Theo dõi độ nhạy của tham số dừng: ATR theo dõi dừng lỗ đặt quá chặt có thể dẫn đến rút lui quá sớm trong điều chỉnh nhỏ, trong khi đặt quá nhẹ có thể dẫn đến quá nhiều lợi nhuận. Giải phápĐiều chỉnh chu kỳ ATR và nhân số dựa trên đặc tính biến động lịch sử của giống mục tiêu; xem xét thực hiện chiến lược bán tháo theo đợt, một số vị trí sử dụng dừng cố định và một số vị trí sử dụng dừng theo dõi.

  5. Hạn chế tần suất giao dịchChiến lược: Thực hiện tối đa hai giao dịch mỗi ngày ((thương mại ban đầu và giao dịch cơ hội thứ hai), có thể không tận dụng đầy đủ tất cả các cơ hội trong ngày. Giải phápLưu ý: Cân nhắc chiến lược mở rộng, giám sát các khoảng giá quan trọng trong các khoảng thời gian khác trong ngày; hoặc kết hợp với các chỉ số kỹ thuật khác để tạo ra chiến lược tổng hợp, tăng nguồn tín hiệu giao dịch.

Hướng tối ưu hóa chiến lược

  1. Chu kỳ chuyển đổi giữa các khoảng mởChiến lược hiện tại sử dụng khoảng thời gian mở cửa 5 phút cố định, có thể xem xét độ dài của khoảng thời gian điều chỉnh theo động thái biến động của thị trường. Trong thị trường biến động thấp, khoảng thời gian có thể được rút ngắn xuống còn 3 phút, trong khi thị trường biến động cao có thể kéo dài đến 10 phút để thích ứng tốt hơn với các tình trạng thị trường khác nhau.

  2. Kết hợp xác nhận số lượng giao hàng: Tăng điều kiện lọc khối lượng giao dịch trong cơ chế xác minh đột phá, yêu cầu khối lượng giao dịch tại thời điểm đột phá cao hơn đáng kể so với khối lượng giao dịch trung bình của vài chu kỳ trước, tăng hiệu quả đột phá. Điều này có thể được thực hiện bằng cách tính tỷ lệ khối lượng giao dịch đột phá với khối lượng giao dịch trung bình của N chu kỳ trước.

  3. Phân tích nhiều khung thời gian: giới thiệu bộ lọc hướng xu hướng của khung thời gian cao hơn, chỉ tham gia khi hướng xu hướng của đường mặt trời hoặc đường giờ phù hợp với hướng phá vỡ, tăng tỷ lệ chiến thắng giao dịch. Xu hướng khung thời gian cao hơn có thể được xác định bằng độ lệch trung bình di chuyển đơn giản hoặc chỉ số xu hướng cao hơn.

  4. Tối ưu hóa quản lý tài chính: Thực hiện cơ chế điều chỉnh quy mô vị trí động, tự động điều chỉnh số lượng hợp đồng dựa trên biến động lịch sử, quy mô tài khoản hiện tại và hiệu suất gần đây, để kiểm soát rủi ro tinh tế hơn. Ví dụ: tăng dần vị trí sau khi thu lợi nhuận liên tục và giảm vị trí sau khi thua lỗ liên tục.

  5. Mô hình học máy tích hợpCác đặc điểm có thể bao gồm kích thước khoảng mở, biến động của thị trường, xu hướng giá trong ngày giao dịch trước đó, mô hình thời gian cụ thể.

  6. Tăng cường logic giao dịch cơ hội thứ haiTối ưu hóa các điều kiện kích hoạt giao dịch cơ hội thứ hai, không chỉ dựa trên các giao dịch ban đầu thất bại, mà còn xem xét sự thay đổi cấu trúc thị trường và các chỉ số động lực mới nổi, để tăng tỷ lệ thành công của giao dịch thứ hai.

  7. Các tham số cá nhân hóa giống: Phát triển các tập hợp tham số được tối ưu hóa cho các loại giao dịch khác nhau, xem xét các đặc tính biến động và hành vi giá trị riêng của từng loại. Ví dụ, các loại biến động lớn có thể cần thiết lập bộ lọc lỏng lẻo hơn và tỷ lệ lợi nhuận rủi ro bảo thủ hơn.

  8. Tích hợp các chỉ số cảm xúc thị trườngGhi chú: đưa ra chỉ số VIX hoặc các chỉ số cảm xúc thị trường khác, điều chỉnh các tham số chiến lược hoặc tạm thời cấm giao dịch trong thời gian cảm xúc thị trường cực đoan, tránh môi trường không chắc chắn cao.

Tóm tắt

Chiến lược theo dõi ATR phá vỡ lỗ hổng trong khoảng mở là một hệ thống giao dịch định lượng có cấu trúc hoàn hảo, kết hợp khéo léo các bước phá vỡ trong khoảng mở, cơ chế lọc thông minh, tùy chọn nhập cảnh linh hoạt và chức năng quản lý rủi ro tiên tiến. Chiến lược này đặc biệt phù hợp với giao dịch trong ngày của thị trường chứng khoán và tương lai, thu lợi nhuận bằng cách nắm bắt các bước phá vỡ định hướng sau khi mở.

Giá trị cốt lõi của chiến lược là cơ chế xác thực nhiều lớp và hệ thống quản lý rủi ro, giảm đáng kể các giao dịch phá vỡ giả mạo thông qua các bộ lọc đường bóng và khoảng cách, đồng thời sử dụng tỷ lệ chi trả rủi ro và ATR theo dõi dừng lỗ để đảm bảo tiếp xúc rủi ro và bảo vệ lợi nhuận nhất quán. Tính năng giao dịch cơ hội thứ hai cho phép chiến lược tăng khả năng thích ứng và cơ hội kiếm thêm lợi nhuận.

Mặc dù có nhiều ưu điểm, người dùng vẫn cần lưu ý đến tầm quan trọng của việc tối ưu hóa các tham số, các thị trường và giống khác nhau có thể cần điều chỉnh cụ thể để đạt được hiệu quả tối ưu. Đồng thời, các nhà giao dịch được khuyến khích sử dụng chiến lược này như một phần của hệ thống giao dịch hoàn chỉnh, kết hợp với phân tích thị trường và các nguyên tắc quản lý rủi ro rộng hơn.

Bằng cách thực hiện các hướng tối ưu hóa được đề xuất, đặc biệt là tham số thích ứng, phân tích nhiều khung thời gian và hệ thống quản lý tài chính được tăng cường, chiến lược này có tiềm năng nâng cao hơn nữa sự ổn định và khả năng sinh lợi của nó, trở thành một công cụ mạnh mẽ trong hộp công cụ của các nhà giao dịch chuyên nghiệp.

Mã nguồn chiến lược
/*backtest
start: 2025-07-18 00:00:00
end: 2025-07-30 00:00:00
period: 30m
basePeriod: 30m
exchanges: [{"eid":"Futures_Binance","currency":"ETH_USDT"}]
*/

//@version=5
strategy("Casper SMC 5min ORB - Roboquant AI", overlay=true, default_qty_type=strategy.fixed, default_qty_value=1, max_bars_back=500, calc_on_order_fills=true, calc_on_every_tick=false, initial_capital=50000, currency=currency.USD)

// === STRATEGY SETTINGS ===
// Risk Management
contracts = input.int(1, "Contracts", minval=1, group="Risk Management")
risk_multiplier = input.float(2.0, "Risk:Reward Multiplier", minval=0.5, maxval=10.0, group="Risk Management")
sl_points = input.int(2, "Stop Loss Points Below/Above Breakout Candle", minval=1, group="Risk Management")

// Entry Settings
entry_type = input.string("Instant", "Entry Type", options=["Retracement", "Instant"], group="Entry Settings")
retracement_percent = input.float(50.0, "Retracement % of Breakout Candle Body", minval=10.0, maxval=90.0, group="Entry Settings")

// Stop Loss Settings
sl_type = input.string("Opposite Range", "Stop Loss Type", options=["Breakout Candle", "Opposite Range"], group="Stop Loss Settings")

// Second Chance Trade Settings
enable_second_chance = input.bool(false, "Enable Second Chance Trade", group="Second Chance Trade")
second_chance_info = input.string("If initial SL is hit, allow opposite breakout trade", "Info: Second Chance Logic", group="Second Chance Trade")

// Breakout Filter Settings
use_wick_filter = input.bool(false, "Use Wick Filter", group="Breakout Filter")
max_wick_percent = input.float(50.0, "Max Wick % of Candle Body", minval=10.0, maxval=200.0, group="Breakout Filter")

// Breakout Distance Filters
use_breakout_distance_filter = input.bool(true, "Use Breakout Distance Filter", group="Breakout Distance Filter")
min_breakout_multiplier = input.float(0.1, "Min Breakout Distance (OR Size * X)", minval=0.0, maxval=3.0, group="Breakout Distance Filter")
max_breakout_multiplier = input.float(1.6, "Max Breakout Distance (OR Size * X)", minval=0.5, maxval=5.0, group="Breakout Distance Filter")

// Trailing Stop Loss Settings
use_trailing_sl = input.bool(false, "Use Trailing Stop Loss", group="Trailing Stop Loss")
profit_r_multiplier = input.float(1.0, "Start Trailing After X R Profit", minval=0.5, maxval=5.0, group="Trailing Stop Loss")
atr_length = input.int(14, "ATR Length", minval=1, maxval=50, group="Trailing Stop Loss")
atr_multiplier = input.float(1.0, "ATR Multiplier for Trailing", minval=0.5, maxval=5.0, group="Trailing Stop Loss")

// Session Management
or_start_hour = input.int(9, "Opening Range Start Hour", minval=0, maxval=23, group="Session Management")
or_start_minute = input.int(30, "Opening Range Start Minute", minval=0, maxval=59, group="Session Management")
or_end_minute = input.int(35, "Opening Range End Minute", minval=0, maxval=59, group="Session Management")
session_timezone = input.string("America/New_York", "Session Timezone", group="Session Management")
force_session_close = input.bool(true, "Force Close at Session End", group="Session Management")
session_end_hour = input.int(16, "Session End Hour", minval=0, maxval=23, group="Session Management")
session_end_minute = input.int(0, "Session End Minute", minval=0, maxval=59, group="Session Management")

// Day of Week Trading Filters
trade_monday = input.bool(true, "Trade on Monday", group="Day of Week Filters")
trade_tuesday = input.bool(true, "Trade on Tuesday", group="Day of Week Filters")
trade_wednesday = input.bool(true, "Trade on Wednesday", group="Day of Week Filters")
trade_thursday = input.bool(true, "Trade on Thursday", group="Day of Week Filters")
trade_friday = input.bool(true, "Trade on Friday", group="Day of Week Filters")

// Visual Settings
high_line_color = input.color(color.green, title="Opening Range High Line Color", group="Visual Settings")
low_line_color = input.color(color.red, title="Opening Range Low Line Color", group="Visual Settings")

// Label Control Settings
show_trading_disabled_labels = input.bool(false, "Show Trading Disabled Labels", group="Label Controls")
show_breakout_validation_labels = input.bool(true, "Show Breakout Validation Labels", group="Label Controls")
show_second_chance_labels = input.bool(false, "Show Second Chance Labels", group="Label Controls")
show_trade_status_labels = input.bool(false, "Show Trade Status Labels", group="Label Controls")
show_entry_labels = input.bool(false, "Show Entry Labels", group="Label Controls")
show_sl_tp_labels = input.bool(false, "Show Stop Loss / Take Profit Labels", group="Label Controls")

// === VARIABLES ===
// ATR for trailing stop loss
atr = ta.atr(atr_length)

// === NYSE OPENING RANGE LOGIC ===
// FIXED: Using configurable hour/minute inputs with timezone
current_time = time(timeframe.period, "0000-2400:23456", session_timezone)
current_hour = hour(current_time, session_timezone)
current_minute = minute(current_time, session_timezone)
is_opening_range = current_hour == or_start_hour and current_minute >= or_start_minute and current_minute <= or_end_minute

// Check if we're at the start of a new trading day - FIXED: More reliable detection
is_new_day = ta.change(time("1D"))

// ADDED: Check if trading is allowed on current day of week (using session timezone)
current_day = dayofweek(current_time, session_timezone)
is_trading_day_allowed = (current_day == dayofweek.monday and trade_monday) or (current_day == dayofweek.tuesday and trade_tuesday) or (current_day == dayofweek.wednesday and trade_wednesday) or (current_day == dayofweek.thursday and trade_thursday) or (current_day == dayofweek.friday and trade_friday)

// Variables to store opening range high and low for current day
var float or_high = na
var float or_low = na
var bool lines_drawn = false
var bool breakout_occurred = false
var float breakout_candle_high = na
var float breakout_candle_low = na
var float breakout_price = na
var string breakout_direction = na
var int or_start_bar = na  // ADDED: Store the bar index when opening range starts

// ADDED: Second chance trade variables
var bool first_trade_sl_hit = false
var string first_trade_direction = na
var bool second_chance_available = false
var bool second_trade_taken = false
var bool daily_trades_complete = false  // ADDED: Prevent more than 2 trades per day

// Reset variables at the start of each trading day
if is_new_day
    or_high := na
    or_low := na
    lines_drawn := false
    breakout_occurred := false
    breakout_candle_high := na
    breakout_candle_low := na
    breakout_price := na
    breakout_direction := na
    or_start_bar := na  // ADDED: Reset opening range start bar
    // ADDED: Reset second chance variables
    first_trade_sl_hit := false
    first_trade_direction := na
    second_chance_available := false
    second_trade_taken := false
    daily_trades_complete := false  // ADDED: Reset trade limit

// Capture opening range data during 09:30-09:35 EST
if is_opening_range
    if na(or_high) or na(or_low)
        or_high := high
        or_low := low
        or_start_bar := bar_index  // ADDED: Store the bar index when opening range starts
    else
        or_high := math.max(or_high, high)
        or_low := math.min(or_low, low)

// Draw lines when we're past the opening range and haven't drawn yet
if not is_opening_range and not na(or_high) and not na(or_low) and not na(or_start_bar) and not lines_drawn
    // FIXED: Lines start from the actual opening range start time and extend forward
    start_x = or_start_bar
    end_x = bar_index + 50  // Extend lines forward for visibility
    

    
    lines_drawn := true
    
    // ADDED: Show visual indicator if trading is disabled for current day
    if not is_trading_day_allowed and show_trading_disabled_labels
        day_name = current_day == dayofweek.monday ? "Monday" :
                   current_day == dayofweek.tuesday ? "Tuesday" :
                   current_day == dayofweek.wednesday ? "Wednesday" :
                   current_day == dayofweek.thursday ? "Thursday" :
                   current_day == dayofweek.friday ? "Friday" : "Weekend"
        label.new(x=bar_index, y=(or_high + or_low) / 2, text="Trading Disabled\n" + day_name, color=color.gray, textcolor=color.white, style=label.style_label_center, size=size.normal)

// Check for breakouts after opening range is complete (only first breakout of the day)
// FIXED: Added barstate.isconfirmed to avoid lookahead bias
if barstate.isconfirmed and not is_opening_range and not na(or_high) and not na(or_low) and lines_drawn and not breakout_occurred and not daily_trades_complete and is_trading_day_allowed
    // Calculate candle body and wick percentages
    candle_body = math.abs(close - open)
    top_wick = high - math.max(open, close)
    bottom_wick = math.min(open, close) - low
    top_wick_percent = candle_body > 0 ? (top_wick / candle_body) * 100 : 0
    bottom_wick_percent = candle_body > 0 ? (bottom_wick / candle_body) * 100 : 0
    
    // ADDED: Calculate opening range size for distance filters
    or_size = or_high - or_low
    
    // Check for first breakout above opening range high
    if close > or_high
        // FIXED: Mark breakout as occurred FIRST (this is THE breakout candle)
        breakout_occurred := true
        breakout_candle_high := high
        breakout_candle_low := low
        breakout_price := close
        breakout_direction := "long"
        
        // ADDED: Validate this specific breakout candle against distance filter
        breakout_distance_valid = true
        if use_breakout_distance_filter
            min_breakout_level = or_high + (or_size * min_breakout_multiplier)
            max_breakout_level = or_high + (or_size * max_breakout_multiplier)
            breakout_distance_valid := close >= min_breakout_level and close <= max_breakout_level
        
        // Apply wick filter for long breakouts
        wick_filter_valid = not use_wick_filter or top_wick_percent <= max_wick_percent
        
        // Show appropriate label based on validation results
        if show_breakout_validation_labels
            if wick_filter_valid and breakout_distance_valid
                label.new(x=bar_index, y=high, text="VALID", color=high_line_color, textcolor=color.white, style=label.style_label_down, size=size.tiny)
            else
                label.new(x=bar_index, y=high, text="INVALID", color=color.gray, textcolor=color.white, style=label.style_label_down, size=size.tiny)
        
        // Mark breakout as invalid so no trade will be placed (regardless of label setting)
        if not (wick_filter_valid and breakout_distance_valid)
            breakout_direction := "invalid"
    
    // Check for first breakout below opening range low  
    else if close < or_low
        // FIXED: Mark breakout as occurred FIRST (this is THE breakout candle)
        breakout_occurred := true
        breakout_candle_high := high
        breakout_candle_low := low
        breakout_price := close
        breakout_direction := "short"
        
        // ADDED: Validate this specific breakout candle against distance filter
        breakout_distance_valid = true
        if use_breakout_distance_filter
            min_breakout_level = or_low - (or_size * min_breakout_multiplier)
            max_breakout_level = or_low - (or_size * max_breakout_multiplier)
            breakout_distance_valid := close <= min_breakout_level and close >= max_breakout_level
        
        // Apply wick filter for short breakouts
        wick_filter_valid = not use_wick_filter or bottom_wick_percent <= max_wick_percent
        
        // Show appropriate label based on validation results
        if show_breakout_validation_labels
            if wick_filter_valid and breakout_distance_valid
                label.new(x=bar_index, y=low, text="VALID", color=low_line_color, textcolor=color.white, style=label.style_label_up, size=size.tiny)
            else
                label.new(x=bar_index, y=low, text="INVALID", color=color.gray, textcolor=color.white, style=label.style_label_up, size=size.tiny)
        
        // Mark breakout as invalid so no trade will be placed (regardless of label setting)
        if not (wick_filter_valid and breakout_distance_valid)
            breakout_direction := "invalid"

// ADDED: Check for second chance breakout (opposite direction after initial SL hit)
// FIXED: Added barstate.isconfirmed to avoid lookahead bias
if barstate.isconfirmed and not is_opening_range and not na(or_high) and not na(or_low) and lines_drawn and second_chance_available and not second_trade_taken and not daily_trades_complete and is_trading_day_allowed
    // Calculate candle body and wick percentages
    candle_body = math.abs(close - open)
    top_wick = high - math.max(open, close)
    bottom_wick = math.min(open, close) - low
    top_wick_percent = candle_body > 0 ? (top_wick / candle_body) * 100 : 0
    bottom_wick_percent = candle_body > 0 ? (bottom_wick / candle_body) * 100 : 0
    
    // ADDED: Calculate opening range size for distance filters
    or_size = or_high - or_low
    
    // If first trade was LONG and failed, look for SHORT breakout
    if first_trade_direction == "long" and close < or_low
        // FIXED: Mark second chance breakout as taken FIRST
        second_trade_taken := true
        second_chance_available := false
        breakout_candle_high := high
        breakout_candle_low := low
        breakout_price := close
        breakout_direction := "short"
        
        // ADDED: Validate this specific breakout candle against distance filter
        breakout_distance_valid = true
        if use_breakout_distance_filter
            min_breakout_level = or_low - (or_size * min_breakout_multiplier)
            max_breakout_level = or_low - (or_size * max_breakout_multiplier)
            breakout_distance_valid := close <= min_breakout_level and close >= max_breakout_level
        
        // Apply wick filter for short breakouts
        wick_filter_valid = not use_wick_filter or bottom_wick_percent <= max_wick_percent
        
        // Show appropriate label based on validation results
        if show_second_chance_labels
            if wick_filter_valid and breakout_distance_valid
                label.new(x=bar_index, y=low, text="2nd Chance\nOR Low Break\nVALID", color=color.orange, textcolor=color.white, style=label.style_label_up, size=size.tiny)
            else
                label.new(x=bar_index, y=low, text="2nd Chance\nOR Low Break\nINVALID", color=color.gray, textcolor=color.white, style=label.style_label_up, size=size.tiny)
        
        // Mark breakout as invalid so no trade will be placed (regardless of label setting)
        if not (wick_filter_valid and breakout_distance_valid)
            breakout_direction := "invalid"
    
    // If first trade was SHORT and failed, look for LONG breakout
    else if first_trade_direction == "short" and close > or_high
        // FIXED: Mark second chance breakout as taken FIRST
        second_trade_taken := true
        second_chance_available := false
        breakout_candle_high := high
        breakout_candle_low := low
        breakout_price := close
        breakout_direction := "long"
        
        // ADDED: Validate this specific breakout candle against distance filter
        breakout_distance_valid = true
        if use_breakout_distance_filter
            min_breakout_level = or_high + (or_size * min_breakout_multiplier)
            max_breakout_level = or_high + (or_size * max_breakout_multiplier)
            breakout_distance_valid := close >= min_breakout_level and close <= max_breakout_level
        
        // Apply wick filter for long breakouts
        wick_filter_valid = not use_wick_filter or top_wick_percent <= max_wick_percent
        
        // Show appropriate label based on validation results
        if show_second_chance_labels
            if wick_filter_valid and breakout_distance_valid
                label.new(x=bar_index, y=high, text="2nd Chance\nOR High Break\nVALID", color=color.orange, textcolor=color.white, style=label.style_label_down, size=size.tiny)
            else
                label.new(x=bar_index, y=high, text="2nd Chance\nOR High Break\nINVALID", color=color.gray, textcolor=color.white, style=label.style_label_down, size=size.tiny)
        
        // Mark breakout as invalid so no trade will be placed (regardless of label setting)
        if not (wick_filter_valid and breakout_distance_valid)
            breakout_direction := "invalid"

// === STRATEGY LOGIC ===
// Check if we have a breakout and place retracement entry orders
var bool entry_placed = false
var bool second_entry_placed = false  // ADDED: Track second trade entry separately
var float entry_price = na
var float stop_loss = na
var float take_profit = na
var float trailing_stop = na
var bool trailing_active = false
var float initial_risk = na
var bool trailing_started = false
var string current_entry_id = na  // FIXED: Track which entry ID we're using

// Arrays to store historical trade boxes
var array<box> historical_trade_boxes = array.new<box>()
var array<box> historical_sl_boxes = array.new<box>()
var array<box> historical_tp_boxes = array.new<box>()

// Variables to track current active trade boxes for extending to exit
var box current_profit_box = na
var box current_sl_box = na

// ADDED: General position close detection for extending boxes - Handle timing issues
if barstate.isconfirmed and strategy.position_size == 0 and strategy.position_size[1] != 0
    // Extend trade visualization boxes to exact exit point when any position closes
    if not na(current_profit_box)
        // Ensure minimum 8 bars width or extend to current bar, whichever is longer
        box_left = box.get_left(current_profit_box)
        min_right = box_left + 8
        final_right = math.max(min_right, bar_index)
        box.set_right(current_profit_box, final_right)
        current_profit_box := na  // Clear reference after extending
    if not na(current_sl_box)
        // Ensure minimum 8 bars width or extend to current bar, whichever is longer
        box_left = box.get_left(current_sl_box)
        min_right = box_left + 8
        final_right = math.max(min_right, bar_index)
        box.set_right(current_sl_box, final_right)
        current_sl_box := na  // Clear reference after extending

// ADDED: Backup safety check - extend boxes if position is closed but boxes still active
if not na(current_profit_box) and strategy.position_size == 0
    box_left = box.get_left(current_profit_box)
    min_right = box_left + 8
    final_right = math.max(min_right, bar_index)
    box.set_right(current_profit_box, final_right)
    current_profit_box := na
if not na(current_sl_box) and strategy.position_size == 0
    box_left = box.get_left(current_sl_box)
    min_right = box_left + 8
    final_right = math.max(min_right, bar_index)
    box.set_right(current_sl_box, final_right)
    current_sl_box := na

// Reset entry flag on new day
if is_new_day
    entry_placed := false
    second_entry_placed := false  // ADDED: Reset second entry flag
    entry_price := na
    stop_loss := na
    take_profit := na
    trailing_stop := na
    trailing_active := false
    initial_risk := na
    trailing_started := false
    current_entry_id := na  // FIXED: Reset entry ID
    current_profit_box := na  // ADDED: Reset current trade boxes
    current_sl_box := na

// SIMPLIFIED: Detect when position closes to enable second chance (FIXED for lookahead bias)
if barstate.isconfirmed and strategy.position_size == 0 and strategy.position_size[1] != 0 and entry_placed and not first_trade_sl_hit
    // A position just closed and we had an active trade
    if enable_second_chance and not second_trade_taken
        // Simplified logic - if position closed, enable second chance
        first_trade_sl_hit := true
        first_trade_direction := breakout_direction
        second_chance_available := true
        
        // Reset variables for potential second trade
        entry_price := na
        trailing_stop := na
        trailing_active := false
        initial_risk := na
        trailing_started := false
        current_entry_id := na
        
        // Add visual marker
        if show_trade_status_labels
            label.new(x=bar_index, y=close, text="Trade Closed\nSecond Chance Available", color=color.yellow, textcolor=color.black, style=label.style_label_down, size=size.tiny)
    else
        // Second chance not enabled or already taken - mark day complete
        daily_trades_complete := true

// ADDED: Handle case where first breakout was invalid (no trade placed)
if breakout_occurred and breakout_direction == "invalid" and enable_second_chance and not first_trade_sl_hit
    // First breakout was invalid, enable second chance immediately
    first_trade_sl_hit := true
    // Determine what direction the invalid breakout was
    first_trade_direction := breakout_price > or_high ? "long" : "short"
    second_chance_available := true
    if show_trade_status_labels
        label.new(x=bar_index + 1, y=(or_high + or_low) / 2, text="First Breakout Invalid\nSecond Chance Available", color=color.yellow, textcolor=color.black, style=label.style_label_center, size=size.tiny)

// REMOVED: Complex historical box cleanup to avoid lookahead bias
// Historical boxes will be cleaned up automatically by Pine Script's runtime

// Place entry orders after breakout - FIXED: Add barstate.isconfirmed for consistency
if barstate.isconfirmed and not daily_trades_complete and is_trading_day_allowed and ((breakout_occurred and not entry_placed and not na(breakout_candle_high) and breakout_direction != "invalid") or (second_trade_taken and not second_entry_placed and not na(breakout_candle_high) and breakout_direction != "invalid"))
    // For long breakout
    if breakout_direction == "long"
        // Calculate stop loss based on selected method
        if sl_type == "Breakout Candle"
            stop_loss := breakout_candle_low - (sl_points * syminfo.mintick)
        else
            // Use opposite side of opening range (below opening range low)
            stop_loss := or_low - (sl_points * syminfo.mintick)
        
        if entry_type == "Retracement"
            // Calculate retracement entry price (x% of breakout candle body)
            breakout_candle_body = breakout_candle_high - breakout_candle_low
            retracement_amount = breakout_candle_body * (retracement_percent / 100)
            entry_price := breakout_candle_high - retracement_amount
            
            // FIXED: Store the entry ID we're using (differentiate first vs second chance)
            current_entry_id := second_trade_taken ? "Long Retracement 2nd" : "Long Retracement"
            
            // Place buy limit order at retracement level
            strategy.entry(current_entry_id, strategy.long, limit=entry_price, qty=contracts)
            
            // Add visual markers
            if show_entry_labels
                entry_label_text = second_trade_taken ? "BUY LIMIT (2nd)\n" + str.tostring(entry_price, "#.##") : "BUY LIMIT\n" + str.tostring(entry_price, "#.##")
                label.new(x=bar_index, y=entry_price, text=entry_label_text, color=color.green, textcolor=color.white, style=label.style_label_up, size=size.tiny)
        else
            // Immediate entry at breakout candle close
            entry_price := breakout_price
            
            // FIXED: Store the entry ID we're using (differentiate first vs second chance)
            current_entry_id := second_trade_taken ? "Instant Long 2nd" : "Instant Long"
            
            // Place buy market order
            strategy.entry(current_entry_id, strategy.long, qty=contracts)
            
            // Add visual markers
            if show_entry_labels
                entry_label_text = second_trade_taken ? "BUY MARKET (2nd)\n" + str.tostring(entry_price, "#.##") : "BUY MARKET\n" + str.tostring(entry_price, "#.##")
                label.new(x=bar_index, y=entry_price, text=entry_label_text, color=color.green, textcolor=color.white, style=label.style_label_up, size=size.tiny)
        
        // Calculate take profit based on risk:reward
        risk_size = entry_price - stop_loss
        take_profit := entry_price + (risk_size * risk_multiplier)
        
        // FIXED: Set exit orders with proper entry ID and always include initial stop loss
        if use_trailing_sl
            // Initialize trailing stop and calculate initial risk
            trailing_stop := stop_loss
            trailing_active := true
            initial_risk := math.abs(entry_price - stop_loss)
            trailing_started := false
            // FIXED: Always set initial stop loss, even with trailing enabled
            exit_id = second_trade_taken ? "Long Exit 2nd" : "Long Exit"
            strategy.exit(exit_id, current_entry_id, stop=stop_loss, limit=take_profit)
        else
            // FIXED: Use stored entry ID
            exit_id = second_trade_taken ? "Long Exit 2nd" : "Long Exit"
            strategy.exit(exit_id, current_entry_id, stop=stop_loss, limit=take_profit)
        
        // Create trade visualization boxes (TradingView style) - FIXED: Minimum 8 bars width
        // Blue profit zone box (from entry to take profit)

        
        // Store trade boxes for historical display - FIXED: Remove time usage
        array.push(historical_trade_boxes, current_profit_box)
        array.push(historical_sl_boxes, current_sl_box)
        array.push(historical_tp_boxes, na) // No TP box for long trades
        
        // Add stop loss and take profit markers
        if show_sl_tp_labels
            label.new(x=bar_index, y=stop_loss, text="SL\n" + str.tostring(stop_loss, "#.##"), color=color.red, textcolor=color.white, style=label.style_label_down, size=size.tiny)
            label.new(x=bar_index, y=take_profit, text="TP\n" + str.tostring(take_profit, "#.##"), color=color.blue, textcolor=color.white, style=label.style_label_down, size=size.tiny)
        
        // ADDED: Set the appropriate entry flag based on which trade this is
        if second_trade_taken
            second_entry_placed := true
            daily_trades_complete := true
        else
            entry_placed := true
    
    // For short breakout
    else if breakout_direction == "short"
        // Calculate stop loss based on selected method
        if sl_type == "Breakout Candle"
            stop_loss := breakout_candle_high + (sl_points * syminfo.mintick)
        else
            // Use opposite side of opening range (above opening range high)
            stop_loss := or_high + (sl_points * syminfo.mintick)
        
        if entry_type == "Retracement"
            // Calculate retracement entry price (x% of breakout candle body)
            breakout_candle_body = breakout_candle_high - breakout_candle_low
            retracement_amount = breakout_candle_body * (retracement_percent / 100)
            entry_price := breakout_candle_low + retracement_amount
            
            // FIXED: Store the entry ID we're using (differentiate first vs second chance)
            current_entry_id := second_trade_taken ? "Short Retracement 2nd" : "Short Retracement"
            
            // Place sell limit order at retracement level
            strategy.entry(current_entry_id, strategy.short, limit=entry_price, qty=contracts)
            
            // Add visual markers
            if show_entry_labels
                entry_label_text = second_trade_taken ? "SELL LIMIT (2nd)\n" + str.tostring(entry_price, "#.##") : "SELL LIMIT\n" + str.tostring(entry_price, "#.##")
                label.new(x=bar_index, y=entry_price, text=entry_label_text, color=color.red, textcolor=color.white, style=label.style_label_down, size=size.tiny)
        else
            // Immediate entry at breakout candle close
            entry_price := breakout_price
            
            // FIXED: Store the entry ID we're using (differentiate first vs second chance)
            current_entry_id := second_trade_taken ? "Instant 2nd" : "Instant Short"
            
            // Place sell market order
            strategy.entry(current_entry_id, strategy.short, qty=contracts)
            
            // Add visual markers
            if show_entry_labels
                entry_label_text = second_trade_taken ? "SELL MARKET (2nd)\n" + str.tostring(entry_price, "#.##") : "SELL MARKET\n" + str.tostring(entry_price, "#.##")
                label.new(x=bar_index, y=entry_price, text=entry_label_text, color=color.red, textcolor=color.white, style=label.style_label_down, size=size.tiny)
        
        // Calculate take profit based on risk:reward
        risk_size = stop_loss - entry_price
        take_profit := entry_price - (risk_size * risk_multiplier)
        
        // FIXED: Set exit orders with proper entry ID and always include initial stop loss
        if use_trailing_sl
            // Initialize trailing stop and calculate initial risk
            trailing_stop := stop_loss
            trailing_active := true
            initial_risk := math.abs(entry_price - stop_loss)
            trailing_started := false
            // FIXED: Always set initial stop loss, even with trailing enabled
            exit_id = second_trade_taken ? "Short Exit 2nd" : "Short Exit"
            strategy.exit(exit_id, current_entry_id, stop=stop_loss, limit=take_profit)
        else
            // FIXED: Use stored entry ID
            exit_id = second_trade_taken ? "Short Exit 2nd" : "Short Exit"
            strategy.exit(exit_id, current_entry_id, stop=stop_loss, limit=take_profit)
        
        // Create trade visualization boxes (TradingView style) - FIXED: Minimum 8 bars width

        
        // Store trade boxes for historical display - FIXED: Remove time usage
        array.push(historical_trade_boxes, current_profit_box)
        array.push(historical_sl_boxes, current_sl_box)
        array.push(historical_tp_boxes, na) // No TP box for short trades
        
        // Add stop loss and take profit markers
        if show_sl_tp_labels
            label.new(x=bar_index, y=stop_loss, text="SL\n" + str.tostring(stop_loss, "#.##"), color=color.red, textcolor=color.white, style=label.style_label_up, size=size.tiny)
            label.new(x=bar_index, y=take_profit, text="TP\n" + str.tostring(take_profit, "#.##"), color=color.blue, textcolor=color.white, style=label.style_label_up, size=size.tiny)
        
        // ADDED: Set the appropriate entry flag based on which trade this is
        if second_trade_taken
            second_entry_placed := true
            daily_trades_complete := true
        else
            entry_placed := true

// === TRAILING STOP LOGIC ===
// FIXED: Proper trailing stop loss management
if use_trailing_sl and trailing_active and strategy.position_size != 0 and not na(current_entry_id)
    if strategy.position_size > 0  // Long position
        // Calculate current unrealized profit in points
        current_profit = close - entry_price
        profit_r = current_profit / initial_risk
        
        // Check if we should start trailing (after X R profit)
        if not trailing_started and profit_r >= profit_r_multiplier
            trailing_started := true
            // Start trailing from a level that's better than the initial stop
            trailing_stop := math.max(trailing_stop, close - (atr * atr_multiplier))
        
        // Update trailing stop if trailing has started
        if trailing_started
            // Calculate new trailing stop using ATR
            potential_new_stop = close - (atr * atr_multiplier)
            // Only move stop loss up (never down) and ensure it's better than initial SL
            if potential_new_stop > trailing_stop and potential_new_stop > stop_loss
                trailing_stop := potential_new_stop
                // Update the exit order with new trailing stop
                exit_id = second_trade_taken ? "Long Exit 2nd" : "Long Exit"
                strategy.exit(exit_id, current_entry_id, stop=trailing_stop, limit=take_profit)
    
    else if strategy.position_size < 0  // Short position
        // Calculate current unrealized profit in points
        current_profit = entry_price - close
        profit_r = current_profit / initial_risk
        
        // Check if we should start trailing (after X R profit)
        if not trailing_started and profit_r >= profit_r_multiplier
            trailing_started := true
            // Start trailing from a level that's better than the initial stop
            trailing_stop := math.min(trailing_stop, close + (atr * atr_multiplier))
        
        // Update trailing stop if trailing has started
        if trailing_started
            // Calculate new trailing stop using ATR
            potential_new_stop = close + (atr * atr_multiplier)
            // Only move stop loss down (never up) and ensure it's better than initial SL
            if potential_new_stop < trailing_stop and potential_new_stop < stop_loss
                trailing_stop := potential_new_stop
                // Update the exit order with new trailing stop
                exit_id = second_trade_taken ? "Short Exit 2nd" : "Short Exit"
                strategy.exit(exit_id, current_entry_id, stop=trailing_stop, limit=take_profit)

// === SESSION END CLOSE ===
// Force close all positions at configured session end time (optional)
// FIXED: Using configurable hour/minute with timezone
if force_session_close and current_hour == session_end_hour and current_minute == session_end_minute
    // ADDED: Extend boxes immediately before session close to prevent timing issues
    if not na(current_profit_box)
        // Ensure minimum 8 bars width or extend to current bar, whichever is longer
        box_left = box.get_left(current_profit_box)
        min_right = box_left + 8
        final_right = math.max(min_right, bar_index)
        box.set_right(current_profit_box, final_right)
        current_profit_box := na  // Clear reference after extending
    if not na(current_sl_box)
        // Ensure minimum 8 bars width or extend to current bar, whichever is longer
        box_left = box.get_left(current_sl_box)
        min_right = box_left + 8
        final_right = math.max(min_right, bar_index)
        box.set_right(current_sl_box, final_right)
        current_sl_box := na  // Clear reference after extending
    
    strategy.close_all(comment="Session End Close")

// === ALERTS ===
alert_once_long = (strategy.position_size > 0) and (strategy.position_size[1] == 0)
alert_once_short = (strategy.position_size < 0) and (strategy.position_size[1] == 0)

alertcondition(alert_once_long, title="Long Entry (Once)", message="Long Entry Signal")
alertcondition(alert_once_short, title="Short Entry (Once)", message="Short Entry Signal")