Building a Systematic Edge: Inside the Nifty 50 AI Probability

 



If you trade the Nifty 50, you know how quickly the market can shift. Relying on gut feeling or a single indicator is a surefire way to get shaken out by intraday noise. To consistently find high-probability setups, you have to treat the market like a complex system one that requires systematic testing, validation, and strict rule-based execution.

I recently put together a custom Pine Script indicator the Nifty AI Probability Engine designed to strip away the emotion and reduce trade entries to pure, weighted probabilities.

Here is a look under the hood at how the logic works.

1. Defining the Market Regime (The Context)

A common vulnerability in many trading strategies is treating all market environments the same. A momentum oscillator that works beautifully in a range will destroy your capital in a strong trend.

To solve this, the engine first establishes a "Market Regime" using the Average Directional Index (ADX). By normalizing the ADX, the script determines if the market is actually trending:

$Regime\_Trend = \min\left(\frac{ADX}{30.0}, 1.0\right)$

If the regime trend is above 0.5, the engine activates. If it's choppy and directionless, the system stays out.

2. The Four Pillars of the Scoring System

Instead of waiting for a perfect alignment of a dozen indicators, the engine assigns normalized base scores across four critical categories:

  • Trend: Evaluates price action relative to the 200 EMA and Supertrend direction.
  • Momentum: Checks if the RSI is in the sweet spot (55-70) and if the MACD is crossing its signal line.
  • Volume: Compares current volume against a 20-period SMA to ensure institutions are participating in the move.
  • Volatility: Uses VWAP (Volume Weighted Average Price) to confirm the intraday value baseline.

3. Adaptive AI Weighting

Here is where the engine gets dynamic. The weights assigned to Trend, Momentum, Volume, and Volatility aren't static; they adapt based on the market regime calculated earlier.

For example, as the trend strengthens, the algorithm dynamically increases the weight of the trend component and decreases the reliance on momentum:

$Weight_{trend} = 0.25 + (Regime\_Trend \times 0.25)$

$Weight_{mom} = 0.30 - (Regime\_Trend \times 0.10)$

4. The Execution (Calls vs. Puts)

Once the system calculates the final probability (from 0 to 99%), it filters the signals through a short-term directional bias (EMA 9 vs. EMA 21 crossover).

If the probability exceeds the strict 75% trigger threshold, it prints a clear, actionable label on the chart AI CALL or AI PUT along with a real-time dashboard tracking the current regime and bias.

Pine Script:

//@version=6
indicator("⚡ NIFTY AI SCALPER PRO [v7]", overlay=true, max_labels_count=500, max_lines_count=500, max_boxes_count=100)

// ╔══════════════════════════════════════════════════════════════════╗
// ║                   GLOBAL FUNCTIONS                               ║
// ╚══════════════════════════════════════════════════════════════════╝
f_price(p) => str.tostring(math.round(p, 1))
f_pts(p)   => str.tostring(math.round(p, 0)) + "p"

score_col(v, mx) =>
    r = v / mx
    r >= 0.75 ? color.new(#00E676, 0) : r >= 0.45 ? color.new(#FFB300, 0) : color.new(#FF5252, 0)

make_pill(x, y, txt, bg, tc) =>
    label.new(x, y, txt,
        xloc=xloc.bar_index, style=label.style_label_left,
        color=bg, textcolor=tc, size=size.small, textalign=text.align_left)

// ╔══════════════════════════════════════════════════════════════════╗
// ║                        INPUTS                                    ║
// ╚══════════════════════════════════════════════════════════════════╝
g_ai  = "🧠 AI Engine"
g_smc = "🏦 Smart Money"
g_sig = "🎯 Signal Filters"
g_ex  = "💰 Exit & Targets"
g_ses = "🕐 Session"
g_vis = "📊 Display"

// AI
min_prob    = input.int  (68,    "Min Signal Probability (%)", group=g_ai, minval=50, maxval=99)
warmup_thr  = input.int  (55,    "Warmup Alert Threshold (%)", group=g_ai, minval=40, maxval=95)
ml_lookback = input.int  (50,    "ML Pattern Lookback (bars)", group=g_ai, minval=20, maxval=200)

// SMC
ob_lookback = input.int  (10,    "Order Block Lookback",       group=g_smc, minval=3, maxval=30)
show_ob     = input.bool (true,  "Show Order Blocks",          group=g_smc)
show_bos    = input.bool (true,  "Show BOS / CHoCH Labels",   group=g_smc)
swing_len   = input.int  (10,    "Swing Point Length",         group=g_smc, minval=5, maxval=30)

// Filters
req_candle  = input.bool (true,  "Require Candle Pattern",     group=g_sig)
req_consec  = input.bool (true,  "Require 2-Bar Momentum",     group=g_sig)
req_mtf     = input.bool (true,  "Require 5min MTF Align",     group=g_sig)
req_ob      = input.bool (false, "Require Order Block Entry",  group=g_sig)

// Session
use_sess    = input.bool (true,  "Enable Session Filter",      group=g_ses)
use_decay   = input.bool (true,  "Block Decay Zone (last N min)", group=g_ses)
decay_mins  = input.int  (10,    "Decay Block Minutes",        group=g_ses, minval=5, maxval=20)
s1_st       = input.int  (915,  "Session 1 Start",             group=g_ses)
s1_en       = input.int  (1000, "Session 1 End",               group=g_ses)
s2_st       = input.int  (1145, "Session 2 Start",             group=g_ses)
s2_en       = input.int  (1300, "Session 2 End",               group=g_ses)
s3_st       = input.int  (1430, "Session 3 Start",             group=g_ses)
s3_en       = input.int  (1515, "Session 3 End",               group=g_ses)

// Exit
rr1         = input.float(1.2,  "T1 R:R", group=g_ex, step=0.1)
rr2         = input.float(2.0,  "T2 R:R", group=g_ex, step=0.1)
rr3         = input.float(3.2,  "T3 R:R", group=g_ex, step=0.1)
sl_mult     = input.float(1.0,  "SL ATR Multiplier", group=g_ex, step=0.1)
atr_len     = input.int  (10,   "ATR Length", group=g_ex)
use_trail   = input.bool (true,  "Trailing SL after T1", group=g_ex)
trail_mult  = input.float(0.8,  "Trail ATR Mult", group=g_ex, step=0.1)
use_be      = input.bool (true,  "Auto Breakeven after T1", group=g_ex)

// Display
show_lines  = input.bool (true,  "Show Target Lines", group=g_vis)
show_warmup = input.bool (true,  "Show Warmup Alerts", group=g_vis)
show_regime = input.bool (true,  "Show Regime Background", group=g_vis)

// ╔══════════════════════════════════════════════════════════════════╗
// ║                   CORE INDICATORS                                ║
// ╚══════════════════════════════════════════════════════════════════╝
ema200  = ta.ema(close, 200)
ema50   = ta.ema(close, 50)
ema21   = ta.ema(close, 21)
ema9    = ta.ema(close, 9)
rsi14   = ta.rsi(close, 14)
rsi7    = ta.rsi(close, 7)
[ml, sl_m, hl] = ta.macd(close, 12, 26, 9)
[ms, ss, hs]   = ta.macd(close,  5, 13, 5)
[_, st_d]      = ta.supertrend(3.0, 10)
[stv2, st_d2]  = ta.supertrend(2.0,  7)
vwap_v  = ta.vwap(close)
atr_v   = ta.atr(atr_len)
atr20   = ta.atr(20)
[pDI, mDI, adx_v]  = ta.dmi(14, 14)
[pDIf, mDIf, adxf] = ta.dmi(7,   7)
[bbm, bbu, bbl]    = ta.bb(close, 20, 2.0)
[kcm, kcu, kcl]    = ta.kc(close, 20, 1.5, true)
stoch_k = ta.sma(ta.stoch(close, high, low, 14), 3)
stoch_d = ta.sma(stoch_k, 3)
obv_v   = ta.obv
obv_ema = ta.ema(obv_v, 21)
vol_avg = ta.sma(volume, 20)
vol_avg5= ta.sma(volume, 5)

// ╔══════════════════════════════════════════════════════════════════╗
// ║         MODULE 1 — ADAPTIVE REGIME CLASSIFIER                    ║
// ║  4 states: STRONG_TREND / WEAK_TREND / RANGING / CHOP           ║
// ╚══════════════════════════════════════════════════════════════════╝
adx_norm   = math.min((adx_v + adxf * 0.5) / 45.0, 1.0)
atr_ratio  = atr_v / atr20   // current vs avg volatility
bb_squeeze = bbu < kcu and bbl > kcl

REGIME_STRONG = 0
REGIME_WEAK   = 1
REGIME_RANGE  = 2
REGIME_CHOP   = 3

regime = adx_norm > 0.65 and atr_ratio > 0.85 ? REGIME_STRONG : adx_norm > 0.40 and atr_ratio > 0.70 ? REGIME_WEAK : adx_norm < 0.30 and not bb_squeeze ? REGIME_RANGE : REGIME_CHOP

trending     = regime == REGIME_STRONG or regime == REGIME_WEAK
strong_trend = regime == REGIME_STRONG
ranging      = regime == REGIME_RANGE
choppy       = regime == REGIME_CHOP

// Adaptive weights per regime
w_trend = regime == REGIME_STRONG ? 0.28 : regime == REGIME_WEAK ? 0.20 : 0.10
w_mom   = regime == REGIME_STRONG ? 0.18 : regime == REGIME_WEAK ? 0.22 : 0.20
w_vol   = 0.16
w_vwap  = regime == REGIME_RANGE  ? 0.28 : 0.16
w_pa    = 0.10
w_smc   = regime == REGIME_STRONG ? 0.08 : 0.12
w_ml    = 0.12

// ╔══════════════════════════════════════════════════════════════════╗
// ║         MODULE 2 — SMART MONEY: BOS / CHoCH / ORDER BLOCKS      ║
// ╚══════════════════════════════════════════════════════════════════╝
ph = ta.pivothigh(high, swing_len, swing_len)
pl = ta.pivotlow (low,  swing_len, swing_len)

var float last_ph  = na
var float last_ph2 = na
var float last_pl  = na
var float last_pl2 = na
var int   ph_bar   = na
var int   pl_bar   = na

if not na(ph)
    last_ph2 := last_ph
    last_ph  := ph
    ph_bar   := bar_index - swing_len

if not na(pl)
    last_pl2 := last_pl
    last_pl  := pl
    pl_bar   := bar_index - swing_len

// BOS: price breaks above last swing high (bullish) or below last swing low (bearish)
bos_bull = not na(last_ph) and close > last_ph  and close[1] <= last_ph
bos_bear = not na(last_pl) and close < last_pl  and close[1] >= last_pl

// CHoCH: higher timeframe structure flip
// Bullish CHoCH: was making lower highs, now breaks above last lower high
choch_bull = not na(last_ph) and not na(last_ph2) and last_ph < last_ph2 and close > last_ph
choch_bear = not na(last_pl) and not na(last_pl2) and last_pl > last_pl2 and close < last_pl

var bool smc_bull_bias = false
var bool smc_bear_bias = false

if bos_bull or choch_bull
    smc_bull_bias := true
    smc_bear_bias := false
if bos_bear or choch_bear
    smc_bear_bias := true
    smc_bull_bias := false

// Order Block detection
// Bullish OB: last bearish candle before a bullish BOS
var float ob_bull_top = na
var float ob_bull_bot = na
var int   ob_bull_bar = na
var float ob_bear_top = na
var float ob_bear_bot = na
var int   ob_bear_bar = na

if bos_bull
    for i = 1 to ob_lookback
        if close[i] < open[i]  // bearish candle
            ob_bull_top := high[i]
            ob_bull_bot := low[i]
            ob_bull_bar := bar_index - i
            break

if bos_bear
    for i = 1 to ob_lookback
        if close[i] > open[i]  // bullish candle
            ob_bear_top := high[i]
            ob_bear_bot := low[i]
            ob_bear_bar := bar_index - i
            break

// Is price inside an order block?
in_bull_ob = not na(ob_bull_top) and close >= ob_bull_bot and close <= ob_bull_top
in_bear_ob = not na(ob_bear_top) and close >= ob_bear_bot and close <= ob_bear_top
ob_ok_bull = not req_ob or in_bull_ob
ob_ok_bear = not req_ob or in_bear_ob

// Draw order block boxes
var box bull_ob_box = na
var box bear_ob_box = na

if show_ob and bos_bull and not na(ob_bull_bar)
    box.delete(bull_ob_box)
    bull_ob_box := box.new(
        ob_bull_bar, ob_bull_top, bar_index, ob_bull_bot,
        xloc=xloc.bar_index, extend=extend.right,
        border_color=color.new(#00E676, 40), border_width=1,
        bgcolor=color.new(#00E676, 88))

if show_ob and bos_bear and not na(ob_bear_bar)
    box.delete(bear_ob_box)
    bear_ob_box := box.new(
        ob_bear_bar, ob_bear_top, bar_index, ob_bear_bot,
        xloc=xloc.bar_index, extend=extend.right,
        border_color=color.new(#FF5252, 40), border_width=1,
        bgcolor=color.new(#FF5252, 88))

// BOS / CHoCH labels
if show_bos and bos_bull
    label.new(bar_index, low, "BOS ▲",
        style=label.style_label_up, color=color.new(#00E676, 20),
        textcolor=color.white, yloc=yloc.belowbar, size=size.tiny)

if show_bos and bos_bear
    label.new(bar_index, high, "BOS ▼",
        style=label.style_label_down, color=color.new(#FF5252, 20),
        textcolor=color.white, yloc=yloc.abovebar, size=size.tiny)

if show_bos and choch_bull
    label.new(bar_index, low, "CHoCH ▲",
        style=label.style_label_up, color=color.new(#FFD600, 20),
        textcolor=color.black, yloc=yloc.belowbar, size=size.tiny)

if show_bos and choch_bear
    label.new(bar_index, high, "CHoCH ▼",
        style=label.style_label_down, color=color.new(#FFD600, 20),
        textcolor=color.black, yloc=yloc.abovebar, size=size.tiny)

// ╔══════════════════════════════════════════════════════════════════╗
// ║         MODULE 3 — VOLUME DELTA ENGINE                           ║
// ║  Reconstructed buy/sell pressure from candle anatomy            ║
// ╚══════════════════════════════════════════════════════════════════╝
rng        = high - low
buy_vol    = rng > 0 ? volume * (close - low)  / rng : volume * 0.5
sell_vol   = rng > 0 ? volume * (high - close) / rng : volume * 0.5
delta      = buy_vol - sell_vol
cum_delta  = ta.ema(delta, 14)
delta_bull = cum_delta > 0 and cum_delta > cum_delta[1]
delta_bear = cum_delta < 0 and cum_delta < cum_delta[1]

// Delta divergence: price up but delta falling = exhaustion
delta_div_bear = close > close[3] and cum_delta < cum_delta[3]
delta_div_bull = close < close[3] and cum_delta > cum_delta[3]

// Volume absorption: high volume, tiny body = big player absorbing
body_pct    = rng > 0 ? math.abs(close - open) / rng : 0.0
absorb_bull = volume > vol_avg * 1.8 and body_pct < 0.25 and close > open
absorb_bear = volume > vol_avg * 1.8 and body_pct < 0.25 and close < open
vol_spike   = volume > vol_avg * 1.3

// ╔══════════════════════════════════════════════════════════════════╗
// ║         MODULE 4 — MULTI-TIMEFRAME CONFLUENCE                    ║
// ╚══════════════════════════════════════════════════════════════════╝
htf5_ema9  = request.security(syminfo.tickerid, "5",  ta.ema(close, 9),  lookahead=barmerge.lookahead_off)
htf5_ema21 = request.security(syminfo.tickerid, "5",  ta.ema(close, 21), lookahead=barmerge.lookahead_off)
htf5_close = request.security(syminfo.tickerid, "5",  close,             lookahead=barmerge.lookahead_off)
htf5_rsi   = request.security(syminfo.tickerid, "5",  ta.rsi(close, 14), lookahead=barmerge.lookahead_off)
[_5, htf5_std] = ta.supertrend(3.0, 10)
htf5_st    = request.security(syminfo.tickerid, "5",  htf5_std,          lookahead=barmerge.lookahead_off)

htf15_ema9  = request.security(syminfo.tickerid, "15", ta.ema(close, 9),  lookahead=barmerge.lookahead_off)
htf15_ema21 = request.security(syminfo.tickerid, "15", ta.ema(close, 21), lookahead=barmerge.lookahead_off)
htf15_close = request.security(syminfo.tickerid, "15", close,             lookahead=barmerge.lookahead_off)
[_15, htf15_std] = ta.supertrend(3.0, 10)
htf15_st    = request.security(syminfo.tickerid, "15", htf15_std,         lookahead=barmerge.lookahead_off)

mtf5_bull  = htf5_ema9 > htf5_ema21 and htf5_close > htf5_ema21 and htf5_st < 0 and htf5_rsi > 50
mtf5_bear  = htf5_ema9 < htf5_ema21 and htf5_close < htf5_ema21 and htf5_st > 0 and htf5_rsi < 50
mtf15_bull = htf15_ema9 > htf15_ema21 and htf15_close > htf15_ema21 and htf15_st < 0
mtf15_bear = htf15_ema9 < htf15_ema21 and htf15_close < htf15_ema21 and htf15_st > 0

mtf_stack_bull = (mtf5_bull ? 1 : 0) + (mtf15_bull ? 1 : 0)
mtf_stack_bear = (mtf5_bear ? 1 : 0) + (mtf15_bear ? 1 : 0)

mtf_ok_bull = not req_mtf or mtf_stack_bull >= 1
mtf_ok_bear = not req_mtf or mtf_stack_bear >= 1

// ╔══════════════════════════════════════════════════════════════════╗
// ║         MODULE 5 — ML-STYLE PREDICTIVE BIAS                     ║
// ║  Counts historical occurrences of current pattern → win rate    ║
// ╚══════════════════════════════════════════════════════════════════╝
// Pattern fingerprint: RSI zone + MACD state + volume state + candle direction
rsi_zone  = rsi14 > 60 ? 2 : rsi14 > 50 ? 1 : rsi14 > 40 ? 0 : rsi14 > 30 ? -1 : -2
macd_state = ml > sl_m ? 1 : -1
vol_state  = volume > vol_avg ? 1 : 0
candle_dir = close > open ? 1 : -1

pat_bull_match = 0
pat_bear_match = 0
pat_total      = 0

for i = 1 to ml_lookback
    h_rsi_zone   = rsi14[i] > 60 ? 2 : rsi14[i] > 50 ? 1 : rsi14[i] > 40 ? 0 : rsi14[i] > 30 ? -1 : -2
    h_macd_state = ml[i] > sl_m[i] ? 1 : -1
    h_vol_state  = volume[i] > vol_avg[i] ? 1 : 0
    h_candle_dir = close[i] > open[i] ? 1 : -1
    match = h_rsi_zone == rsi_zone and h_macd_state == macd_state and h_vol_state == vol_state and h_candle_dir == candle_dir
    if match
        pat_total += 1
        if close[i - 1] > close[i]
            pat_bull_match += 1
        else
            pat_bear_match += 1

ml_bull_rate = pat_total > 5 ? pat_bull_match / pat_total * 100 : 50.0
ml_bear_rate = pat_total > 5 ? pat_bear_match / pat_total * 100 : 50.0

// ╔══════════════════════════════════════════════════════════════════╗
// ║         MODULE 6 — SESSION & PREMIUM DECAY                       ║
// ╚══════════════════════════════════════════════════════════════════╝
t_hhmm  = hour * 100 + minute
t_mins  = hour * 60 + minute
to_min(hhmm) => (hhmm / 100) * 60 + hhmm % 100

raw1 = t_hhmm >= s1_st and t_hhmm < s1_en
raw2 = t_hhmm >= s2_st and t_hhmm < s2_en
raw3 = t_hhmm >= s3_st and t_hhmm < s3_en

ok1  = not use_decay or (to_min(s1_en) - t_mins > decay_mins)
ok2  = not use_decay or (to_min(s2_en) - t_mins > decay_mins)
ok3  = not use_decay or (to_min(s3_en) - t_mins > decay_mins)

in_s1    = raw1 and ok1
in_s2    = raw2 and ok2
in_s3    = raw3 and ok3
in_sess  = not use_sess or (in_s1 or in_s2 or in_s3)
in_decay = use_sess and ((raw1 and not ok1) or (raw2 and not ok2) or (raw3 and not ok3))

sess_quality = in_s1 ? 1.0 : in_s2 ? 0.7 : in_s3 ? 0.85 : 0.0

// ╔══════════════════════════════════════════════════════════════════╗
// ║         MODULE 7 — CANDLE PATTERNS                               ║
// ╚══════════════════════════════════════════════════════════════════╝
c_body  = math.abs(close - open)
c_range = high - low
c_br    = c_range > 0 ? c_body / c_range : 0.0
c_uwk   = high  - math.max(close, open)
c_lwk   = math.min(close, open) - low

bull_eng = close > open and open < close[1] and close > open[1] and close[1] < open[1]
bear_eng = close < open and open > close[1] and close < open[1] and close[1] > open[1]
pin_b    = c_lwk > c_body * 2.0 and c_uwk < c_body * 0.5 and c_br < 0.35
pin_s    = c_uwk > c_body * 2.0 and c_lwk < c_body * 0.5 and c_br < 0.35
mom_b    = c_br > 0.65 and close > open
mom_s    = c_br > 0.65 and close < open
ob_bull_c = high > high[1] and low < low[1] and close > open
ob_bear_c = high > high[1] and low < low[1] and close < open

bull_pat = bull_eng or pin_b or mom_b or ob_bull_c
bear_pat = bear_eng or pin_s or mom_s or ob_bear_c
cpat_ok_b = not req_candle or bull_pat
cpat_ok_s = not req_candle or bear_pat

// ╔══════════════════════════════════════════════════════════════════╗
// ║         MODULE 8 — OPTIONS INTELLIGENCE                          ║
// ╚══════════════════════════════════════════════════════════════════╝
// IV proxy: normalised BB width vs 50-bar average
bb_w      = (bbu - bbl) / bbm
bb_w_avg  = ta.sma(bb_w, 50)
iv_regime = bb_w > bb_w_avg * 1.3 ? "HIGH" : bb_w < bb_w_avg * 0.7 ? "LOW" : "NORMAL"

// Premium decay curve — worst in final 15min of session
decay_penalty = in_decay ? 0.7 : 1.0

// Distance from VWAP = moneyness proxy
vwap_dist_pct = math.abs(close - vwap_v) / vwap_v * 100
otm_penalty   = vwap_dist_pct > 0.8 ? 0.85 : 1.0  // far from VWAP = more OTM

// ╔══════════════════════════════════════════════════════════════════╗
// ║         AI SCORING ENGINE — 7 MODULES                           ║
// ╚══════════════════════════════════════════════════════════════════╝

// ── BULL SCORES ───────────────────────────────────────────────────
// Trend module (max 25)
tr_b  = 0.0
tr_b += close > ema200 ? 6.0 : 0.0
tr_b += close > ema50  ? 4.0 : 0.0
tr_b += ema21  > ema50 ? 4.0 : 0.0
tr_b += st_d   < 0     ? 6.0 : 0.0
tr_b += st_d2  < 0     ? 5.0 : 0.0

// Momentum module (max 25)
rsi_w   = rsi14 * 0.5 + rsi14[1] * 0.3 + rsi14[2] * 0.2
h_rise3 = hl > hl[1] and hl[1] > hl[2]
consec_b = rsi14 > rsi14[1] and ms > ss and ms[1] > ss[1]

mo_b  = 0.0
mo_b += (rsi_w > 52 and rsi_w < 72) ? 5.0 : 0.0
mo_b += (rsi7  > 54 and rsi7  < 80) ? 4.0 : 0.0
mo_b += ml > sl_m                   ? 5.0 : 0.0
mo_b += ms > ss                     ? 4.0 : 0.0
mo_b += h_rise3                     ? 5.0 : 0.0
mo_b += stoch_k > 50 and stoch_k > stoch_d ? 2.0 : 0.0

// Volume module (max 20)
vl_b  = 0.0
vl_b += vol_spike and close > open ? 8.0 : 0.0
vl_b += delta_bull                 ? 6.0 : 0.0
vl_b += obv_v > obv_ema            ? 4.0 : 0.0
vl_b += absorb_bull                ? 2.0 : 0.0

// VWAP module (max 20)
vw_b  = 0.0
vw_b += close > vwap_v  ? 7.0 : 0.0
vw_b += open  > vwap_v  ? 3.0 : 0.0
vw_b += low   > vwap_v  ? 4.0 : 0.0
vw_b += close > bbm     ? 4.0 : 0.0
vw_b += not delta_div_bear ? 2.0 : 0.0

// Price Action module (max 10)
pa_b  = 0.0
pa_b += c_br > 0.60 ? 4.0 : 0.0
pa_b += bull_eng    ? 4.0 : 0.0
pa_b += (high - close) < (close - low) * 0.35 ? 2.0 : 0.0

// SMC module (max 20)
smc_b = 0.0
smc_b += smc_bull_bias ? 6.0 : 0.0
smc_b += bos_bull       ? 5.0 : 0.0
smc_b += choch_bull     ? 4.0 : 0.0
smc_b += in_bull_ob     ? 5.0 : 0.0

// ML module (max 20)
ml_b  = 0.0
ml_b += ml_bull_rate >= 65 ? 10.0 : ml_bull_rate >= 55 ? 6.0 : ml_bull_rate >= 50 ? 3.0 : 0.0
ml_b += mtf_stack_bull == 2 ? 10.0 : mtf_stack_bull == 1 ? 6.0 : 0.0

// ── BEAR SCORES ───────────────────────────────────────────────────
tr_s  = 0.0
tr_s += close < ema200 ? 6.0 : 0.0
tr_s += close < ema50  ? 4.0 : 0.0
tr_s += ema21  < ema50 ? 4.0 : 0.0
tr_s += st_d   > 0     ? 6.0 : 0.0
tr_s += st_d2  > 0     ? 5.0 : 0.0

h_fall3  = hl < hl[1] and hl[1] < hl[2]
consec_s = rsi14 < rsi14[1] and ms < ss and ms[1] < ss[1]

mo_s  = 0.0
mo_s += (rsi_w < 48 and rsi_w > 28) ? 5.0 : 0.0
mo_s += (rsi7  < 46 and rsi7  > 20) ? 4.0 : 0.0
mo_s += ml < sl_m                   ? 5.0 : 0.0
mo_s += ms < ss                     ? 4.0 : 0.0
mo_s += h_fall3                     ? 5.0 : 0.0
mo_s += stoch_k < 50 and stoch_k < stoch_d ? 2.0 : 0.0

vl_s  = 0.0
vl_s += vol_spike and close < open ? 8.0 : 0.0
vl_s += delta_bear                 ? 6.0 : 0.0
vl_s += obv_v < obv_ema            ? 4.0 : 0.0
vl_s += absorb_bear                ? 2.0 : 0.0

vw_s  = 0.0
vw_s += close < vwap_v  ? 7.0 : 0.0
vw_s += open  < vwap_v  ? 3.0 : 0.0
vw_s += high  < vwap_v  ? 4.0 : 0.0
vw_s += close < bbm     ? 4.0 : 0.0
vw_s += not delta_div_bull ? 2.0 : 0.0

pa_s  = 0.0
pa_s += c_br > 0.60 ? 4.0 : 0.0
pa_s += bear_eng    ? 4.0 : 0.0
pa_s += (close - low) < (high - close) * 0.35 ? 2.0 : 0.0

smc_s = 0.0
smc_s += smc_bear_bias ? 6.0 : 0.0
smc_s += bos_bear       ? 5.0 : 0.0
smc_s += choch_bear     ? 4.0 : 0.0
smc_s += in_bear_ob     ? 5.0 : 0.0

ml_s  = 0.0
ml_s += ml_bear_rate >= 65 ? 10.0 : ml_bear_rate >= 55 ? 6.0 : ml_bear_rate >= 50 ? 3.0 : 0.0
ml_s += mtf_stack_bear == 2 ? 10.0 : mtf_stack_bear == 1 ? 6.0 : 0.0

// ── FINAL PROBABILITY ─────────────────────────────────────────────
raw_bull = ((tr_b/25.0)*w_trend + (mo_b/25.0)*w_mom + (vl_b/20.0)*w_vol +
            (vw_b/20.0)*w_vwap + (pa_b/10.0)*w_pa  + (smc_b/20.0)*w_smc +
            (ml_b/20.0)*w_ml) * 100

raw_bear = ((tr_s/25.0)*w_trend + (mo_s/25.0)*w_mom + (vl_s/20.0)*w_vol +
            (vw_s/20.0)*w_vwap + (pa_s/10.0)*w_pa  + (smc_s/20.0)*w_smc +
            (ml_s/20.0)*w_ml) * 100

// Regime boost: strong trend amplifies, chop suppresses
reg_mult = regime == REGIME_STRONG ? 1.15 : regime == REGIME_CHOP ? 0.60 : 1.0

// Options decay & OTM penalty
opt_mult = decay_penalty * otm_penalty

prob_bull = math.min(raw_bull * reg_mult * opt_mult, 99)
prob_bear = math.min(raw_bear * reg_mult * opt_mult, 99)

// Mutual exclusion — suppress weaker side
prob_bull := prob_bull > prob_bear + 8 ? prob_bull : prob_bull > prob_bear ? prob_bull * 0.80 : prob_bull * 0.65
prob_bear := prob_bear > prob_bull + 8 ? prob_bear : prob_bear > prob_bull ? prob_bear * 0.80 : prob_bear * 0.65

// Suppress signals in chop
prob_bull := choppy ? prob_bull * 0.50 : prob_bull
prob_bear := choppy ? prob_bear * 0.50 : prob_bear

// ╔══════════════════════════════════════════════════════════════════╗
// ║         SIGNAL HEAT SCORE (1–10)                                 ║
// ╚══════════════════════════════════════════════════════════════════╝
heat_b  = 0.0
heat_b += prob_bull >= 85 ? 3.0 : prob_bull >= 75 ? 2.5 : prob_bull >= 68 ? 2.0 : 1.0
heat_b += mtf_stack_bull == 2 ? 2.5 : mtf_stack_bull == 1 ? 1.5 : 0.0
heat_b += smc_bull_bias   ? 1.5 : 0.0
heat_b += delta_bull       ? 1.0 : 0.0
heat_b += bull_pat         ? 0.5 : 0.0
heat_b += in_bull_ob       ? 1.0 : 0.0
heat_b += sess_quality * 0.5

heat_s  = 0.0
heat_s += prob_bear >= 85 ? 3.0 : prob_bear >= 75 ? 2.5 : prob_bear >= 68 ? 2.0 : 1.0
heat_s += mtf_stack_bear == 2 ? 2.5 : mtf_stack_bear == 1 ? 1.5 : 0.0
heat_s += smc_bear_bias   ? 1.5 : 0.0
heat_s += delta_bear       ? 1.0 : 0.0
heat_s += bear_pat         ? 0.5 : 0.0
heat_s += in_bear_ob       ? 1.0 : 0.0
heat_s += sess_quality * 0.5

bull_heat = math.min(math.round(heat_b), 10)
bear_heat = math.min(math.round(heat_s), 10)

heat_blk(s) =>
    s >= 9 ? "██████████" : s >= 8 ? "█████████░" : s >= 7 ? "████████░░" : s >= 6 ? "███████░░░" : s >= 5 ? "██████░░░░" : s >= 4 ? "█████░░░░░" : s >= 3 ? "████░░░░░░" : s >= 2 ? "███░░░░░░░" : s >= 1 ? "██░░░░░░░░" : "█░░░░░░░░░"

heat_clr(s) =>
    s >= 9 ? color.new(#00E676, 0) : s >= 7 ? color.new(#76FF03, 0) : s >= 5 ? color.new(#FFD600, 0) : s >= 3 ? color.new(#FF6D00, 0) : color.new(#FF1744, 0)

// ╔══════════════════════════════════════════════════════════════════╗
// ║         FINAL ENTRY SIGNALS                                      ║
// ╚══════════════════════════════════════════════════════════════════╝
bull_bias = ema9 > ema21 and close > ema21
bear_bias = ema9 < ema21 and close < ema21

consec_ok_b = not req_consec or consec_b
consec_ok_s = not req_consec or consec_s

call_sig = prob_bull >= min_prob
           and bull_bias and st_d < 0
           and cpat_ok_b and consec_ok_b
           and mtf_ok_bull and ob_ok_bull
           and not delta_div_bear
           and in_sess and not choppy

put_sig  = prob_bear >= min_prob
           and bear_bias and st_d > 0
           and cpat_ok_s and consec_ok_s
           and mtf_ok_bear and ob_ok_bear
           and not delta_div_bull
           and in_sess and not choppy

warmup_b = show_warmup and prob_bull >= warmup_thr and prob_bull < min_prob and bull_bias and st_d < 0 and in_sess
warmup_s = show_warmup and prob_bear >= warmup_thr and prob_bear < min_prob and bear_bias and st_d > 0 and in_sess

// ╔══════════════════════════════════════════════════════════════════╗
// ║   INTELLIGENT SL ENGINE — 5-layer structure-aware placement      ║
// ║                                                                  ║
// ║  For 1-min Nifty scalping, a flat ATR-multiple SL is too naive. ║
// ║  Price noise alone eats through fixed-distance stops. Instead:  ║
// ║                                                                  ║
// ║  Layer 1 — Recent swing low/high (last 5 bars)                  ║
// ║    The lowest low (for calls) or highest high (for puts) of the  ║
// ║    past 5 bars is the real structural invalidation point.        ║
// ║    If price goes below that low, the setup is broken — not when  ║
// ║    it hits an arbitrary ATR distance.                            ║
// ║                                                                  ║
// ║  Layer 2 — Candle wick anatomy                                   ║
// ║    If the entry candle has a lower wick > 30% of its range,      ║
// ║    that wick IS the proven rejection. SL goes just below it      ║
// ║    (1 tick buffer). This is tighter AND more logical than ATR.   ║
// ║                                                                  ║
// ║  Layer 3 — Order block floor/ceiling                             ║
// ║    If inside a bull OB, SL goes below the OB bottom.            ║
// ║    If inside a bear OB, SL goes above the OB top.               ║
// ║    OB zones ARE institutional rejection — if price exits them    ║
// ║    the thesis is invalidated.                                    ║
// ║                                                                  ║
// ║  Layer 4 — Volatility envelope (floor + ceiling)                 ║
// ║    SL is clamped between 0.4× ATR (minimum — avoids noise stop) ║
// ║    and 1.6× ATR (maximum — avoids unacceptable R:R).            ║
// ║    In a BB squeeze, tighten to 0.35× ATR floor (low volatility  ║
// ║    = price moves less = tighter SL is safe).                     ║
// ║                                                                  ║
// ║  Layer 5 — VWAP buffer                                           ║
// ║    For calls, if VWAP is between close and the raw SL,           ║
// ║    place SL just below VWAP (VWAP is the strongest intraday      ║
// ║    magnet — a close below it on 1-min invalidates bull thesis).  ║
// ║    Same logic for puts above VWAP.                               ║
// ║                                                                  ║
// ║  Final SL = best (tightest logical) of all applicable layers,   ║
// ║    then clamped to the volatility envelope.                      ║
// ║  Targets scale from this final SL distance, not raw ATR.        ║
// ╚══════════════════════════════════════════════════════════════════╝

// ── Layer 1: recent structure low/high (5-bar lookback) ───────────
struct_low  = ta.lowest(low,  5)   // last 5 bars lowest low
struct_high = ta.highest(high, 5)  // last 5 bars highest high

// ── Layer 2: candle wick anatomy ──────────────────────────────────
entry_low_wick  = math.min(open, close) - low   // lower wick size
entry_high_wick = high - math.max(open, close)  // upper wick size
candle_rng      = high - low
wick_ratio_low  = candle_rng > 0 ? entry_low_wick  / candle_rng : 0.0
wick_ratio_high = candle_rng > 0 ? entry_high_wick / candle_rng : 0.0
tick            = syminfo.mintick

// Wick-based SL candidates
wick_sl_bull = wick_ratio_low  > 0.30 ? low  - tick * 2 : na  // just below wick
wick_sl_bear = wick_ratio_high > 0.30 ? high + tick * 2 : na  // just above wick

// ── Layer 3: order block floor/ceiling ────────────────────────────
ob_sl_bull = in_bull_ob and not na(ob_bull_bot) ? ob_bull_bot - tick * 2 : na
ob_sl_bear = in_bear_ob and not na(ob_bear_top) ? ob_bear_top + tick * 2 : na

// ── Layer 4: volatility envelope ──────────────────────────────────
atr_floor_mult = bb_squeeze ? 0.35 : 0.40   // tighter floor in squeeze
atr_ceil_mult  = strong_trend ? 1.80 : 1.60  // slightly wider in strong trend
atr_floor_bull = close - atr_v * atr_floor_mult
atr_floor_bear = close + atr_v * atr_floor_mult
atr_ceil_bull  = close - atr_v * atr_ceil_mult
atr_ceil_bear  = close + atr_v * atr_ceil_mult

// ── Layer 5: VWAP buffer ──────────────────────────────────────────
// For calls: if VWAP is below close but above raw struct_low → use VWAP - 1 tick
// For puts:  if VWAP is above close but below raw struct_high → use VWAP + 1 tick
vwap_sl_bull = (vwap_v < close and vwap_v > struct_low)  ? vwap_v - tick * 3 : na
vwap_sl_bear = (vwap_v > close and vwap_v < struct_high) ? vwap_v + tick * 3 : na

// ── Assemble final SL: pick the HIGHEST (tightest) valid bull SL ──
// Priority: OB > Wick > VWAP > Structure > ATR floor
// Then clamp: must not be BELOW the ATR ceiling (too wide = bad R:R)
//             must not be ABOVE the ATR floor (too close = noise stop)

raw_sl_bull = struct_low - tick * 2   // structure-based baseline
raw_sl_bear = struct_high + tick * 2

// Apply wick if valid and tighter than structure
raw_sl_bull := not na(wick_sl_bull) and wick_sl_bull > raw_sl_bull ? wick_sl_bull : raw_sl_bull
raw_sl_bear := not na(wick_sl_bear) and wick_sl_bear < raw_sl_bear ? wick_sl_bear : raw_sl_bear

// Apply VWAP if valid and tighter than current best
raw_sl_bull := not na(vwap_sl_bull) and vwap_sl_bull > raw_sl_bull ? vwap_sl_bull : raw_sl_bull
raw_sl_bear := not na(vwap_sl_bear) and vwap_sl_bear < raw_sl_bear ? vwap_sl_bear : raw_sl_bear

// Apply OB if valid and tighter
raw_sl_bull := not na(ob_sl_bull) and ob_sl_bull > raw_sl_bull ? ob_sl_bull : raw_sl_bull
raw_sl_bear := not na(ob_sl_bear) and ob_sl_bear < raw_sl_bear ? ob_sl_bear : raw_sl_bear

// Clamp to volatility envelope: floor (not too tight) and ceiling (not too wide)
call_sl = math.max(math.min(raw_sl_bull, atr_floor_bull), atr_ceil_bull)
put_sl  = math.min(math.max(raw_sl_bear, atr_floor_bear), atr_ceil_bear)

// ── Actual risk distance from these intelligent SLs ───────────────
sl_d_call = math.max(close - call_sl, atr_v * 0.4)  // true risk in points
sl_d_put  = math.max(put_sl  - close, atr_v * 0.4)

// ── Targets scale from actual SL distance, not flat ATR ───────────
// This keeps R:R ratios honest regardless of how tight/wide SL ended up
call_t1  = close + sl_d_call * rr1
call_t2  = close + sl_d_call * rr2
call_t3  = close + sl_d_call * rr3

put_t1   = close - sl_d_put  * rr1
put_t2   = close - sl_d_put  * rr2
put_t3   = close - sl_d_put  * rr3

// Trailing SL — based on Supertrend value + ATR buffer
trl_b_sl = stv2 - atr_v * trail_mult
trl_s_sl = stv2 + atr_v * trail_mult

// sl_d for legacy uses (BE detection uses call/put specific now)
sl_d = atr_v * sl_mult

// ╔══════════════════════════════════════════════════════════════════╗
// ║         COLOUR PALETTE                                           ║
// ╚══════════════════════════════════════════════════════════════════╝
C_W    = color.white
C_DARK = color.new(#060A12, 0)

LC_SL   = color.new(#FF2D2D, 15)
LC_T1_C = color.new(#00E676, 15)
LC_T2_C = color.new(#00C853, 15)
LC_T3_C = color.new(#00BCD4, 10)
LC_BE   = color.new(#00E5FF, 15)
LC_TSL  = color.new(#FFB300, 10)
LC_T1_P = color.new(#FF9100, 15)
LC_T2_P = color.new(#C62828, 15)
LC_T3_P = color.new(#7B1FA2, 10)

// ╔══════════════════════════════════════════════════════════════════╗
// ║         PERSISTENT HANDLES                                       ║
// ╚══════════════════════════════════════════════════════════════════╝
var line  ln_sl  = na
var line  ln_t1  = na
var line  ln_t2  = na
var line  ln_t3  = na
var line  ln_be  = na
var line  ln_tsl = na

var label lb_sl  = na
var label lb_t1  = na
var label lb_t2  = na
var label lb_t3  = na
var label lb_be  = na
var label lb_tsl = na

var float sv_sl   = na
var float sv_t1   = na
var float sv_t2   = na
var float sv_t3   = na
var bool  sv_call = false

var bool  act_call = false
var bool  act_put  = false
var float entry_px = na
var bool  t1_hit   = false
var bool  be_on    = false

// ╔══════════════════════════════════════════════════════════════════╗
// ║   TRADE LIFECYCLE ENGINE                                         ║
// ║                                                                  ║
// ║  States:  IDLE → ACTIVE → (T1 HIT → BE/TRAIL) → CLOSED         ║
// ║                                                                  ║
// ║  SL breach detection runs EVERY bar:                            ║
// ║   • Before T1: breach = close crosses sv_sl                     ║
// ║   • After T1 with trail: breach = close crosses TSL level       ║
// ║   • After T1 with BE: breach = close crosses entry_px           ║
// ║                                                                  ║
// ║  On breach:                                                      ║
// ║   1. STOPPED label fires at breach bar                          ║
// ║   2. All lines + labels wiped                                   ║
// ║   3. Trade state reset to IDLE                                  ║
// ║   4. call_sig / put_sig can fire immediately on same bar        ║
// ║      → new signal = new lines + targets drawn right away        ║
// ╚══════════════════════════════════════════════════════════════════╝

// ── T1 detection ──────────────────────────────────────────────────
if act_call and not na(sv_t1) and not t1_hit
    if high >= sv_t1
        t1_hit := true
        be_on  := use_be

if act_put and not na(sv_t1) and not t1_hit
    if low <= sv_t1
        t1_hit := true
        be_on  := use_be

// ── SL / TSL breach detection — runs every bar BEFORE new signal ──
var bool sl_just_hit = false
sl_just_hit := false

if act_call and not na(sv_sl)
    // Before T1: hard SL breach
    plain_sl_breach = not t1_hit and low <= sv_sl
    // After T1 + trailing: TSL breach
    trail_sl_breach = t1_hit and use_trail and low <= trl_b_sl
    // After T1 + BE: BE breach (price returned below entry)
    be_breach       = t1_hit and be_on and low < entry_px

    if plain_sl_breach or trail_sl_breach or be_breach
        sl_just_hit := true
        // STOPPED label
        stop_txt = plain_sl_breach ? "  ✕ STOPPED  SL hit  " : trail_sl_breach ? "  ✕ STOPPED  TSL hit  " : "  ✕ STOPPED  BE breach  "
        label.new(bar_index, low, stop_txt,
            style=label.style_label_up, color=color.new(#FF0000, 0),
            textcolor=color.white, yloc=yloc.belowbar, size=size.small)
        // Wipe all lines and labels
        line.delete(ln_sl)
        line.delete(ln_t1)
        line.delete(ln_t2)
        line.delete(ln_t3)
        line.delete(ln_be)
        line.delete(ln_tsl)
        label.delete(lb_sl)
        label.delete(lb_t1)
        label.delete(lb_t2)
        label.delete(lb_t3)
        label.delete(lb_be)
        label.delete(lb_tsl)
        // Reset trade state → IDLE
        act_call := false
        act_put  := false
        entry_px := na
        t1_hit   := false
        be_on    := false
        sv_sl    := na
        sv_t1    := na
        sv_t2    := na
        sv_t3    := na

if act_put and not na(sv_sl)
    plain_sl_breach = not t1_hit and high >= sv_sl
    trail_sl_breach = t1_hit and use_trail and high >= trl_s_sl
    be_breach       = t1_hit and be_on and high > entry_px

    if plain_sl_breach or trail_sl_breach or be_breach
        sl_just_hit := true
        stop_txt = plain_sl_breach ? "  ✕ STOPPED  SL hit  " : trail_sl_breach ? "  ✕ STOPPED  TSL hit  " : "  ✕ STOPPED  BE breach  "
        label.new(bar_index, high, stop_txt,
            style=label.style_label_down, color=color.new(#FF0000, 0),
            textcolor=color.white, yloc=yloc.abovebar, size=size.small)
        line.delete(ln_sl)
        line.delete(ln_t1)
        line.delete(ln_t2)
        line.delete(ln_t3)
        line.delete(ln_be)
        line.delete(ln_tsl)
        label.delete(lb_sl)
        label.delete(lb_t1)
        label.delete(lb_t2)
        label.delete(lb_t3)
        label.delete(lb_be)
        label.delete(lb_tsl)
        act_call := false
        act_put  := false
        entry_px := na
        t1_hit   := false
        be_on    := false
        sv_sl    := na
        sv_t1    := na
        sv_t2    := na
        sv_t3    := na

// ── T3 / trade complete detection ─────────────────────────────────
var bool trade_done = false
trade_done := false

if act_call and not na(sv_t3) and high >= sv_t3
    trade_done := true
    label.new(bar_index, high, "  ✓ T3 HIT  TRADE COMPLETE  ",
        style=label.style_label_down, color=color.new(#00BCD4, 0),
        textcolor=color.black, yloc=yloc.abovebar, size=size.small)
    line.delete(ln_sl)  
    line.delete(ln_t1)  
    line.delete(ln_t2)  
    line.delete(ln_t3)
    line.delete(ln_be)  
    line.delete(ln_tsl)
    label.delete(lb_sl) 
    label.delete(lb_t1) 
    label.delete(lb_t2) 
    label.delete(lb_t3)
    label.delete(lb_be) 
    label.delete(lb_tsl)
    act_call := false
    act_put  := false
    entry_px := na
    t1_hit   := false
    be_on    := false
    sv_sl    := na
    sv_t1    := na
    sv_t2    := na
    sv_t3    := na

if act_put and not na(sv_t3) and low <= sv_t3
    trade_done := true
    label.new(bar_index, low, "  ✓ T3 HIT  TRADE COMPLETE  ",
        style=label.style_label_up, color=color.new(#00BCD4, 0),
        textcolor=color.black, yloc=yloc.belowbar, size=size.small)
    line.delete(ln_sl)  
    line.delete(ln_t1)  
    line.delete(ln_t2)  
    line.delete(ln_t3)
    line.delete(ln_be)  
    line.delete(ln_tsl)
    label.delete(lb_sl) 
    label.delete(lb_t1) 
    label.delete(lb_t2) 
    label.delete(lb_t3)
    label.delete(lb_be) 
    label.delete(lb_tsl)
    act_call := false
    act_put  := false
    entry_px := na
    t1_hit   := false
    be_on    := false
    sv_sl    := na
    sv_t1    := na
    sv_t2    := na
    sv_t3    := na

// ── NEW SIGNAL: only fires when IDLE (no active trade) ────────────
// After SL breach: state resets to IDLE on same bar → new signal
// can fire immediately if AI conditions are met
if show_lines and (call_sig or put_sig) and not act_call and not act_put
    line.delete(ln_sl)
    line.delete(ln_t1)
    line.delete(ln_t2)
    line.delete(ln_t3)
    line.delete(ln_be)
    line.delete(ln_tsl)

    act_call := call_sig
    act_put  := put_sig
    entry_px := close
    t1_hit   := false
    be_on    := false
    sv_call  := call_sig
    bx = bar_index

    if call_sig
        sv_sl := call_sl
        sv_t1 := call_t1
        sv_t2 := call_t2
        sv_t3 := call_t3
        ln_sl := line.new(bx, call_sl, bx, call_sl, xloc=xloc.bar_index, extend=extend.right, color=LC_SL,   width=2, style=line.style_dotted)
        ln_t1 := line.new(bx, call_t1, bx, call_t1, xloc=xloc.bar_index, extend=extend.right, color=LC_T1_C, width=1, style=line.style_dashed)
        ln_t2 := line.new(bx, call_t2, bx, call_t2, xloc=xloc.bar_index, extend=extend.right, color=LC_T2_C, width=2, style=line.style_dashed)
        ln_t3 := line.new(bx, call_t3, bx, call_t3, xloc=xloc.bar_index, extend=extend.right, color=LC_T3_C, width=3, style=line.style_solid)

    if put_sig
        sv_sl := put_sl
        sv_t1 := put_t1
        sv_t2 := put_t2
        sv_t3 := put_t3
        ln_sl := line.new(bx, put_sl,  bx, put_sl,  xloc=xloc.bar_index, extend=extend.right, color=LC_SL,   width=2, style=line.style_dotted)
        ln_t1 := line.new(bx, put_t1,  bx, put_t1,  xloc=xloc.bar_index, extend=extend.right, color=LC_T1_P, width=1, style=line.style_dashed)
        ln_t2 := line.new(bx, put_t2,  bx, put_t2,  xloc=xloc.bar_index, extend=extend.right, color=LC_T2_P, width=2, style=line.style_dashed)
        ln_t3 := line.new(bx, put_t3,  bx, put_t3,  xloc=xloc.bar_index, extend=extend.right, color=LC_T3_P, width=3, style=line.style_solid)

// ── Breakeven line ─────────────────────────────────────────────────
if show_lines and be_on and (act_call or act_put) and not na(entry_px)
    line.delete(ln_be)
    line.delete(ln_sl)
    ln_be := line.new(bar_index, entry_px, bar_index, entry_px,
                      xloc=xloc.bar_index, extend=extend.right,
                      color=color.new(#00E5FF, 15), width=2, style=line.style_solid)

// ── Trailing SL line ───────────────────────────────────────────────
if show_lines and use_trail and t1_hit and (act_call or act_put)
    line.delete(ln_tsl)
    trl = act_call ? trl_b_sl : trl_s_sl
    ln_tsl := line.new(bar_index, trl, bar_index, trl,
                       xloc=xloc.bar_index, extend=extend.right,
                       color=LC_TSL, width=2, style=line.style_solid)

// ╔══════════════════════════════════════════════════════════════════╗
// ║   DYNAMIC PILL LABELS — right edge, live intelligence            ║
// ║                                                                  ║
// ║  Each label is rebuilt every bar with real-time context:         ║
// ║  • SL   → distance in pts + SAFE / AT RISK / BREACHED status    ║
// ║  • T1   → ● HIT / ○ NEXT / ◌ PENDING + pts-to-target           ║
// ║  • T2   → same + colour shifts as price approaches              ║
// ║  • T3   → same                                                   ║
// ║  • TSL  → live trail value + pts from current price             ║
// ║  • BE   → PROTECTED when price safely beyond entry              ║
// ║                                                                  ║
// ║  Colour intelligence:                                            ║
// ║  CALL targets: grey=far  →  gold=50% there  →  bright=close     ║
// ║  SL:           green=safe → amber=warning   →  red=danger       ║
// ╚══════════════════════════════════════════════════════════════════╝
if barstate.islast and show_lines and not na(sv_sl)
    rx = bar_index

    label.delete(lb_sl)
    label.delete(lb_t1)
    label.delete(lb_t2)
    label.delete(lb_t3)
    label.delete(lb_be)
    label.delete(lb_tsl)

    // ── Live distance helpers ────────────────────────────────────────
    // How far is current price from each level, in points
    d_sl  = math.abs(close - sv_sl)
    d_t1  = math.abs(close - sv_t1)
    d_t2  = math.abs(close - sv_t2)
    d_t3  = math.abs(close - sv_t3)

    // Trade range = entry to T3 (full range of the trade)
    trade_rng = math.abs(sv_t3 - entry_px)

    // Progress ratio toward each target (0.0 = at entry, 1.0 = at target)
    prog_t1 = trade_rng > 0 ? math.min(math.abs(close - entry_px) / math.abs(sv_t1 - entry_px), 1.0) : 0.0
    prog_t2 = trade_rng > 0 ? math.min(math.abs(close - entry_px) / math.abs(sv_t2 - entry_px), 1.0) : 0.0
    prog_t3 = trade_rng > 0 ? math.min(math.abs(close - entry_px) / math.abs(sv_t3 - entry_px), 1.0) : 0.0

    // SL proximity ratio (0 = safe far away, 1 = right at SL)
    sl_rng    = math.abs(entry_px - sv_sl)
    sl_prox   = sl_rng > 0 ? math.min(d_sl / sl_rng, 1.0) : 1.0

    // Has price actually breached each level in the correct direction?
    t1_done = sv_call ? close >= sv_t1 : close <= sv_t1
    t2_done = sv_call ? close >= sv_t2 : close <= sv_t2
    t3_done = sv_call ? close >= sv_t3 : close <= sv_t3
    sl_hit  = sv_call ? close <= sv_sl : close >= sv_sl

    // ── SL label — colour & text changes based on proximity ─────────
    // safe (>80% range away) → warning (40-80%) → danger (<40%) → breached
    sl_status = sl_hit ? "  ✕ BREACHED " : sl_prox > 0.80 ? "  ✓ SAFE     " : sl_prox > 0.50 ? "  ⚠ WARNING  " : "  ✕ DANGER   "

    sl_clr = sl_hit ? color.new(#FF0000, 0) : sl_prox > 0.80 ? color.new(#1B5E20, 0) : sl_prox > 0.50 ? color.new(#E65100, 0) : color.new(#FF2D2D, 0)

    sl_arrow = sv_call ? "▼ SL " : "▲ SL "

    if not be_on
        lb_sl := make_pill(rx, sv_sl,
            "  " + sl_arrow + f_price(sv_sl) + "  " + f_pts(d_sl) + sl_status,
            sl_clr, C_W)

    // ── Target label builder — shared logic for T1/T2/T3 ────────────
    // Status tag: ● HIT | ○ NEXT | ◌ PENDING
    // Colour: dims when hit, brightens as price approaches

    // ── T1 ───────────────────────────────────────────────────────────
    t1_status = t1_done ? "  ● HIT     " : prog_t1 > 0.75 ? "  ○ NEXT ▶▶ " : prog_t1 > 0.40 ? "  ○ NEXT ▶  " : "  ◌ PENDING "

    t1_pct_str = t1_done ? "" : " (" + str.tostring(math.round(prog_t1 * 100, 0)) + "%)"

    if sv_call
        t1_bg = t1_done ? color.new(#546E7A, 0) : prog_t1 > 0.75 ? color.new(#00E676, 0) : prog_t1 > 0.40 ? color.new(#FFD600, 0) : color.new(#37474F, 0)
        t1_tc = t1_done or prog_t1 <= 0.40 ? C_W : C_DARK
        lb_t1 := make_pill(rx, sv_t1,
            "  ▲ T1  " + f_price(sv_t1) + "  " + f_pts(d_t1) + t1_pct_str + t1_status,
            t1_bg, t1_tc)
    else
        t1_bg = t1_done ? color.new(#546E7A, 0) : prog_t1 > 0.75 ? color.new(#FF9100, 0) : prog_t1 > 0.40 ? color.new(#FFD600, 0) : color.new(#37474F, 0)
        t1_tc = t1_done or prog_t1 <= 0.40 ? C_W : C_DARK
        lb_t1 := make_pill(rx, sv_t1,
            "  ▼ T1  " + f_price(sv_t1) + "  " + f_pts(d_t1) + t1_pct_str + t1_status,
            t1_bg, t1_tc)

    // ── T2 ───────────────────────────────────────────────────────────
    t2_status = t2_done ? "  ● HIT     " : t1_done and prog_t2 > 0.75 ? "  ○ NEXT ▶▶ " : t1_done and prog_t2 > 0.40 ? "  ○ NEXT ▶  " : "  ◌ PENDING "

    t2_pct_str = t2_done ? "" : " (" + str.tostring(math.round(prog_t2 * 100, 0)) + "%)"

    if sv_call
        t2_bg = t2_done ? color.new(#546E7A, 0) : t1_done and prog_t2 > 0.75 ? color.new(#00C853, 0) : t1_done and prog_t2 > 0.40 ? color.new(#FFD600, 0) : t1_done ? color.new(#1B5E20, 0) : color.new(#263238, 0)
        t2_tc = t2_done or (not t1_done) ? C_W : C_DARK
        lb_t2 := make_pill(rx, sv_t2,
            "  ▲ T2  " + f_price(sv_t2) + "  " + f_pts(d_t2) + t2_pct_str + t2_status,
            t2_bg, t2_tc)
    else
        t2_bg = t2_done ? color.new(#546E7A, 0) : t1_done and prog_t2 > 0.75 ? color.new(#C62828, 0) : t1_done and prog_t2 > 0.40 ? color.new(#FFD600, 0) : t1_done ? color.new(#7B1A1A, 0) : color.new(#263238, 0)
        t2_tc = C_W
        lb_t2 := make_pill(rx, sv_t2,
            "  ▼ T2  " + f_price(sv_t2) + "  " + f_pts(d_t2) + t2_pct_str + t2_status,
            t2_bg, t2_tc)

    // ── T3 ───────────────────────────────────────────────────────────
    t3_status = t3_done ? "  ● HIT     " : t2_done and prog_t3 > 0.75 ? "  ○ NEXT ▶▶ " : t2_done and prog_t3 > 0.40 ? "  ○ NEXT ▶  " : "  ◌ PENDING "

    t3_pct_str = t3_done ? "" : " (" + str.tostring(math.round(prog_t3 * 100, 0)) + "%)"

    if sv_call
        t3_bg = t3_done ? color.new(#546E7A, 0) : t2_done and prog_t3 > 0.75 ? color.new(#00BCD4, 0) : t2_done and prog_t3 > 0.40 ? color.new(#FFD600, 0) : t2_done ? color.new(#006064, 0) : color.new(#1C313A, 0)
        t3_tc = t3_done or (not t2_done) ? C_W : C_DARK
        lb_t3 := make_pill(rx, sv_t3,
            "  ▲ T3  " + f_price(sv_t3) + "  " + f_pts(d_t3) + t3_pct_str + t3_status,
            t3_bg, t3_tc)
    else
        t3_bg = t3_done ? color.new(#546E7A, 0) : t2_done and prog_t3 > 0.75 ? color.new(#7B1FA2, 0) : t2_done and prog_t3 > 0.40 ? color.new(#FFD600, 0) : t2_done ? color.new(#4A148C, 0) : color.new(#1C313A, 0)
        t3_tc = C_W
        lb_t3 := make_pill(rx, sv_t3,
            "  ▼ T3  " + f_price(sv_t3) + "  " + f_pts(d_t3) + t3_pct_str + t3_status,
            t3_bg, t3_tc)

    // ── BE label — shows PROTECTED once safe ─────────────────────────
    if be_on and not na(entry_px)
        be_safe    = sv_call ? close > entry_px + atr_v * 0.3 : close < entry_px - atr_v * 0.3
        be_status  = be_safe ? "  ✓ PROTECTED" : "  ≈ AT ENTRY "
        be_clr     = be_safe ? color.new(#00E5FF, 0) : color.new(#0288D1, 0)
        lb_be := make_pill(rx, entry_px,
            "  ═ BE  " + f_price(entry_px) + be_status,
            be_clr, C_DARK)

    // ── TSL label — live trailing distance ───────────────────────────
    if use_trail and t1_hit and (act_call or act_put)
        trl_lbl    = act_call ? trl_b_sl : trl_s_sl
        tsl_dist   = math.abs(close - trl_lbl)
        tsl_arrow  = act_call ? "  ↑ TSL " : "  ↓ TSL "
        tsl_status = act_call ? (close > trl_lbl ? "  ↑ trailing" : "  ✕ CROSSED") : (close < trl_lbl ? "  ↓ trailing" : "  ✕ CROSSED")
        tsl_clr    = (act_call and close <= trl_lbl) or (act_put and close >= trl_lbl) ? color.new(#FF0000, 0) : color.new(#FFB300, 0)
        lb_tsl := make_pill(rx, trl_lbl,
            tsl_arrow + f_price(trl_lbl) + "  " + f_pts(tsl_dist) + tsl_status,
            tsl_clr, C_DARK)

// ╔══════════════════════════════════════════════════════════════════╗
// ║   ENTRY / WARMUP / STRUCTURE LABELS                              ║
// ╚══════════════════════════════════════════════════════════════════╝
if call_sig
    label.new(bar_index, low,
        "  ▲ CALL  " + str.tostring(math.round(prob_bull,1)) + "%  " + str.tostring(int(bull_heat)) + "/10  ",
        style=label.style_label_up, color=color.new(#00C853, 0),
        textcolor=C_W, yloc=yloc.belowbar, size=size.normal)

if put_sig
    label.new(bar_index, high,
        "  ▼ PUT   " + str.tostring(math.round(prob_bear,1)) + "%  " + str.tostring(int(bear_heat)) + "/10  ",
        style=label.style_label_down, color=color.new(#FF2D2D, 0),
        textcolor=C_W, yloc=yloc.abovebar, size=size.normal)

if warmup_b and not call_sig
    label.new(bar_index, low, " ⚡ " + str.tostring(math.round(prob_bull,1)) + "% ",
        style=label.style_label_up, color=color.new(#FF8F00, 20),
        textcolor=C_W, yloc=yloc.belowbar, size=size.small)

if warmup_s and not put_sig
    label.new(bar_index, high, " ⚡ " + str.tostring(math.round(prob_bear,1)) + "% ",
        style=label.style_label_down, color=color.new(#FF8F00, 20),
        textcolor=C_W, yloc=yloc.abovebar, size=size.small)

if in_decay
    label.new(bar_index, high, " ⏱ DECAY ",
        style=label.style_label_down, color=color.new(#FF6F00, 25),
        textcolor=C_W, yloc=yloc.abovebar, size=size.tiny)

// ╔══════════════════════════════════════════════════════════════════╗
// ║         FULL HUD DASHBOARD                                       ║
// ╚══════════════════════════════════════════════════════════════════╝
var table hud = table.new(position.bottom_right, 2, 30,
    bgcolor=color.new(#060A12, 8), border_width=1,
    border_color=color.new(#1C2B3A, 30),
    frame_width=2, frame_color=color.new(#00BCD4, 50))

H_HDR  = color.new(#0D1F2D, 0)
H_ROW  = color.new(#0A1520, 15)
H_DIV  = color.new(#060D18, 0)
H_BULL = color.new(#003D1F, 15)
H_BEAR = color.new(#3D0000, 15)
H_WARN = color.new(#3D2600, 15)
H_NEUT = color.new(#111820, 35)
TS     = color.new(#8EABC0, 0)

if barstate.islast
    // Header
    rg_str  = regime == REGIME_STRONG ? "STRONG" : regime == REGIME_WEAK ? "WEAK" : regime == REGIME_RANGE ? "RANGE" : "CHOP"
    rg_clr  = regime == REGIME_STRONG ? color.new(#00E676,0) : regime == REGIME_WEAK ? color.new(#76FF03,0) : regime == REGIME_RANGE ? color.new(#FFB300,0) : color.new(#FF5252,0)

    table.cell(hud, 0, 0, "  NIFTY AI v7  [1min]",  text_color=color.new(#00BCD4,0), bgcolor=H_HDR, text_size=size.small)
    table.cell(hud, 1, 0, in_sess ? "● LIVE" : in_decay ? "⏱ DECAY" : "○ OFF",
               text_color=in_sess ? color.new(#00E676,0) : in_decay ? color.new(#FF6F00,0) : color.new(#FF5252,0),
               bgcolor=H_HDR, text_size=size.small)

    // Regime row
    table.cell(hud, 0, 1, "Regime",  text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 1, rg_str, text_color=rg_clr, text_size=size.small, bgcolor=H_ROW)

    // Probabilities
    table.cell(hud, 0, 2, "AI CALL %", text_color=TS, text_size=size.small, bgcolor=H_ROW)
    table.cell(hud, 1, 2, str.tostring(math.round(prob_bull,1)) + "%",
               text_color=prob_bull >= min_prob ? color.new(#00E676,0) : prob_bull >= warmup_thr ? color.new(#FFB300,0) : color.new(#546E7A,0),
               bgcolor=prob_bull >= min_prob ? H_BULL : prob_bull >= warmup_thr ? H_WARN : H_NEUT,
               text_size=size.normal)

    table.cell(hud, 0, 3, "AI PUT %",  text_color=TS, text_size=size.small, bgcolor=H_ROW)
    table.cell(hud, 1, 3, str.tostring(math.round(prob_bear,1)) + "%",
               text_color=prob_bear >= min_prob ? color.new(#FF5252,0) : prob_bear >= warmup_thr ? color.new(#FFB300,0) : color.new(#546E7A,0),
               bgcolor=prob_bear >= min_prob ? H_BEAR : prob_bear >= warmup_thr ? H_WARN : H_NEUT,
               text_size=size.normal)

    // Heat meter
    act_heat = prob_bull > prob_bear ? int(bull_heat) : int(bear_heat)
    table.cell(hud, 0, 4, "Signal Heat", text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 4, heat_blk(act_heat) + " " + str.tostring(act_heat) + "/10",
               text_color=heat_clr(act_heat), text_size=size.tiny, bgcolor=H_ROW)

    // Divider: Score Breakdown
    table.cell(hud, 0, 5, "  MODULE SCORES", text_color=color.new(#2A4A62,0), bgcolor=H_DIV, text_size=size.tiny)
    table.cell(hud, 1, 5, "", bgcolor=H_DIV)

    table.cell(hud, 0, 6,  "Trend",       text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 6,  str.tostring(math.round(tr_b,0)) + " / 25",   text_color=score_col(tr_b,25),  text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 0, 7,  "Momentum",    text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 7,  str.tostring(math.round(mo_b,0)) + " / 25",   text_color=score_col(mo_b,25),  text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 0, 8,  "Vol Delta",   text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 8,  str.tostring(math.round(vl_b,0)) + " / 20",   text_color=score_col(vl_b,20),  text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 0, 9,  "VWAP / BB",   text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 9,  str.tostring(math.round(vw_b,0)) + " / 20",   text_color=score_col(vw_b,20),  text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 0, 10, "Price Action",text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 10, str.tostring(math.round(pa_b,0)) + " / 10",   text_color=score_col(pa_b,10),  text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 0, 11, "SMC",         text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 11, str.tostring(math.round(smc_b,0)) + " / 20",  text_color=score_col(smc_b,20), text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 0, 12, "ML / MTF",    text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 12, str.tostring(math.round(ml_b,0)) + " / 20",   text_color=score_col(ml_b,20),  text_size=size.tiny, bgcolor=H_ROW)

    // Divider: Smart Money
    table.cell(hud, 0, 13, "  SMART MONEY", text_color=color.new(#2A4A62,0), bgcolor=H_DIV, text_size=size.tiny)
    table.cell(hud, 1, 13, "", bgcolor=H_DIV)

    table.cell(hud, 0, 14, "Structure",   text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 14,
               bos_bull ? "BOS ▲" : bos_bear ? "BOS ▼" : choch_bull ? "CHoCH ▲" : choch_bear ? "CHoCH ▼" : smc_bull_bias ? "Bias ▲" : smc_bear_bias ? "Bias ▼" : "—",
               text_color=smc_bull_bias ? color.new(#00E676,0) : smc_bear_bias ? color.new(#FF5252,0) : color.new(#546E7A,0),
               text_size=size.tiny, bgcolor=H_ROW)

    table.cell(hud, 0, 15, "Order Block",  text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 15,
               in_bull_ob ? "IN BULL OB" : in_bear_ob ? "IN BEAR OB" : "—",
               text_color=in_bull_ob ? color.new(#00E676,0) : in_bear_ob ? color.new(#FF5252,0) : color.new(#546E7A,0),
               text_size=size.tiny, bgcolor=H_ROW)

    // Divider: Confluence
    table.cell(hud, 0, 16, "  MTF CONFLUENCE", text_color=color.new(#2A4A62,0), bgcolor=H_DIV, text_size=size.tiny)
    table.cell(hud, 1, 16, "", bgcolor=H_DIV)

    table.cell(hud, 0, 17, "5min Bias",  text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 17, mtf5_bull ? "BULL ▲" : mtf5_bear ? "BEAR ▼" : "NEUTRAL",
               text_color=mtf5_bull ? color.new(#00E676,0) : mtf5_bear ? color.new(#FF5252,0) : color.new(#FFB300,0),
               text_size=size.tiny, bgcolor=H_ROW)

    table.cell(hud, 0, 18, "15min Bias", text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 18, mtf15_bull ? "BULL ▲" : mtf15_bear ? "BEAR ▼" : "NEUTRAL",
               text_color=mtf15_bull ? color.new(#00E676,0) : mtf15_bear ? color.new(#FF5252,0) : color.new(#FFB300,0),
               text_size=size.tiny, bgcolor=H_ROW)

    table.cell(hud, 0, 19, "Stack",      text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    stack_bull_str = mtf_stack_bull == 2 ? "2/2 ●●" : mtf_stack_bull == 1 ? "1/2 ●○" : "0/2 ○○"
    stack_bear_str = mtf_stack_bear == 2 ? "2/2 ●●" : mtf_stack_bear == 1 ? "1/2 ●○" : "0/2 ○○"
    table.cell(hud, 1, 19,
               prob_bull > prob_bear ? stack_bull_str : stack_bear_str,
               text_color=color.new(#00BCD4,0), text_size=size.tiny, bgcolor=H_ROW)

    // Divider: Volume & ML
    table.cell(hud, 0, 20, "  VOLUME & ML", text_color=color.new(#2A4A62,0), bgcolor=H_DIV, text_size=size.tiny)
    table.cell(hud, 1, 20, "", bgcolor=H_DIV)

    table.cell(hud, 0, 21, "Delta",       text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 21, delta_bull ? "BUY PRES" : delta_bear ? "SELL PRES" : "NEUTRAL",
               text_color=delta_bull ? color.new(#00E676,0) : delta_bear ? color.new(#FF5252,0) : color.new(#546E7A,0),
               text_size=size.tiny, bgcolor=H_ROW)

    table.cell(hud, 0, 22, "ML Bull%",    text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 22, str.tostring(math.round(ml_bull_rate,0)) + "% (" + str.tostring(pat_total) + " bars)",
               text_color=ml_bull_rate >= 60 ? color.new(#00E676,0) : ml_bull_rate >= 50 ? color.new(#FFB300,0) : color.new(#FF5252,0),
               text_size=size.tiny, bgcolor=H_ROW)

    table.cell(hud, 0, 23, "IV Regime",   text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 23, iv_regime,
               text_color=iv_regime == "LOW" ? color.new(#00E676,0) : iv_regime == "HIGH" ? color.new(#FF5252,0) : color.new(#FFB300,0),
               text_size=size.tiny, bgcolor=H_ROW)

    // Divider: Filters
    table.cell(hud, 0, 24, "  FILTERS", text_color=color.new(#2A4A62,0), bgcolor=H_DIV, text_size=size.tiny)
    table.cell(hud, 1, 24, "", bgcolor=H_DIV)

    table.cell(hud, 0, 25, "Candle Pat",  text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 25, bull_pat ? "BULL OK" : bear_pat ? "BEAR OK" : "—",
               text_color=bull_pat or bear_pat ? color.new(#00E676,0) : color.new(#546E7A,0),
               text_size=size.tiny, bgcolor=H_ROW)

    table.cell(hud, 0, 26, "2-Bar Consec",text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 26, consec_b ? "BULL OK" : consec_s ? "BEAR OK" : "—",
               text_color=consec_b or consec_s ? color.new(#00E676,0) : color.new(#546E7A,0),
               text_size=size.tiny, bgcolor=H_ROW)

    table.cell(hud, 0, 27, "Delta Div",   text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 27, delta_div_bull ? "BULL DIV" : delta_div_bear ? "BEAR DIV" : "none",
               text_color=delta_div_bull ? color.new(#00E676,0) : delta_div_bear ? color.new(#FF5252,0) : color.new(#546E7A,0),
               text_size=size.tiny, bgcolor=H_ROW)

    // Divider: Trade
    // Trade state banner
    trade_state_str = act_call ? "▲ CALL ACTIVE" : act_put ? "▼ PUT ACTIVE" : sl_just_hit ? "✕ STOPPED" : trade_done ? "✓ COMPLETE" : "○ IDLE"
    trade_state_clr = act_call ? color.new(#00E676,0) : act_put ? color.new(#FF5252,0) : sl_just_hit ? color.new(#FF0000,0) : trade_done ? color.new(#00BCD4,0) : color.new(#546E7A,0)
    trade_state_bg  = act_call ? H_BULL : act_put ? H_BEAR : H_DIV

    table.cell(hud, 0, 28, "  TRADE", text_color=color.new(#2A4A62,0), bgcolor=H_DIV, text_size=size.tiny)
    table.cell(hud, 1, 28, trade_state_str, text_color=trade_state_clr, bgcolor=trade_state_bg, text_size=size.tiny)

    trl_disp = act_call ? trl_b_sl : act_put ? trl_s_sl : na

    // Live target progress rows
    table.cell(hud, 0, 29, "  LIVE TRADE", text_color=color.new(#2A4A62,0), bgcolor=H_DIV, text_size=size.tiny)
    table.cell(hud, 1, 29, "", bgcolor=H_DIV)

    // We need extra rows — extend table dynamically via cell updates
    // T1/T2/T3 hit status
    t1_done_d = not na(sv_sl) and (sv_call ? close >= sv_t1 : close <= sv_t1)
    t2_done_d = not na(sv_sl) and (sv_call ? close >= sv_t2 : close <= sv_t2)
    t3_done_d = not na(sv_sl) and (sv_call ? close >= sv_t3 : close <= sv_t3)
    sl_hit_d  = not na(sv_sl) and (sv_call ? close <= sv_sl : close >= sv_sl)

    tgt_str = (t3_done_d ? "T3●" : "T3○") + "  " + (t2_done_d ? "T2●" : "T2○") + "  " + (t1_done_d ? "T1●" : "T1○")
    tgt_clr = t3_done_d ? color.new(#00BCD4,0) : t2_done_d ? color.new(#00C853,0) : t1_done_d ? color.new(#00E676,0) : color.new(#546E7A,0)

    table.cell(hud, 0, 28, "Targets",     text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 28, tgt_str, text_color=tgt_clr, text_size=size.tiny,
               bgcolor=t3_done_d ? color.new(#003040,20) : t1_done_d ? H_BULL : sl_hit_d ? H_BEAR : H_ROW)

    table.cell(hud, 0, 29, "TSL / BE",    text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 29,
               be_on and not na(entry_px) ? "═ BE: " + f_price(entry_px) : t1_hit and not na(trl_disp) ? "TSL: " + f_price(trl_disp) : "— await T1",
               text_color=be_on ? color.new(#00E5FF,0) : color.new(#FFB300,0),
               text_size=size.tiny, bgcolor=H_ROW)

    // SL intelligence row — show which layer set the SL and risk in points
    sl_source = not na(ob_sl_bull) and call_sl == math.max(math.min(ob_sl_bull, atr_floor_bull), atr_ceil_bull) ? "OB floor" : not na(vwap_sl_bull) and vwap_v < close and vwap_v > struct_low ? "VWAP" : not na(wick_sl_bull) and wick_ratio_low > 0.30 ? "Wick" : "Structure"
    sl_source_bear = not na(ob_sl_bear) and put_sl == math.min(math.max(ob_sl_bear, atr_floor_bear), atr_ceil_bear) ? "OB ceil" : not na(vwap_sl_bear) and vwap_v > close and vwap_v < struct_high ? "VWAP" : not na(wick_sl_bear) and wick_ratio_high > 0.30 ? "Wick" : "Structure"
    risk_pts_call = math.round(close - call_sl, 0)
    risk_pts_put  = math.round(put_sl - close, 0)

    table.cell(hud, 0, 28, "SL Source",   text_color=TS, text_size=size.tiny, bgcolor=H_ROW)
    table.cell(hud, 1, 28,
               act_call ? sl_source + " (" + str.tostring(risk_pts_call) + "p)" : act_put ? sl_source_bear + " (" + str.tostring(risk_pts_put) + "p)" : "—",
               text_color=color.new(#00BCD4,0), text_size=size.tiny, bgcolor=H_ROW)

// ╔══════════════════════════════════════════════════════════════════╗
// ║         CHART PLOTS & BACKGROUNDS                                ║
// ╚══════════════════════════════════════════════════════════════════╝
plot(ema200,  "EMA 200", color=color.new(#ECEFF1, 25), linewidth=2)
plot(ema50,   "EMA 50",  color=color.new(#CE93D8, 30), linewidth=1)
plot(ema21,   "EMA 21",  color=color.new(#42A5F5, 15))
plot(ema9,    "EMA 9",   color=color.new(#FFA726, 10))
plot(vwap_v,  "VWAP",    color=color.new(#FFEE58, 15), linewidth=2)

// Regime backgrounds — global scope (bgcolor cannot be in local scope in Pine v6)
bgcolor(show_regime and regime == REGIME_STRONG and st_d < 0 ? color.new(#00E676, 95) : na)
bgcolor(show_regime and regime == REGIME_STRONG and st_d > 0 ? color.new(#FF2D2D, 95) : na)
bgcolor(show_regime and regime == REGIME_WEAK   and st_d < 0 ? color.new(#00E676, 97) : na)
bgcolor(show_regime and regime == REGIME_WEAK   and st_d > 0 ? color.new(#FF2D2D, 97) : na)
bgcolor(show_regime and regime == REGIME_RANGE               ? color.new(#FFB300, 98) : na)
bgcolor(show_regime and regime == REGIME_CHOP                ? color.new(#FF5252, 98) : na)
bgcolor(in_sess                   ? color.new(#00BCD4, 98) : na)
bgcolor(in_decay                  ? color.new(#FF6F00, 97) : na)
bgcolor(warmup_b and not call_sig ? color.new(#FF8F00, 97) : na)
bgcolor(warmup_s and not put_sig  ? color.new(#FF8F00, 97) : na)

// Squeeze dots
plotshape(bb_squeeze, "Squeeze", shape.circle, location.bottom, color=color.new(#FF6D00, 40), size=size.tiny)

// Volume absorption markers
plotshape(absorb_bull, "Absorb Bull", shape.diamond, location.belowbar, color=color.new(#00E676, 30), size=size.tiny)
plotshape(absorb_bear, "Absorb Bear", shape.diamond, location.abovebar, color=color.new(#FF5252, 30), size=size.tiny)

The Takeaway

In the markets, we can't control the outcome of a single trade, but we can completely control our edge. By shifting from a predictive mindset to a probabilistic one, we can stress-test our strategies and execute with confidence.

#Nifty50 #OptionsTrading #AlgorithmicTrading #PineScript #TradingView #SystematicTrading #DataAnalytics #FinancialMarkets

Post a Comment

0 Comments