Code: Select all
//@version=5
strategy("Pro Session Circuit Breaker & MTF Logic", overlay=true, calc_on_every_tick=true, process_orders_on_close=true, max_labels_count=50)
// =========================================================================
// 1. INPUTS: MODULAR ARCHITECTURE
// Grouping inputs keeps the UI clean and parameter optimization efficient
// =========================================================================
grp_sess = "1. Session & Timing"
sess_time = input.session("0800-1700", title="Trading Session", group=grp_sess)
tz = input.string("UTC", title="Timezone", group=grp_sess)
grp_risk = "2. Risk Management & Lockout"
max_losses = input.int(2, title="Max Consecutive Losses", group=grp_risk)
risk_pct = input.float(1.0, title="Risk Per Trade (%)", step=0.1, group=grp_risk)
grp_mtf = "3. M15 Structural Filter"
min_body_ratio = input.float(0.25, title="Min M15 Body/Range Ratio", step=0.05, group=grp_mtf, tooltip="Rejects execution if M15 prints a doji/chop candle.")
// =========================================================================
// 2. MTF SYNCHRONIZATION: M15 STRUCTURAL CHECK
// lookahead = barmerge.lookahead_off ensures zero repainting in backtests
// =========================================================================
[m15_open, m15_close, m15_high, m15_low] = request.security(syminfo.tickerid, "15", [open, close, high, low], lookahead=barmerge.lookahead_off)
m15_range = m15_high - m15_low
m15_body = math.abs(m15_open - m15_close)
// Protect against divide-by-zero on flat candles
m15_ratio = m15_range == 0 ? 0 : (m15_body / m15_range)
is_m15_valid = m15_ratio >= min_body_ratio
// =========================================================================
// 3. STATE MANAGEMENT: SESSION & CIRCUIT BREAKERS
// =========================================================================
in_sess = not na(time(timeframe.period, sess_time, tz))
new_sess = in_sess and not nz(in_sess[1])
var int consec_losses = 0
var bool is_locked = false
var int closed_trades = 0
var float session_pnl = 0.0
// Reset state strictly at the session open
if new_sess
consec_losses := 0
is_locked := false
session_pnl := 0.0
// Evaluate PnL asynchronously upon trade closure
if strategy.closedtrades > closed_trades
last_profit = strategy.closedtrades.profit(strategy.closedtrades - 1)
session_pnl += last_profit
if last_profit < 0
consec_losses += 1
else if last_profit > 0
consec_losses := 0 // Winner resets the streak
if consec_losses >= max_losses
is_locked := true
closed_trades := strategy.closedtrades
// =========================================================================
// 4. POSITION SIZING & EXECUTION
// =========================================================================
// Dynamic sizing based on ATR to represent a realistic stop loss distance
sl_pips = ta.atr(14) * 1.5
risk_capital = (strategy.equity * risk_pct) / 100
// Convert distance to quantity based on asset point value
pos_qty = sl_pips > 0 ? (risk_capital / (sl_pips * syminfo.mintick * syminfo.pointvalue)) : 0
// Placeholder M1 Trigger
entry_signal = ta.crossover(ta.ema(close, 9), ta.ema(close, 21))
// The Ultimate Gatekeeper
if entry_signal and in_sess and not is_locked and is_m15_valid
strategy.entry("Long", strategy.long, qty=pos_qty)
strategy.exit("Exit", "Long", loss=sl_pips, profit=sl_pips * 2) // 1:2 R:R
// =========================================================================
// 5. VISUALIZATION: THE PRO HUD (Heads-Up Display)
// =========================================================================
bgcolor(is_locked ? color.new(color.red, 92) : na, title="Locked Out BG")
bgcolor(new_sess ? color.new(color.blue, 85) : na, title="Session Reset BG")
// Initialize Table Object once
var table hud = table.new(position.top_right, 2, 4, border_width=1, border_color=color.new(color.gray, 50))
// Update HUD only on the last bar to save processing power
if barstate.islast
// Row 0: System Status
table.cell(hud, 0, 0, "System Status", text_color=color.gray, bgcolor=color.new(color.black, 20))
table.cell(hud, 1, 0, is_locked ? "LOCKED" : in_sess ? "ACTIVE" : "WAITING", text_color=color.white, bgcolor=is_locked ? color.new(color.red, 30) : in_sess ? color.new(color.green, 30) : color.new(color.gray, 30))
// Row 1: Loss Streak
table.cell(hud, 0, 1, "Loss Streak", text_color=color.gray, bgcolor=color.new(color.black, 20))
table.cell(hud, 1, 1, str.tostring(consec_losses) + " / " + str.tostring(max_losses), text_color=color.white, bgcolor=consec_losses > 0 ? color.new(color.orange, 50) : color.new(color.black, 20))
// Row 2: M15 Structure
table.cell(hud, 0, 2, "M15 Structure", text_color=color.gray, bgcolor=color.new(color.black, 20))
table.cell(hud, 1, 2, is_m15_valid ? "CLEAN" : "CHOP", text_color=color.white, bgcolor=is_m15_valid ? color.new(color.green, 50) : color.new(color.red, 50))
// Row 3: Session PnL
table.cell(hud, 0, 3, "Session Net PnL", text_color=color.gray, bgcolor=color.new(color.black, 20))
table.cell(hud, 1, 3, "$" + str.tostring(math.round(session_pnl, 2)), text_color=session_pnl >= 0 ? color.green : color.red, bgcolor=color.new(color.black, 20))