オープニングレンジ突破ATRトレーリングストップロス戦略

ATR OR SMC 量化交易 追踪止损 开盘区间突破 风险管理 交易自动化
作成日: 2025-07-31 10:51:36 最終変更日: 2025-07-31 10:51:36
コピー: 0 クリック数: 267
2
フォロー
319
フォロワー

オープニングレンジ突破ATRトレーリングストップロス戦略 オープニングレンジ突破ATRトレーリングストップロス戦略

戦略概要

オープニングレンジブレイクATRトラッキングストップ戦略は,オープニングレンジブレイクとスマート市場分析のSmart Money Conceptsを組み合わせた定量取引システムである.この戦略は,株式市場の開幕後5分後に形成される価格区間のブレイクチャンスを捕捉することに専念し,複数のフィルタリング条件を組み合わせて取引信号の品質を確保する.システムは,即時またはリコール入場をサポートし,リスクと報酬の比較をダイナミックに調整する仕組みを採用し,利益管理を最適化するためにATR (Average True Range) トラッキングストップを選択することができます.この戦略には,最初の取引が失敗した後に反転の機会を捕捉することを可能にする”二次チャンス”取引機能もあります.

戦略原則

オープニング・レンジを突破するATRのストップ・トラッキング戦略の核心的な論理は,市場開封後の初期価格区間の重要性に基づいています. この戦略は,まず,特定の時間窓 (09:30-09:35 EST) で価格の最高点と最低点を捕捉し,記録し”,オープニング・レンジ” (Opening Range) を形成します. その後,システムは,この区間の突破行動を監視し,取引品質を確保するために以下の重要なメカニズムを組み合わせます.

  1. オープン区間識別と突破検証: システムは,指定された時間ウィンドウ内の価格の最高点と最低点を記録し,その後の突破を監視する. 突破は,二重フィルタリングメカニズムによって検証されなければならない:

    • 図影線比率フィルタ:突破の上/下影線が実体指定比率を超えないようにして,偽突破を避ける.
    • 突破距離のフィルター:価格の突破幅が合理的で,小さすぎないように (微小な突破を避ける),大きすぎないように (過剰な延長を避ける) を確保する.
  2. 入学制度戦略は2つの入学方法をサポートしています.

    • 即時入場:有効な突破が確認された同じチャートでの閉盘価格に直接入場する.
    • 逆戻り入場:価格が逆戻りして突破実体指定パーセント位置に再入場するまで待つ.通常は50%逆戻り位置に設定できます.
  3. ストップダスト設定システムには2種類のストップ・ローが用意されています.

    • 突破のストップ:突破の極限点の外にストップを設定する.
    • 対向区間ストップ:開盤区間の対向境界の外側にストップを設定し,価格により大きな波動空間を与えます.
  4. リスク管理システムでは,リスク:リワード・マルチプライヤーを採用し,自動でストップポジションを計算し,ダイナミックなリスク管理を実現する.例えば,2:1のリスク・リワード・比率を設定すると,潜在的利益は潜在的損失の2倍になる.

  5. ATRの追跡停止: 利益が既定のリスク・リターン比率に達すると,システムはATRベースの追跡ストップを起動し,利益の一部をロックし,トレンドの継続を許可します.

  6. 2次チャンス取引スタートトレードがストップ・ローズや失敗を誘発すると,システムは自動的に開場区間の逆転の突破機会を探し,当日の双方向取引の可能性を実現します.

戦略的優位性

  1. 質の高い取引機会に焦点を当てて複数の検証メカニズム (影線フィルター,距離フィルター) を使って,偽突破取引を大幅に削減し,勝利率を向上させる.

  2. フレキシブルな入学制度:即時またはリコール入場をサポートし,異なる取引スタイルと市場条件に対応します.即時入場は強いトレンドに適しており,リコール入場は優良な入場価格を得ることができます.

  3. リスク管理に適応する: リスクと報酬の倍数に基づくダイナミックストップセットは,各取引が一貫したリスク特性を有することを保証し,標準化された資金管理を実現する.

  4. 利益の最大化ATRのストップトラッキング機能は,既得利益を保護しながら,強気なトレンドの継続を許し,早退を回避する.

  5. 高度可視化システムには,区間標識,突破確認標識,取引状況指示,入場/止損/停止標識など,取引意思決定の直感性を向上させるための包括的な視覚補助機能があります.

  6. 偏見のない設計戦略を全面的に採用するbarstate.isconfirmedすべての意思決定が確認された価格データに基づいて,予測偏差を回避し,実際の取引環境に適合することを保証します.

  7. 2次チャンスメカニズム: 戦略は,二次機会取引機能を有効にすることで,初期方向を誤って判断したときに,市場の変化に素早く適応し,逆転の機会を捕捉し,資金の利用効率を向上させることができます.

  8. セッション管理の最適化: 内蔵のセッション終了自動平仓機能は,夜中にポジションを保持しないことを保証し,夜間リスクを下げます.

戦略リスク

  1. 区間形成期変動リスク:開盤区間の形成期 ((09:30-09:35),市場には異常な波動が起こり,区間が過幅または過狭になる可能性があります.過幅した区間は過大なストップに繋がり,過狭な区間は頻繁に偽ブレイクを引き起こす可能性があります. 解決方法: オープン区間サイズフィルタを増加させ,異常区間を除外する条件を考慮する; または,取引日フィルタを調整して,高変動の特定の日を回避する (重要な経済データ発表日など).

  2. 突破後,急激な撤退の危険性: 効果的突破後,市場が急激に後退する可能性があるため,ストップレードが誘発された後,市場は元の方向に動き続ける. 解決方法:対向区間ストップのようなより緩やかなストップ設定を使用することを検討する. より良い入場価格とより少ないリスク暴露を得るために,入場をリコールするために入場メカニズムを調整する.

  3. 信号の質はフィルター設定に依存する: 突破検証の影線フィルターと距離フィルターパラメータの設定は,信号品質に顕著な影響を及ぼし,不適切なパラメータは,良い取引機会をフィルターしたり,低品質の信号を過剰に受け取ったりする可能性があります. 解決方法: フィルターパラメータを過去追及で最適化し,特定の市場と品種のための最適な設定を見つけ; 適応パラメータを使用し,市場の波動的な動向に応じてフィルタリング基準を調整することを検討する.

  4. ストップダストパラメータの感度を追跡:ATRのストップ・ロスを追跡するパラメータの設定が厳しすぎると,小回調で早退が起こり,過度な設定は利回りが過剰に起こりうる. 解決方法ターゲットの品種の歴史的な変動特性をベースにATR周期と倍数を調整する. 固定ストップを部分的に,追跡ストップを部分的に使用する,分期平仓戦略を導入することを考慮する.

  5. 取引頻度制限戦略: 一日に最大2回の取引を行う (初回取引と二次機会取引) が,その日のすべての機会を十分に利用できない可能性があります. 解決方法: 展開戦略を考慮し,日中の他の時間帯の重要な価格区間を監視する; または,他の技術指標と組み合わせて複合戦略を形成し,取引信号源を増やす.

戦略最適化の方向性

  1. 開盤間周期に適応する:現在の戦略は,固定5分間の開盤区間を使用し,市場の変動の動向に応じて区間の長さを調整することができます.低波動市場では,区間時間を3分に短縮し,高波動市場では,10分に延長し,異なる市場状態に適応することができます.

  2. 複合交量確認: 突破検証メカニズムで交差量フィルタリング条件を追加し,突破時の交差量が前回のサイクル平均より大幅に高いことを要求し,突破効果を高める.これは,突破時の交差量と前Nサイクル交差量平均の比率を計算することによって実現できる.

  3. 多時間枠分析:より高い時間枠のトレンド方向フィルタを導入し,日線または時間線のトレンド方向が突破方向と一致するときにのみ入場し,取引の勝率を高めます. 単純な移動平均の斜率またはより高度なトレンド指標を使用して,より高い時間枠のトレンドを決定できます.

  4. 資金管理の最適化: 動的なポジションスケール調整メカニズムを導入し,歴史の変動,現在のアカウントのサイズ,近年の業績に基づいて契約数を自動的に調整し,より精密なリスク制御を実現する.例えば,連続した利益の後,ポジションを徐々に増やし,連続した損失の後,ポジションを減少させる.

  5. 統合された機械学習モデル: 機械学習モデルを導入し,ブレークの質を評価し,歴史データ訓練モデルを使用して最も成功する可能性のあるブレークのパターンを識別します. 特徴には,開設区間のサイズ,市場の変動性,前日取引の価格動向,特定の時間パターンなどがあります.

  6. 2次機会取引の論理を強化する: 初期取引の失敗だけでなく,市場構造の変化と新興の動力指標を考慮して,二次取引の成功率を向上させるための二次機会取引のトリガー条件を最適化する.

  7. パーソナライズされた品種パラメータ: 異なる取引品種のために最適化されたパラメータセットを開発し,それぞれの品種の独特の変動特性と価格行動を考慮する.例えば,波動性の高い品種は,より緩やかなフィルター設定とより保守的なリスク・リターン比率を必要とします.

  8. 市場情緒指標を統合する:VIX指数または他の市場情緒指標を導入し,極端な市場情緒の間,戦略パラメータを調整するか,一時的に取引を禁止し,高い不確実性の環境を回避する.

要約する

オープニング区間突破ATRトラッキングストップストラテジーは,オープニング区間突破,スマートフィルタリング機構,柔軟なエントリーオプション,および高度なリスク管理機能を巧妙に組み合わせた,構造的に整った量化取引システムです. この戦略は,米国株式市場と期貨市場の当日取引に特に適しており,オープニング後の方向的な突破をキャプチャすることで利益を得ることができます.

戦略の核心的な価値は,多層の検証機構とリスク管理システムで,影線と距離フィルターによって偽突破取引を大幅に削減し,リスクリターン比率の倍数とATRのストップロスを使用して,一貫したリスク露出と利益保護を確保しています.二次機会取引機能は,戦略に適応性と追加の収益の機会を追加しています.

この戦略は多くの利点があるにもかかわらず,ユーザーはパラメータの最適化の重要性に注意する必要があります.異なる市場と品種は,最適な効果を達成するためにターゲットを合わせた調整を必要とする可能性があります.同時に,トレーダーは,この戦略を完全な取引システムの一部として,より広範な市場分析とリスク管理の原則と組み合わせて使用することをお勧めします.

この戦略は,推奨された方向の最適化,特に自適性パラメータ,マルチタイムフレーム分析,強化された資金管理システムを実装することで,その安定性と収益性をさらに向上させ,プロフェッショナルトレーダーのツールキットに強力なツールとなる可能性を秘めています.

ストラテジーソースコード
/*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")