
오픈 레인지 브레이커스 ATR 추적 스톱 전략은 오픈 레인지 브레이커스 (Opening Range Breakout) 과 스마트 시장 분석 (Smart Money Concepts) 을 결합한 정량 거래 시스템이다. 이 전략은 주식 시장 개시 후 5 분 (09: 30-09: 35 EST) 에 형성 된 가격 영역 브레이커스 기회를 포착하는 데 초점을 맞추고 거래 신호 품질을 보장하는 여러 가지 필터링 조건을 결합한다.
개장 범위를 돌파하는 ATR 추적 스톱 스트래치 전략의 핵심 논리는 시장 개장 후의 초기 가격 범위의 중요성에 기초한다. 이 전략은 먼저 특정 시간 창 (09:30-09:35 EST) 에서 가격의 최고점과 최저점을 포착하고 기록하여 “개장 범위를” (Opening Range) 을 형성한다.
오픈 영역 식별 및 뚫림 검증: 시스템은 지정된 시간 창 내에서 가격의 최고점과 최저점을 기록하고, 이후 돌파를 모니터링한다. 돌파는 두 가지 필터링 메커니즘을 통해 검증되어야 한다:
입학 메커니즘이 전략은 두 가지 접근 방식을 지원합니다.
손해 중지 설정이 시스템은 두 가지 유형의 상쇄를 제공합니다.
위험 관리시스템: 리스크: 리워드 멀티플라이어 (Risk:Reward Multiplier) 를 사용하여 자동으로 정지 위치를 계산하여 동적 리스크 관리를 구현합니다. 예를 들어, 2:1의 리스크: 리워드 비율을 설정하면 잠재적인 이익은 잠재적인 손실의 두 배입니다.
ATR 추적 손실: 수익이 기본 설정된 리스크-타임-비율에 도달하면, 시스템은 ATR 기반의 추적 스톱로스를 활성화할 수 있으며, 수익의 일부를 잠겨두고 트렌드를 계속 허용합니다.
2차 기회 거래: 초기 거래가 스톱로스 또는 실패를 유발하면, 시스템은 자동으로 상장 간 반향의 돌파 기회를 찾고, 당일 양방향 거래의 가능성을 실현한다.
고품질 거래 기회에 집중하세요.다중 검증 메커니즘을 통해 (그림 필터, 거리 필터) 이 전략은 가짜 돌파 거래를 크게 줄이고 승률을 높였다.
융통성 있는 입학제도: 즉각적 또는 회수적 입시를 지원하고, 다양한 거래 스타일과 시장 조건에 적합하다. 즉각적 입시는 강한 추세에 적합하며, 회수적 입시는 더 우수한 입시 가격을 얻을 수 있다.
자율적 위험 관리: 리스크에 대한 수익률에 대한 동적 정지 설정은 모든 거래가 일관된 위험 특성을 가지고 표준화된 자금 관리를 구현합니다.
수익을 극대화ATR는 이윤을 보호하면서도 손실을 추적하는 기능을 통해 강세를 지속할 수 있으며, 조기 퇴출을 방지합니다.
고도 시각화시스템: 시스템은 간격 표기, 뚫고 확인 표기, 거래 상태 표시, 입시/피해/정지 표기 등과 같은 포괄적인 시각 보조 기능을 제공하여 거래 의사결정을 향상시킵니다.
편견 없는 후검 설계전략의 전면적 적용barstate.isconfirmed모든 결정이 확인된 가격 데이터에 기반하고, 예측의 편차를 피하고, 실제 거래 환경에 맞도록 보장합니다.
2차 기회 제도: 2차 기회 거래 기능을 활성화하면, 전략은 초기 방향에서 잘못된 판단을 할 때 시장 변화에 빠르게 적응하고, 역전 기회를 포착하여 자금 활용 효율성을 높일 수 있습니다.
세션 관리 최적화: 내장된 세션 종료 자동 평지 기능은 하룻밤 동안 상장을 하지 않도록 보장하고, 하룻밤 동안의 위험을 감소시킵니다.
범위를 형성하는 기간의 변동 위험오프닝 간격이 형성되는 기간 (09:30-09:35) 에 시장이 비정상적으로 변동하여 간격이 너무 넓거나 너무 좁아질 수 있습니다. 너무 넓은 간격은 너무 큰 중지 손실을 초래할 수 있으며 너무 좁은 간격은 종종 가짜 돌파구를 유발할 수 있습니다. 해결 방법: 개장 간격 크기의 필터 조건을 추가하여 비정상적인 간격을 제외하거나 거래 날짜 필터를 조정하여 중요한 경제 데이터 발표일과 같은 특정 날의 높은 변동성을 피하는 것이 고려 될 수 있습니다.
은 후 급격한 철수 위험: 유효한 돌파 이후 시장이 급격한 회수 현상이 발생할 수 있으며, 스톱 손실이 유발된 후 시장이 원래의 방향으로 움직일 수 있습니다. 해결 방법더 느슨한 정지 설정을 사용하거나, 더 나은 입점 가격과 더 적은 위험 노출을 위해 입점 메커니즘을 조정하는 것을 고려하십시오.
신호 품질은 필터 설정에 의존: 뚫고 검증된 그림자 필터링과 거리 필터링 파라미터 설정은 신호 품질에 중대한 영향을 미칩니다. 부적절한 파라미터는 좋은 거래 기회를 필터링하거나 너무 많은 저품질의 신호를 수신할 수 있습니다. 해결 방법: 역사 회귀를 통해 필터 변수를 최적화하여 특정 시장 및 품종에 대한 최적의 설정을 찾습니다. 시장의 변동적 동력에 따라 필터링 기준을 조정하는 적응 변수를 사용하는 것을 고려하십시오.
추적 스톱 손실 변수 감수성:ATR 추적 중지 손실의 파라미터를 너무 단단하게 설정하면 작은 회귀에서 조기 퇴출을 초래할 수 있으며, 너무 느긋하게 설정하면 너무 많은 회귀를 초래할 수 있다. 해결 방법: 타겟 품종의 역사적인 변동 특성에 따라 ATR 주기와 배수를 조정합니다. 일부 포지션은 고정 스톱을 사용하고 일부 포지션은 추적 스톱을 사용하는 분기 평점 전략을 적용하는 것을 고려합니다.
거래 빈도 제한전략: 하루에 최대 두 번의 거래가 이루어집니다. (초기 거래와 2차 기회 거래) 하루의 모든 기회를 충분히 활용하지 못할 수도 있습니다. 해결 방법확장 전략을 고려하여 하루의 다른 시간대의 중요한 가격 범위를 모니터링하거나 다른 기술 지표와 결합하여 거래 신호 출처를 증가시키는 복합 전략을 수립하십시오.
연장 간격 주기 적응: 현재 전략은 고정된 5분 상장 간격을 사용하며, 시장의 변동성 동적에 따라 간격의 길이를 고려할 수 있다. 낮은 변동성 시장에서는 간격 시간을 3분으로 단축할 수 있고, 높은 변동성 시장에서는 10분으로 연장할 수 있으며, 다른 시장 상태에 더 잘 적응할 수 있다.
합성 물량 확인: 브레이크 검증 메커니즘에서 거래량 필터링 조건을 추가하여, 이전 몇 주기의 평균 거래량보다 뚜렷하게 높은 거래량을 요구하며, 브레이크 효과를 향상시킵니다. 이것은 브레이크 브레이크 거래량과 이전 N 주기의 거래량 평균값의 비율을 계산하여 달성 할 수 있습니다.
다중 시간 프레임 분석: 더 높은 시간 프레임의 트렌드 방향 필터를 도입하여 일선 또는 시간선 트렌드 방향과 돌파 방향이 일치하는 경우에만 입문하여 거래의 승률을 높여줍니다. 간단한 이동 평균 경사 또는 더 고급 트렌드 지표로 더 높은 시간 프레임의 트렌드를 결정할 수 있습니다.
자금 관리 최적화: 역동적인 포지션 규모 조정 메커니즘을 구현하여, 역사적인 변동성, 현재 계정 규모 및 최근 성과에 따라 자동으로 계약 수를 조정하여 더 정교한 위험 통제를 구현합니다. 예를 들어, 연속적인 수익 후 포지션을 점차적으로 증가시키고, 연속적인 손실 후 포지션을 감소시킵니다.
통합 기계 학습 모델: 기계 학습 모델을 도입하여 브레이크 품질을 평가하고, 역사 데이터 훈련 모델을 통해 가장 성공적인 브레이크 패턴을 식별합니다. 특징은 오픈 분량의 크기, 시장의 변동성, 전날 거래의 가격 움직임, 특정 시간 패턴 등이 포함될 수 있습니다.
2차 기회 거래 논리를 강화합니다.2차 기회 거래의 트리거 조건을 최적화하여 초기 거래의 실패뿐만 아니라 시장 구조의 변화와 새로운 동력 지표를 고려하여 2차 거래의 성공률을 높여줍니다.
개인화 품종 매개 변수: 다양한 거래 품종을 위해 최적화된 파라미터 세트를 개발하여 각 품종의 고유한 변동성 및 가격 행동을 고려합니다. 예를 들어, 변동성이 높은 품종은 더 느슨한 필터 설정과 더 보수적인 리스크 수익률을 필요로 할 수 있습니다.
시장 감정 지표 통합: 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")