
Breakout Opening Range (ATR) Tracking Stop Loss Strategy adalah sistem perdagangan kuantitatif yang menggabungkan Breakout Opening Range (Opening Range Breakout) dengan analisis pasaran pintar (Smart Money Concepts). Strategi ini memberi tumpuan kepada menangkap peluang Breakout dalam Julat Harga yang terbentuk 5 minit selepas pembukaan pasaran saham AS (09:30-09:35 EST), dan menggabungkan pelbagai syarat penapisan untuk memastikan kualiti isyarat perdagangan.
Logik teras strategi penembusan ATR untuk menjejaki hentian terhad berdasarkan kepentingan julat harga awal selepas pembukaan pasaran. Strategi ini pertama-tama menangkap dan merekodkan harga tertinggi dan terendah pada tetingkap masa tertentu (09:30-09:35 EST) untuk membentuk “Julat Pembukaan”. Kemudian, sistem memantau harga untuk tindakan penembusan dalam julat ini, menggabungkan mekanisme penting berikut untuk memastikan kualiti perdagangan:
Pengiktirafan dan pengesahan penembusan dalam kawasan terbukaSistem ini merekodkan harga tertinggi dan terendah dalam tetingkap masa yang ditetapkan, kemudian memantau penembusan. Penembusan mesti disahkan melalui mekanisme penapisan berganda:
Mekanisme kemasukanStrategi ini menyokong dua jenis kemasukan:
Tetapan Stop LossSistem ini menyediakan dua jenis penangguhan:
Pengurusan RisikoSistem menggunakan penggandaan ganjaran risiko (Risk:Reward Multiplier) untuk mengira kedudukan berhenti secara automatik, mewujudkan pengurusan risiko yang dinamik. Sebagai contoh, menetapkan nisbah ganjaran risiko 2: 1 bermakna potensi keuntungan adalah dua kali ganda daripada potensi kerugian.
ATR mengesan kerugian: Apabila keuntungan mencapai nisbah risiko-balas yang ditetapkan, sistem boleh mengaktifkan tracking stop loss berdasarkan ATR, mengunci sebahagian daripada keuntungan sambil membenarkan trend berterusan.
Perdagangan peluang keduaSistem ini secara automatik mencari peluang terobosan yang terbalik dalam tempoh perdagangan terbuka, untuk mewujudkan kemungkinan perdagangan dua hala pada hari itu.
Fokus pada peluang dagangan berkualitiDengan menggunakan mekanisme pengesahan berganda (penyaringan garis bayangan, penyaringan jarak), strategi ini dapat mengurangkan jumlah transaksi palsu dan meningkatkan kadar kemenangan.
Mekanisme kemasukan yang fleksibel: Mendukung kemasukan segera atau penarikan balik, menyesuaikan diri dengan gaya perdagangan dan keadaan pasaran yang berbeza. Masukan segera sesuai dengan trend yang kuat, sementara kemasukan balik dapat memperoleh harga kemasukan yang lebih baik.
Pengurusan risiko penyesuaianPengaturan Hentian Dinamis Berasaskan Risiko-Pengembalian-Nombor-Risiko memastikan setiap dagangan mempunyai ciri-ciri risiko yang konsisten, mewujudkan pengurusan dana yang standard.
Memaksimumkan keuntunganATR menjejaki fungsi stop loss sambil melindungi keuntungan yang telah dicapai, membolehkan pergerakan yang kuat berterusan dan mengelakkan penarikan awal.
Visibiliti tinggiSistem ini menyediakan fungsi bantuan visual yang komprehensif, termasuk penanda jarak, penanda pengesahan penembusan, petunjuk status perdagangan, penanda masuk / henti / berhenti, dan lain-lain, untuk meningkatkan intuisi keputusan perdagangan.
Reka bentuk yang tidak berat sebelahPengambilan sepenuhnya strategi:barstate.isconfirmedMemastikan semua keputusan berdasarkan data harga yang disahkan, mengelakkan bias ramalan, dan sesuai dengan keadaan perdagangan sebenar.
Mekanisme peluang keduaDengan mengaktifkan fungsi perdagangan peluang kedua, strategi dapat menyesuaikan diri dengan cepat dengan perubahan pasaran, menangkap peluang terbalik, dan meningkatkan kecekapan penggunaan dana.
Pengurusan sesi yang lebih baikFungsi penutupan sesi automatik yang terbina dalam memastikan tidak menyimpan dagangan semalaman, mengurangkan risiko bermalam.
Risiko pergerakan dalam tempoh pembentukanDalam tempoh pembentukan antara 09:30-09:35, pasaran mungkin mengalami turun naik yang luar biasa, yang menyebabkan antara terlalu lebar atau terlalu sempit. Antara yang terlalu lebar boleh menyebabkan kerugian yang terlalu besar, dan antara yang terlalu sempit boleh mencetuskan pecah palsu yang kerap. PenyelesaianAnda boleh mempertimbangkan untuk menambah ukuran penapis untuk tempoh terbuka, untuk mengecualikan tempoh yang tidak biasa; atau menyesuaikan penapis tarikh dagangan untuk mengelakkan hari-hari tertentu yang mempunyai turun naik yang tinggi (seperti hari pengumuman data ekonomi penting).
Risiko penarikan balik selepas penembusan: Pasaran mungkin akan berpatah balik selepas penembusan yang berkesan, menyebabkan pasaran terus bergerak ke arah yang sama selepas penangguhan telah dicetuskan. PenyelesaianPertimbangkan untuk menggunakan tetapan henti rugi yang lebih longgar, seperti henti rugi pada jarak yang sama; atau menyesuaikan mekanisme kemasukan untuk memutar balik kemasukan untuk mendapatkan harga kemasukan yang lebih baik dan pendedahan risiko yang lebih kecil.
Kualiti isyarat bergantung kepada tetapan penapisPencerobohan: Penapis garis bayangan yang disahkan dan parameter penapis jarak mempunyai kesan yang ketara terhadap kualiti isyarat, parameter yang tidak betul mungkin menapis peluang perdagangan yang baik atau menerima terlalu banyak isyarat berkualiti rendah. PenyelesaianMengoptimumkan parameter penapis dengan mengkaji semula sejarah untuk mencari tetapan terbaik untuk pasaran dan varieti tertentu. Pertimbangkan untuk menggunakan parameter penyesuaian dan menyesuaikan standard penapisan mengikut dinamik turun naik pasaran.
Sensitiviti parameter hentiTetapan parameter ATR yang mengesan kerugian terlampau ketat boleh menyebabkan penarikan diri terlalu awal dalam penyesuaian kecil, manakala tetapan yang terlalu longgar boleh menyebabkan terlalu banyak pulangan keuntungan. Penyelesaian: Sesuaikan kitaran ATR dan penggandaan berdasarkan ciri-ciri turun naik sejarah varieti sasaran; pertimbangkan untuk melaksanakan strategi seting seting, beberapa kedudukan menggunakan stop loss tetap, dan beberapa kedudukan menggunakan stop loss pengesanan.
Had frekuensi daganganStrategi: Perdagangan maksimum dua kali sehari (perdagangan awal dan perdagangan peluang kedua) mungkin tidak dapat memanfaatkan sepenuhnya semua peluang dalam sehari. PenyelesaianPertimbangan strategi pengembangan untuk memantau jarak harga penting pada waktu lain dalam hari; atau strategi gabungan untuk menambah sumber isyarat dagangan yang digabungkan dengan petunjuk teknikal lain.
Siklus masa lapangStrategi semasa menggunakan selang terbuka 5 minit yang tetap, boleh mempertimbangkan panjang selang penyesuaian mengikut dinamik turun naik pasaran. Dalam pasaran turun naik yang rendah, selang boleh dipersingkat menjadi 3 minit, dan dalam pasaran turun naik yang tinggi, ia boleh dilanjutkan menjadi 10 minit, lebih baik untuk menyesuaikan diri dengan keadaan pasaran yang berbeza.
Pengesahan kuantiti gabunganMenambah syarat penapisan jumlah transaksi dalam mekanisme pengesahan penembusan, yang memerlukan jumlah transaksi pada saat penembusan secara ketara lebih tinggi daripada jumlah transaksi rata-rata dalam beberapa kitaran sebelumnya, meningkatkan keberkesanan penembusan. Ini dapat dicapai dengan mengira nisbah jumlah transaksi penembusan dengan nilai purata jumlah transaksi N kitaran sebelumnya.
Analisis pelbagai kerangka masa: Memperkenalkan penapis arah trend pada bingkai masa yang lebih tinggi, masuk hanya apabila arah trend garis matahari atau garis jam selaras dengan arah penembusan, meningkatkan peluang kemenangan perdagangan. Trend bingkai masa yang lebih tinggi boleh ditentukan dengan simpangan purata bergerak sederhana atau penunjuk trend yang lebih tinggi.
Pengurusan wang yang optimum: melaksanakan mekanisme penyesuaian saiz kedudukan yang dinamik, menyesuaikan jumlah kontrak secara automatik berdasarkan turun naik sejarah, saiz akaun semasa dan prestasi terkini, untuk mengawal risiko yang lebih halus. Sebagai contoh, meningkatkan kedudukan secara beransur-ansur selepas keuntungan berturut-turut dan mengurangkan kedudukan selepas kerugian berturut-turut.
Model pembelajaran mesin bersepaduModel pembelajaran mesin diperkenalkan untuk menilai kualiti penembusan, mengenal pasti model penembusan yang paling mungkin berjaya melalui model latihan data sejarah. Ciri-ciri boleh merangkumi saiz ruang terbuka, turun naik pasaran, pergerakan harga pada hari perdagangan sebelumnya, dan lain-lain.
Peningkatan Logik Perniagaan Peluang KeduaMengoptimumkan keadaan pemicu perdagangan peluang kedua, bukan hanya berdasarkan kegagalan perdagangan awal, tetapi juga mempertimbangkan perubahan struktur pasaran dan penunjuk momentum baru, untuk meningkatkan kadar kejayaan perdagangan kedua.
Parameter varieti peribadiPerkembangan set parameter yang dioptimumkan untuk pelbagai jenis perdagangan, dengan mengambil kira sifat turun naik dan tingkah laku harga yang unik untuk setiap jenis. Sebagai contoh, jenis yang lebih turun naik mungkin memerlukan tetapan penapis yang lebih longgar dan nisbah ganjaran risiko yang lebih konservatif.
Menyatakan sentimen pasaranMemperkenalkan indeks VIX atau penunjuk sentimen pasaran lain, menyesuaikan parameter strategi atau melarang perdagangan sementara semasa sentimen pasaran yang melampau, mengelakkan keadaan ketidakpastian yang tinggi.
Breakout ATR Tracking Stop Loss Strategy adalah sistem perdagangan kuantitatif yang tersusun dengan baik yang menggabungkan penembusan dalam tempoh pembukaan, mekanisme penapisan pintar, pilihan masuk yang fleksibel, dan fungsi pengurusan risiko yang canggih. Strategi ini sangat sesuai untuk perdagangan dalam sehari di pasaran saham dan niaga hadapan AS, yang menghasilkan keuntungan dengan menangkap penembusan arah selepas pembukaan.
Nilai teras strategi ini adalah mekanisme pengesahan bertingkat dan sistem pengurusan risiko, yang secara signifikan mengurangkan perdagangan palsu melalui penapis garis bayangan dan jarak, sambil menggunakan perkalian ganjaran risiko dan pengesanan henti kerugian ATR untuk memastikan pendedahan risiko dan perlindungan keuntungan yang konsisten. Fungsi perdagangan peluang kedua menambah fleksibiliti dan peluang keuntungan tambahan kepada strategi.
Walaupun strategi ini mempunyai banyak kelebihan, pengguna perlu mengambil perhatian terhadap kepentingan pengoptimuman parameter, yang mungkin memerlukan penyesuaian khusus untuk pasaran dan varieti yang berbeza untuk mencapai kesan terbaik. Pada masa yang sama, peniaga disarankan untuk menggunakan strategi ini sebagai sebahagian daripada sistem perdagangan yang lengkap, digabungkan dengan analisis pasaran yang lebih luas dan prinsip pengurusan risiko.
Strategi ini mempunyai potensi untuk meningkatkan kestabilan dan keuntungan lebih lanjut dan menjadi alat yang kuat dalam kotak alat peniaga profesional dengan melaksanakan arah pengoptimuman yang disyorkan, terutamanya parameter yang sesuai, analisis jangka masa yang banyak dan sistem pengurusan wang yang dipertingkatkan.
/*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")