Indicator · Pine Script v6 · On the chart

SPX Midnight Box + Snag Logic

by dearvn

This indicator overlays an intraday SPX and ES chart with a midnight session range, regular-trading-hours opening levels, and a VWAP projection box.

View on GitHub

GitHub stars257on the repository
Pine versionv6current
Lines of code1,620
Settings40inputs
LicenceNone statedfile: MPL-2.0
Last update6 weeks ago11 Sep 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

1,620 lines of Pine Script v6 from es-logic-v6.txt, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

es-logic-v6.txt
//@version=6
// ═══════════════════════════════════════════════════════════════════════════════
//  SPX / ES — MIDNIGHT BOX + SNAG LOGIC  ·  v6
//
//  An intraday overlay for SPX / ES futures combining four independent reads:
//    · Midnight range box (00:00–06:00 local) with high / mid / low rails and
//      an "absolute entry" rejection signal at either edge.
//    · RTH open range (±15 / ±30 / ±60) plus the pre-10:15 high / low levels.
//    · A confirmed VWAP 30-point projection box with a colour-coded VWAP
//      trend line and a live VWAP + RSI dashboard.
//    · The Snag call/put engine: a 13-minute WMA trend state machine feeding
//      10-minute VWMA entry rails with trailing stops and exit signals.
//  Everything is non-repainting: higher-timeframe data is read with
//  lookahead_off, so a bar never sees data that did not exist at its close.
//
//  Full changelog at the bottom of this file.
//
//  This source code is subject to the terms of the Mozilla Public License 2.0
//  at https://mozilla.org/MPL/2.0/
//
//  Original v5 "SPX Midnight Box + Snag Logic"  © thinhdangho
//  v6 rewrite (Pine v6 port, look-ahead / DST / drawing-budget fixes,
//  diagnostics, alert conditions, status panel)  © Doan, 2026
//  Gaussian Adaptive MA  © LeafAlgo — https://www.tradingview.com/v/th7NZUPM/
// ═══════════════════════════════════════════════════════════════════════════════

indicator("SPX Midnight Box + Snag Logic v6", overlay = true, max_bars_back = 4999, max_lines_count = 500, max_labels_count = 500, max_boxes_count = 100)

// ═══════════════════════════════════════════════════════════════════════════════
//  0.  INPUT GROUPS
// ═══════════════════════════════════════════════════════════════════════════════
G_SESS  = "1 · Session & Midnight Box"
G_LVL   = "2 · Open / 10:15 Levels"
G_VWAP  = "3 · VWAP 30-Point Box"
G_RSI   = "4 · RSI Panel"
G_ALMA  = "5 · ALMA Smoothing"
G_GAMA  = "6 · Gaussian Adaptive MA"
G_HTF   = "7 · Higher-Timeframe WMA Trend"
G_SR    = "8 · Pivot Support / Resistance"
G_BAR   = "9 · Candle Colors"
G_ENG   = "10 · Snag Call/Put Engine"
G_DIAG  = "11 · Diagnostics (optional marks)"
G_ALERT = "12 · Webhook Alert"

// ═══════════════════════════════════════════════════════════════════════════════
//  1.  GENERIC HELPERS
// ═══════════════════════════════════════════════════════════════════════════════

// Safe division: returns na instead of inf / 0-divide garbage.
f_div(float nom, float den) =>
    na(den) or den == 0 ? float(na) : nom / den

f_percent(float nom, float den) =>
    100 * nz(f_div(nom, den))

f_roundUp(float number, int decimals) =>
    factor = math.pow(10, decimals)
    math.ceil(number * factor) / factor

// ═══════════════════════════════════════════════════════════════════════════════
//  2.  SESSION / TIME
//      v6 fix: every session test now uses one IANA timezone. The v5 script
//      mixed "America/Los_Angeles", "UTC-8" and "UTC-7", so half the session
//      windows were one hour off for ~8 months of the year (DST).
// ═══════════════════════════════════════════════════════════════════════════════
tz         = input.string("America/Los_Angeles", "Session Timezone", options = ["America/Los_Angeles", "America/New_York", "America/Chicago", "UTC"], group = G_SESS)
entryTol   = input.float(2.0, "Absolute Entry Tolerance", step = 0.25, group = G_SESS)
showEMAs   = input.bool(true, "Show EMAs", group = G_SESS)

showOpenLevels = input.bool(true, "Show Open Range Levels", group = G_LVL)
show1015Levels = input.bool(true, "Show 10:15 Levels", group = G_LVL)

// Bar-open clock (drives the daily reset and the level drawings)
hOpen = hour(time, tz)
mOpen = minute(time, tz)

// Bar-close clock (drives session snapshots and the webhook alert)
hCls = hour(time_close, tz)
mCls = minute(time_close, tz)

curYear  = year(time, tz)
curMonth = month(time, tz)
curDay   = dayofmonth(time, tz)

dayKey = curYear * 10000 + curMonth * 100 + curDay
newDay = barstate.isfirst or dayKey != dayKey[1]

isOpenBar = hOpen == 6 and mOpen == 30
inFirst45 = (hOpen == 6 and mOpen >= 30) or (hOpen == 7 and mOpen < 15)

pre1015   = hOpen < 10 or (hOpen == 10 and mOpen < 15)
post1015  = hOpen == 10 and mOpen >= 15 and mOpen <= 55
is1015Bar = hOpen == 10 and mOpen == 15

// Daily time anchors
rangeStart = timestamp(tz, curYear, curMonth, curDay, 0, 0, 1)
rangeEnd   = timestamp(tz, curYear, curMonth, curDay, 6, 0, 1)
endTime    = timestamp(tz, curYear, curMonth, curDay, 13, 0, 0)   // also used for badges

inMidnightRange    = time_close >= rangeStart and time <= rangeEnd
startMidnightRange = inMidnightRange and not inMidnightRange[1]

// ═══════════════════════════════════════════════════════════════════════════════
//  3.  VISIBLE-CHART HIGH / LOW  (native — replaces the PineCoders imports)
//      The v5 script imported PineCoders/VisibleChart TWICE (v4 unaliased and
//      v2 as pcVC) plus PineCoders/Time which was never used at all.
// ═══════════════════════════════════════════════════════════════════════════════
var float visLow = na

inVisible = time >= chart.left_visible_bar_time and time <= chart.right_visible_bar_time
if inVisible
    visLow := na(visLow) ? low : math.min(visLow, low)

// ═══════════════════════════════════════════════════════════════════════════════
//  4.  CHART-TIMEFRAME MOVING AVERAGES
//      v5 computed ta.wma(close, 200) twice (ma200 and wma_200) and
//      ta.wma(close, 11 / 48) twice each. De-duplicated here.
// ═══════════════════════════════════════════════════════════════════════════════
ema18 = ta.ema(close, 18)
ema50 = ta.ema(close, 50)
ema3  = ta.ema(close, 3)

wma11_c  = ta.wma(close, 11)
wma48_c  = ta.wma(close, 48)
wma200_c = ta.wma(close, 200)

ema18Up = ema18 > ema18[1]
ema18Dn = ema18 < ema18[1]

plot(showEMAs ? ema18 : na, title = "EMA 18", color = ema18Up ? color.green : color.red, linewidth = 2)
plot(showEMAs ? ema50 : na, title = "EMA 50", color = color.blue, linewidth = 2)

showChartWMAs = input.bool(true, "Plot chart-timeframe WMA 11 / 48 / 200", group = G_HTF)
plot(showChartWMAs ? wma11_c  : na, title = "WMA 11",  color = color.white)
plot(showChartWMAs ? wma48_c  : na, title = "WMA 48",  color = color.orange)
plot(showChartWMAs ? wma200_c : na, title = "WMA 200", color = color.orange)

// ═══════════════════════════════════════════════════════════════════════════════
//  5.  HIGHER-TIMEFRAME REQUESTS  —  NO-REPAINT
//      v5 called request.security(..., lookahead = barmerge.lookahead_on) on
//      *intraday* expressions with no history offset. On historical bars that
//      returns the FINAL value of an HTF bar that had not closed yet, so every
//      13m / 15m / 10m / 2m signal below was reading the future. Backtests
//      looked perfect and live trading did not match.
//
//      f_htf() uses lookahead_off, so a chart bar only ever sees higher-
//      timeframe data that already existed at that point in time. History is
//      then fixed, and the live bar develops and settles at its close, which
//      is what a live indicator should do.
//      "Strict HTF" additionally drops the forming HTF bar: values never move
//      intrabar, at the cost of one extra HTF bar of lag.
// ═══════════════════════════════════════════════════════════════════════════════
strictHTF = input.bool(false, "Strict HTF (ignore the forming HTF bar)", group = G_HTF, tooltip = "OFF = the live higher-timeframe bar develops during the session. Non-repainting on history.\nON  = only fully closed higher-timeframe bars are used. Nothing moves intrabar, but signals lag by one HTF bar.")
htfOff = strictHTF ? 1 : 0

f_htf(simple string tf, float expr) =>
    request.security(syminfo.tickerid, tf, expr[htfOff], lookahead = barmerge.lookahead_off)

// 10-minute
open_10  = f_htf("10", open)
close_10 = f_htf("10", close)
wma11_10 = f_htf("10", wma11_c)
wma48_10 = f_htf("10", wma48_c)

// 13-minute (the trend engine)
close_13  = f_htf("13", close)
wma11_13  = f_htf("13", wma11_c)
wma48_13  = f_htf("13", wma48_c)
wma200_13 = f_htf("13", wma200_c)

// 15-minute
close_15  = f_htf("15", close)
open_15   = f_htf("15", open)
wma200_15 = f_htf("15", wma200_c)

// 2-minute
close_2  = f_htf("2", close)
wma200_2 = f_htf("2", wma200_c)

// Daily — high[1]/close[1] with lookahead_on is already the safe idiom
// (the previous, completed daily bar), so no extra offset is applied.
highest_1d = request.security(syminfo.tickerid, "D", high[1],  lookahead = barmerge.lookahead_on)
// v5 bug: close_1d requested `close` (not close[1]) on the daily timeframe with
// lookahead_on, i.e. today's not-yet-known settlement. Fixed to close[1].
close_1d   = request.security(syminfo.tickerid, "D", close[1], lookahead = barmerge.lookahead_on)

// ═══════════════════════════════════════════════════════════════════════════════
//  6.  RSI
// ═══════════════════════════════════════════════════════════════════════════════
rsiPanelLength = input.int(14, "RSI Panel Length", minval = 1, group = G_RSI)
rsiPanelValue  = ta.rsi(close, rsiPanelLength)
rsiPanelChange  = not na(rsiPanelValue[1]) ? rsiPanelValue - rsiPanelValue[1] : na
rsiPanelChange6 = not na(rsiPanelValue[6]) ? rsiPanelValue - rsiPanelValue[6] : na
rsiPanelRising  = not na(rsiPanelValue[1]) and rsiPanelValue > rsiPanelValue[1]
rsiPanelFalling = not na(rsiPanelValue[1]) and rsiPanelValue < rsiPanelValue[1]
rsiPanelStatus = rsiPanelValue >= 70 ? "OVERBOUGHT" : rsiPanelValue <= 30 ? "OVERSOLD" : rsiPanelValue >= 55 and rsiPanelRising ? "BULLISH RISING" : rsiPanelValue >= 50 ? "BULLISH" : rsiPanelValue <= 45 and rsiPanelFalling ? "BEARISH FALLING" : rsiPanelValue < 50 ? "BEARISH" : "NEUTRAL"
rsiPanelColor  = rsiPanelValue >= 70 ? color.orange : rsiPanelValue <= 30 ? color.aqua : rsiPanelRising ? color.lime : rsiPanelFalling ? color.red : color.gray

// Fast RSI used by the Snag engine
rsi     = ta.rsi(close, 9)
rsi_15  = f_htf("15", rsi)
rsi_2   = f_htf("2", rsi)

// ═══════════════════════════════════════════════════════════════════════════════
//  7.  STATE
// ═══════════════════════════════════════════════════════════════════════════════
var float dayOpen      = na
var bool  callStrong   = false
var bool  putStrong    = false
var bool  strongLocked = false
var bool  putNow       = false
var bool  callNow      = false
var float highPre1015  = na
var float lowPre1015   = na

var float boxHigh = na
var float boxLow  = na
var float boxMid  = na

var box   midnightBox     = na
var line  midnightHiLine  = na
var line  midnightLoLine  = na
var line  midnightMidLine = na
var label highBadge       = na
var label lowBadge        = na
var label midBadge        = na

var bool  absCallFired  = false
var bool  absPutFired   = false
var float absCallLevel  = na
var float absPutLevel   = na
var label absEntryLabel = na

var line  openLine     = na
var line  up15Line     = na
var line  dn15Line     = na
var line  up30Line     = na
var line  dn30Line     = na
var line  up60Line     = na
var line  dn60Line     = na
var line  high1015Line = na
var line  low1015Line  = na
var label dirLabel     = na

var table rightPanel = table.new(position.middle_right, 1, 3, border_width = 1)

// ═══════════════════════════════════════════════════════════════════════════════
//  8.  VWAP CONFIRMED 30-POINT BOX
// ═══════════════════════════════════════════════════════════════════════════════
showVWAPBoxes           = input.bool(true, "Show VWAP Confirmed 30-Point Box", group = G_VWAP)
vwapBoxPoints           = input.float(30.0, "VWAP Box Points", step = 0.25, group = G_VWAP)
vwapSrc                 = input.source(hlc3, "VWAP Source", group = G_VWAP)
showVWAPTrendLine       = input.bool(true, "Show VWAP Green/Orange/Red Trend Line", group = G_VWAP)
vwapTrendKeepBars       = input.int(250, "VWAP Trend Line History Bars", minval = 25, maxval = 400, group = G_VWAP, tooltip = "Each bar of trend line costs one line object. The chart hard limit is 500 lines total; the S/R block below needs 6 more.")
vwapStrongDownThreshold = input.float(3.0, "VWAP Strong Down Threshold", step = 0.25, group = G_VWAP)

vwapValue = ta.vwap(vwapSrc)

// Green  = VWAP rising vs prior candle
// Orange = VWAP falling mildly
// Red    = VWAP falling strongly over the last 6 candles
vwapIncreasing     = not na(vwapValue[1]) and vwapValue > vwapValue[1]
vwapDecreasing     = not na(vwapValue[1]) and vwapValue < vwapValue[1]
vwapChangeFrom6    = not na(vwapValue[6]) ? vwapValue - vwapValue[6] : na
vwapStrongDecrease = vwapDecreasing and not na(vwapChangeFrom6) and vwapChangeFrom6 <= -vwapStrongDownThreshold
vwapTrendColor     = vwapIncreasing ? color.lime : vwapStrongDecrease ? color.red : vwapDecreasing ? color.orange : color.gray

var array<line> vwapDashSegments = array.new<line>()

if showVWAPTrendLine and not na(vwapValue) and not na(vwapValue[1])
    line vwapSegment = line.new(bar_index - 1, vwapValue[1], bar_index, vwapValue, xloc = xloc.bar_index, extend = extend.none, color = vwapTrendColor, width = 4, style = line.style_solid)
    array.push(vwapDashSegments, vwapSegment)
    if array.size(vwapDashSegments) > vwapTrendKeepBars
        line.delete(array.shift(vwapDashSegments))

var box   vwapUpBox               = na
var box   vwapDownBox             = na
var line  vwapBoxMidLine          = na
var label vwapBoxTopPriceLabel    = na
var label vwapBoxMidPriceLabel    = na
var label vwapBoxBottomPriceLabel = na
var label vwapBottomDiffLabel     = na
var int   vwapSide                = 0
var float vwapCrossPrice          = na
var float vwapPossibleTo          = na
var float vwapBoxTopValue         = na
var float vwapBoxMidValue         = na
var float vwapBoxBottomValue      = na
var table vwapValuePanel          = table.new(position.bottom_center, 1, 4, border_width = 2)

// ═══════════════════════════════════════════════════════════════════════════════
//  9.  DAILY RESET
// ═══════════════════════════════════════════════════════════════════════════════
if newDay
    boxHigh := na
    boxLow  := na
    boxMid  := na

    dayOpen      := na
    callStrong   := false
    putStrong    := false
    strongLocked := false
    putNow       := false
    callNow      := false
    highPre1015  := na
    lowPre1015   := na

    vwapSide       := 0
    vwapCrossPrice := na
    vwapPossibleTo := na

    absCallFired := false
    absPutFired  := false
    absCallLevel := na
    absPutLevel  := na

    if not na(midnightBox)
        box.delete(midnightBox)
        midnightBox := na
    if not na(midnightHiLine)
        line.delete(midnightHiLine)
        midnightHiLine := na
    if not na(midnightLoLine)
        line.delete(midnightLoLine)
        midnightLoLine := na
    if not na(midnightMidLine)
        line.delete(midnightMidLine)
        midnightMidLine := na
    if not na(highBadge)
        label.delete(highBadge)
        highBadge := na
    if not na(lowBadge)
        label.delete(lowBadge)
        lowBadge := na
    if not na(midBadge)
        label.delete(midBadge)
        midBadge := na
    if not na(absEntryLabel)
        label.delete(absEntryLabel)
        absEntryLabel := na
    if not na(openLine)
        line.delete(openLine)
        openLine := na
    if not na(up15Line)
        line.delete(up15Line)
        up15Line := na
    if not na(dn15Line)
        line.delete(dn15Line)
        dn15Line := na
    if not na(up30Line)
        line.delete(up30Line)
        up30Line := na
    if not na(dn30Line)
        line.delete(dn30Line)
        dn30Line := na
    if not na(up60Line)
        line.delete(up60Line)
        up60Line := na
    if not na(dn60Line)
        line.delete(dn60Line)
        dn60Line := na
    if not na(high1015Line)
        line.delete(high1015Line)
        high1015Line := na
    if not na(low1015Line)
        line.delete(low1015Line)
        low1015Line := na
    if not na(dirLabel)
        label.delete(dirLabel)
        dirLabel := na

    vwapBoxTopValue    := na
    vwapBoxMidValue    := na
    vwapBoxBottomValue := na

    while array.size(vwapDashSegments) > 0
        line.delete(array.shift(vwapDashSegments))

// ═══════════════════════════════════════════════════════════════════════════════
// 10.  MIDNIGHT RANGE  (00:00 → 06:00 local)
// ═══════════════════════════════════════════════════════════════════════════════
if startMidnightRange and barstate.isconfirmed
    boxHigh := high
    boxLow  := low
    boxMid  := (boxHigh + boxLow) / 2.0

    if not na(midnightBox)
        box.delete(midnightBox)
    if not na(midnightHiLine)
        line.delete(midnightHiLine)
    if not na(midnightLoLine)
        line.delete(midnightLoLine)
    if not na(midnightMidLine)
        line.delete(midnightMidLine)
    if not na(highBadge)
        label.delete(highBadge)
    if not na(lowBadge)
        label.delete(lowBadge)
    if not na(midBadge)
        label.delete(midBadge)

    midnightBox     := box.new(rangeStart, boxHigh, rangeEnd, boxLow, xloc = xloc.bar_time, border_color = color.orange, border_width = 2, bgcolor = color.new(color.orange, 92))
    midnightHiLine  := line.new(rangeEnd, boxHigh, endTime, boxHigh, xloc = xloc.bar_time, color = color.gray, width = 2, style = line.style_dashed)
    midnightLoLine  := line.new(rangeEnd, boxLow,  endTime, boxLow,  xloc = xloc.bar_time, color = color.gray, width = 2, style = line.style_dashed)
    midnightMidLine := line.new(rangeEnd, boxMid,  endTime, boxMid,  xloc = xloc.bar_time, color = color.yellow, width = 2, style = line.style_dashed)

    highBadge := label.new(endTime, boxHigh, "HIGH: " + str.tostring(boxHigh, format.mintick), xloc = xloc.bar_time, style = label.style_label_left, color = color.gray,   textcolor = color.white, size = size.small)
    lowBadge  := label.new(endTime, boxLow,  "LOW: "  + str.tostring(boxLow,  format.mintick), xloc = xloc.bar_time, style = label.style_label_left, color = color.gray,   textcolor = color.white, size = size.small)
    midBadge  := label.new(endTime, boxMid,  "MID: "  + str.tostring(boxMid,  format.mintick), xloc = xloc.bar_time, style = label.style_label_left, color = color.yellow, textcolor = color.black, size = size.small)

if inMidnightRange and barstate.isconfirmed
    boxHigh := na(boxHigh) ? high : math.max(boxHigh, high)
    boxLow  := na(boxLow)  ? low  : math.min(boxLow, low)
    boxMid  := (boxHigh + boxLow) / 2.0

    if not na(midnightBox)
        box.set_left(midnightBox, rangeStart)
        box.set_right(midnightBox, rangeEnd)
        box.set_top(midnightBox, boxHigh)
        box.set_bottom(midnightBox, boxLow)
    if not na(midnightHiLine)
        line.set_xy1(midnightHiLine, rangeEnd, boxHigh)
        line.set_xy2(midnightHiLine, endTime, boxHigh)
    if not na(midnightLoLine)
        line.set_xy1(midnightLoLine, rangeEnd, boxLow)
        line.set_xy2(midnightLoLine, endTime, boxLow)
    if not na(midnightMidLine)
        line.set_xy1(midnightMidLine, rangeEnd, boxMid)
        line.set_xy2(midnightMidLine, endTime, boxMid)
    if not na(highBadge)
        label.set_xy(highBadge, endTime, boxHigh)
        label.set_text(highBadge, "HIGH: " + str.tostring(boxHigh, format.mintick))
    if not na(lowBadge)
        label.set_xy(lowBadge, endTime, boxLow)
        label.set_text(lowBadge, "LOW: " + str.tostring(boxLow, format.mintick))
    if not na(midBadge)
        label.set_xy(midBadge, endTime, boxMid)
        label.set_text(midBadge, "MID: " + str.tostring(boxMid, format.mintick))

// ═══════════════════════════════════════════════════════════════════════════════
// 11.  RTH OPEN RESET + OPEN RANGE LEVELS
// ═══════════════════════════════════════════════════════════════════════════════
if isOpenBar and barstate.isconfirmed
    dayOpen      := open
    callStrong   := false
    putStrong    := false
    strongLocked := false
    putNow       := false
    callNow      := false
    highPre1015  := na
    lowPre1015   := na

    // v5 deleted these lines but left the ids non-na, so later
    // `if not na(openLine)` guards operated on dead objects.
    if not na(openLine)
        line.delete(openLine)
        openLine := na
    if not na(up15Line)
        line.delete(up15Line)
        up15Line := na
    if not na(dn15Line)
        line.delete(dn15Line)
        dn15Line := na
    if not na(up30Line)
        line.delete(up30Line)
        up30Line := na
    if not na(dn30Line)
        line.delete(dn30Line)
        dn30Line := na
    if not na(up60Line)
        line.delete(up60Line)
        up60Line := na
    if not na(dn60Line)
        line.delete(dn60Line)
        dn60Line := na
    if not na(high1015Line)
        line.delete(high1015Line)
        high1015Line := na
    if not na(low1015Line)
        line.delete(low1015Line)
        low1015Line := na
    if not na(dirLabel)
        label.delete(dirLabel)
        dirLabel := na

hasOpen = not na(dayOpen)
up15 = hasOpen ? dayOpen + 15 : na
dn15 = hasOpen ? dayOpen - 15 : na
up30 = hasOpen ? dayOpen + 30 : na
dn30 = hasOpen ? dayOpen - 30 : na
up60 = hasOpen ? dayOpen + 60 : na
dn60 = hasOpen ? dayOpen - 60 : na

if showOpenLevels and isOpenBar and hasOpen and barstate.isconfirmed
    openLine := line.new(time, dayOpen, endTime, dayOpen, xloc = xloc.bar_time, color = color.white,  width = 2)
    up15Line := line.new(time, up15,    endTime, up15,    xloc = xloc.bar_time, color = color.green,  width = 1, style = line.style_dotted)
    dn15Line := line.new(time, dn15,    endTime, dn15,    xloc = xloc.bar_time, color = color.yellow, width = 1, style = line.style_dotted)
    up30Line := line.new(time, up30,    endTime, up30,    xloc = xloc.bar_time, color = color.green,  width = 2)
    dn30Line := line.new(time, dn30,    endTime, dn30,    xloc = xloc.bar_time, color = color.red,    width = 2)
    up60Line := line.new(time, up60,    endTime, up60,    xloc = xloc.bar_time, color = color.orange, width = 2)
    dn60Line := line.new(time, dn60,    endTime, dn60,    xloc = xloc.bar_time, color = color.purple, width = 1, style = line.style_dotted)

// ═══════════════════════════════════════════════════════════════════════════════
// 12.  VWAP OUTLINE BOX
// ═══════════════════════════════════════════════════════════════════════════════
priceAboveVWAPConfirmed = showVWAPBoxes and barstate.isconfirmed and not na(vwapValue) and close >= vwapValue
priceBelowVWAPConfirmed = showVWAPBoxes and barstate.isconfirmed and not na(vwapValue) and close <= vwapValue

newVWAPUp   = priceAboveVWAPConfirmed and vwapSide != 1
newVWAPDown = priceBelowVWAPConfirmed and vwapSide != -1

// Single teardown for all three cases (new day, flip up, flip down). v5
// repeated this block verbatim three times; it cannot live in a function
// because Pine forbids assigning to globals from function scope.
if newDay or newVWAPUp or newVWAPDown
    if not na(vwapUpBox)
        box.delete(vwapUpBox)
        vwapUpBox := na
    if not na(vwapDownBox)
        box.delete(vwapDownBox)
        vwapDownBox := na
    if not na(vwapBoxMidLine)
        line.delete(vwapBoxMidLine)
        vwapBoxMidLine := na
    if not na(vwapBoxTopPriceLabel)
        label.delete(vwapBoxTopPriceLabel)
        vwapBoxTopPriceLabel := na
    if not na(vwapBoxMidPriceLabel)
        label.delete(vwapBoxMidPriceLabel)
        vwapBoxMidPriceLabel := na
    if not na(vwapBoxBottomPriceLabel)
        label.delete(vwapBoxBottomPriceLabel)
        vwapBoxBottomPriceLabel := na
    if not na(vwapBottomDiffLabel)
        label.delete(vwapBottomDiffLabel)
        vwapBottomDiffLabel := na

if newVWAPUp
    vwapSide := 1

    vwapUpTarget       = close + vwapBoxPoints
    vwapCrossPrice    := close
    vwapPossibleTo    := vwapUpTarget
    vwapBoxTopValue   := vwapUpTarget
    vwapBoxBottomValue := close
    vwapBoxMidValue   := (vwapBoxTopValue + vwapBoxBottomValue) / 2.0

    vwapUpBox               := box.new(left = bar_index, top = vwapBoxTopValue, right = bar_index + 1, bottom = vwapBoxBottomValue, xloc = xloc.bar_index, border_color = color.lime, border_width = 3, bgcolor = color.new(color.lime, 100))
    vwapBoxMidLine          := line.new(bar_index, vwapBoxMidValue, bar_index + 1, vwapBoxMidValue, xloc = xloc.bar_index, extend = extend.none, color = color.yellow, width = 2, style = line.style_dashed)
    vwapBoxTopPriceLabel    := label.new(bar_index + 1, vwapBoxTopValue,    "VWAP TOP: "    + str.tostring(vwapBoxTopValue,    format.mintick), xloc = xloc.bar_index, style = label.style_label_left, color = color.lime,   textcolor = color.black, size = size.small)
    vwapBoxMidPriceLabel    := label.new(bar_index + 1, vwapBoxMidValue,    "VWAP MID: "    + str.tostring(vwapBoxMidValue,    format.mintick), xloc = xloc.bar_index, style = label.style_label_left, color = color.yellow, textcolor = color.black, size = size.small)
    vwapBoxBottomPriceLabel := label.new(bar_index + 1, vwapBoxBottomValue, "VWAP BOTTOM: " + str.tostring(vwapBoxBottomValue, format.mintick), xloc = xloc.bar_index, style = label.style_label_left, color = color.green,  textcolor = color.white, size = size.small)

if newVWAPDown
    vwapSide := -1

    vwapDownTarget      = close - vwapBoxPoints
    vwapCrossPrice     := close
    vwapPossibleTo     := vwapDownTarget
    vwapBoxTopValue    := close
    vwapBoxBottomValue := vwapDownTarget
    vwapBoxMidValue    := (vwapBoxTopValue + vwapBoxBottomValue) / 2.0

    vwapDownBox             := box.new(left = bar_index, top = vwapBoxTopValue, right = bar_index + 1, bottom = vwapBoxBottomValue, xloc = xloc.bar_index, border_color = color.red, border_width = 3, bgcolor = color.new(color.red, 100))
    vwapBoxMidLine          := line.new(bar_index, vwapBoxMidValue, bar_index + 1, vwapBoxMidValue, xloc = xloc.bar_index, extend = extend.none, color = color.yellow, width = 2, style = line.style_dashed)
    vwapBoxTopPriceLabel    := label.new(bar_index + 1, vwapBoxTopValue,    "VWAP TOP: "    + str.tostring(vwapBoxTopValue,    format.mintick), xloc = xloc.bar_index, style = label.style_label_left, color = color.red,    textcolor = color.white, size = size.small)
    vwapBoxMidPriceLabel    := label.new(bar_index + 1, vwapBoxMidValue,    "VWAP MID: "    + str.tostring(vwapBoxMidValue,    format.mintick), xloc = xloc.bar_index, style = label.style_label_left, color = color.yellow, textcolor = color.black, size = size.small)
    vwapBoxBottomPriceLabel := label.new(bar_index + 1, vwapBoxBottomValue, "VWAP BOTTOM: " + str.tostring(vwapBoxBottomValue, format.mintick), xloc = xloc.bar_index, style = label.style_label_left, color = color.red,    textcolor = color.white, size = size.small)

// Keep the active box / mid line / price tags flowing with the current candle.
if not na(vwapUpBox)
    box.set_right(vwapUpBox, bar_index)
if not na(vwapDownBox)
    box.set_right(vwapDownBox, bar_index)

if not na(vwapBoxMidLine) and not na(vwapBoxMidValue)
    line.set_x2(vwapBoxMidLine, bar_index)
    line.set_y1(vwapBoxMidLine, vwapBoxMidValue)
    line.set_y2(vwapBoxMidLine, vwapBoxMidValue)
if not na(vwapBoxTopPriceLabel) and not na(vwapBoxTopValue)
    label.set_xy(vwapBoxTopPriceLabel, bar_index + 1, vwapBoxTopValue)
    label.set_text(vwapBoxTopPriceLabel, "VWAP TOP: " + str.tostring(vwapBoxTopValue, format.mintick))
if not na(vwapBoxMidPriceLabel) and not na(vwapBoxMidValue)
    label.set_xy(vwapBoxMidPriceLabel, bar_index + 1, vwapBoxMidValue)
    label.set_text(vwapBoxMidPriceLabel, "VWAP MID: " + str.tostring(vwapBoxMidValue, format.mintick))
if not na(vwapBoxBottomPriceLabel) and not na(vwapBoxBottomValue)
    label.set_xy(vwapBoxBottomPriceLabel, bar_index + 1, vwapBoxBottomValue)
    label.set_text(vwapBoxBottomPriceLabel, "VWAP BOTTOM: " + str.tostring(vwapBoxBottomValue, format.mintick))

// Distance from the VWAP box bottom to price, and its 3-candle trend.
vwapBottomDiff            = not na(vwapBoxBottomValue) ? close - vwapBoxBottomValue : na
vwapBottomDiffAbs         = math.abs(vwapBottomDiff)
vwapBottomDiffSign        = na(vwapBottomDiff) ? "" : vwapBottomDiff >= 0 ? "+" : "-"
vwapBottomDiffChange3     = not na(vwapBottomDiff[3]) ? vwapBottomDiff - vwapBottomDiff[3] : na
vwapBottomDiffChange3Abs  = math.abs(vwapBottomDiffChange3)
vwapBottomDiffChange3Sign = na(vwapBottomDiffChange3) ? "" : vwapBottomDiffChange3 >= 0 ? "+" : "-"
vwapBottomDiffDirection3  = na(vwapBottomDiffChange3) ? "N/A" : vwapBottomDiffChange3 > 0 ? "3C INCREASE" : vwapBottomDiffChange3 < 0 ? "3C DECREASE" : "3C FLAT"
vwapBottomDiffText  = na(vwapBottomDiff) ? "BOTTOM VWAP DIFF: N/A" : "BOTTOM VWAP DIFF: " + vwapBottomDiffSign + str.tostring(vwapBottomDiffAbs, format.mintick) + "  |  " + vwapBottomDiffDirection3 + ": " + (na(vwapBottomDiffChange3) ? "N/A" : vwapBottomDiffChange3Sign + str.tostring(vwapBottomDiffChange3Abs, format.mintick))
vwapBottomDiffColor = na(vwapBottomDiffChange3) ? color.gray : vwapBottomDiffChange3 > 0 ? color.lime : vwapBottomDiffChange3 < 0 ? color.red : color.orange
vwapDiffLabelY      = close >= open ? high + 8 : low - 8
vwapDiffLabelStyle  = close >= open ? label.style_label_down : label.style_label_up

if barstate.islast and not na(vwapBottomDiff)
    if na(vwapBottomDiffLabel)
        vwapBottomDiffLabel := label.new(bar_index, vwapDiffLabelY, vwapBottomDiffText, xloc = xloc.bar_index, style = vwapDiffLabelStyle, color = vwapBottomDiffColor, textcolor = color.white, size = size.normal)
    else
        label.set_xy(vwapBottomDiffLabel, bar_index, vwapDiffLabelY)
        label.set_text(vwapBottomDiffLabel, vwapBottomDiffText)
        label.set_color(vwapBottomDiffLabel, vwapBottomDiffColor)
        label.set_style(vwapBottomDiffLabel, vwapDiffLabelStyle)

// ═══════════════════════════════════════════════════════════════════════════════
// 13.  BOTTOM DASHBOARD  (VWAP + RSI)
// ═══════════════════════════════════════════════════════════════════════════════
priceAwayFromVWAP      = close - vwapValue
vwapSixCandleChange    = not na(vwapValue[6]) ? vwapValue - vwapValue[6] : na
vwapSixCandleDirection = na(vwapSixCandleChange) ? "N/A" : vwapSixCandleChange > 0 ? "INCREASE" : vwapSixCandleChange < 0 ? "DECREASE" : "FLAT"
vwapSixCandleAbs       = math.abs(vwapSixCandleChange)
vwapSixCandleStrongDn  = not na(vwapSixCandleChange) and vwapSixCandleChange <= -vwapStrongDownThreshold

if barstate.islast
    string crossText     = na(vwapCrossPrice) ? str.tostring(close, format.mintick) : str.tostring(vwapCrossPrice, format.mintick)
    string possibleText  = na(vwapPossibleTo) ? str.tostring(vwapValue, format.mintick) : str.tostring(vwapPossibleTo, format.mintick)
    string awaySign      = priceAwayFromVWAP >= 0 ? "+" : ""
    string sixSign       = na(vwapSixCandleChange) ? "" : vwapSixCandleChange > 0 ? "+" : vwapSixCandleChange < 0 ? "-" : ""
    string sixText       = na(vwapSixCandleChange) ? "N/A" : sixSign + str.tostring(vwapSixCandleAbs, format.mintick)
    string rsiSign       = na(rsiPanelChange)  ? "" : rsiPanelChange  > 0 ? "+" : ""
    string rsi6Sign      = na(rsiPanelChange6) ? "" : rsiPanelChange6 > 0 ? "+" : ""
    string rsiText       = na(rsiPanelChange)  ? "N/A" : rsiSign  + str.tostring(rsiPanelChange,  "#.##")
    string rsi6Text      = na(rsiPanelChange6) ? "N/A" : rsi6Sign + str.tostring(rsiPanelChange6, "#.##")

    string row1 = "VWAP CROSS $: " + crossText + "  |  Possible VWAP to: " + possibleText
    string row2 = "Current VWAP: " + str.tostring(vwapValue, format.mintick) + "  |  Price Away from VWAP: " + awaySign + str.tostring(priceAwayFromVWAP, format.mintick)
    string row3 = "VWAP Last 6 Candles: " + vwapSixCandleDirection + "  |  Change: " + sixText
    string row4 = "RSI(" + str.tostring(rsiPanelLength) + "): " + str.tostring(rsiPanelValue, "#.##") + "  |  Status: " + rsiPanelStatus + "  |  1-Bar: " + rsiText + "  |  6-Bar: " + rsi6Text

    color panelBg   = vwapSide == 1 ? color.lime : vwapSide == -1 ? color.red : color.aqua
    color awayColor = priceAwayFromVWAP >= 0 ? color.lime : color.red
    color sixColor  = na(vwapSixCandleChange) ? color.white : vwapSixCandleChange > 0 ? color.lime : vwapSixCandleStrongDn ? color.red : vwapSixCandleChange < 0 ? color.orange : color.gray

    table.cell(vwapValuePanel, 0, 0, row1, text_color = color.black, bgcolor = panelBg,     text_size = size.large)
    table.cell(vwapValuePanel, 0, 1, row2, text_color = awayColor,   bgcolor = color.black, text_size = size.large)
    table.cell(vwapValuePanel, 0, 2, row3, text_color = sixColor,    bgcolor = color.black, text_size = size.large)
    table.cell(vwapValuePanel, 0, 3, row4, text_color = rsiPanelColor, bgcolor = color.black, text_size = size.large)

// ═══════════════════════════════════════════════════════════════════════════════
// 14.  MORNING HIGH / LOW BEFORE 10:15
// ═══════════════════════════════════════════════════════════════════════════════
if pre1015 and barstate.isconfirmed
    highPre1015 := na(highPre1015) ? high : math.max(highPre1015, high)
    lowPre1015  := na(lowPre1015)  ? low  : math.min(lowPre1015, low)

if show1015Levels and is1015Bar and barstate.isconfirmed and not na(highPre1015) and not na(lowPre1015)
    high1015Line := line.new(time, highPre1015, endTime, highPre1015, xloc = xloc.bar_time, color = color.red,   width = 2)
    low1015Line  := line.new(time, lowPre1015,  endTime, lowPre1015,  xloc = xloc.bar_time, color = color.green, width = 2)

// ═══════════════════════════════════════════════════════════════════════════════
// 15.  ALMA SMOOTHING + GAUSSIAN ADAPTIVE MA  (C-CF / P signals)
// ═══════════════════════════════════════════════════════════════════════════════
src_sm  = input.source(close, "Source", group = G_ALMA)
smooth  = input.int(1,  "Smoothing", minval = 1, group = G_ALMA)
length1 = input.int(25, "Lookback",  minval = 1, group = G_ALMA)
almaOffset = 0.85
almaSigma  = 7
pchange   = f_percent(ta.change(src_sm, smooth), src_sm)
avpchange = ta.alma(pchange, length1, almaOffset, almaSigma)

// GAMA — credit © LeafAlgo, https://www.tradingview.com/v/th7NZUPM/
gamaLength     = input.int(14, "Length", minval = 1, group = G_GAMA)
gamaAdaptive   = input.bool(true, "Adaptive Parameters", group = G_GAMA)
volatilityPeriod = input.int(20, "Volatility Period", minval = 1, group = G_GAMA)
// v5 declared this input inside a ternary branch. Inputs must live at top level.
gamaFixedSigma = input.float(1.0, "Standard Deviation (non-adaptive)", minval = 0.1, group = G_GAMA)

gmaSigma = gamaAdaptive ? ta.stdev(close, volatilityPeriod) : gamaFixedSigma
// Guard: a flat close series makes stdev 0 → division by zero in the weight.
gmaSigmaSafe = math.max(nz(gmaSigma), 1e-10)

float gma = 0.0
float sumOfWeights = 0.0
for i = 0 to gamaLength - 1
    weight = math.exp(-math.pow((i - (gamaLength - 1)) / (2 * gmaSigmaSafe), 2) / 2)
    value  = ta.highest(avpchange, i + 1) + ta.lowest(avpchange, i + 1)
    gma := gma + (value * weight)
    sumOfWeights := sumOfWeights + weight

gma := nz(f_div(gma, sumOfWeights)) / 2
gma := ta.ema(gma, 7)

almaBuy  = ta.crossover(avpchange, gma)  and barstate.isconfirmed
almaSell = ta.crossunder(avpchange, gma) and barstate.isconfirmed

plotshape(almaBuy,  "Buy Signal",  text = "C-CF", textcolor = color.white, style = shape.labelup,   location = location.belowbar, color = color.rgb(0, 161, 5),  offset = -1)
plotshape(almaSell, "Sell Signal", text = "P",    textcolor = color.white, style = shape.labeldown, location = location.abovebar, color = color.rgb(215, 0, 0), offset = -1)

// ═══════════════════════════════════════════════════════════════════════════════
// 16.  10-MINUTE VWMA BANDS  (uper_10 / lower_10 — the engine's entry rails)
// ═══════════════════════════════════════════════════════════════════════════════
cross_up_point   = input.float(10.0, "Strong S/R Cross Up Points",   minval = 1, maxval = 50, group = G_SR)
cross_down_point = input.float(10.0, "Strong S/R Cross Down Points", minval = 1, maxval = 50, group = G_SR)
bandMult = input.float(3.0, "VWMA Band Multiplier", minval = 0.001, maxval = 50, group = G_ENG)

basis3 = ta.vwma(open_10, 50)
dev3   = bandMult * ta.stdev(open_10, 50)
upper3_6 = basis3 + dev3
lower3_6 = basis3 - dev3
uper_10  = (upper3_6 + basis3) / 2
lower_10 = (lower3_6 + basis3) / 2

// v5 spelled this as a 10-term manual average; ta.sma is identical and cheaper.
avg_open  = ta.sma(open, 10)
avg_wma21 = ta.wma(avg_open, 21)

// ═══════════════════════════════════════════════════════════════════════════════
// 17.  PIVOTS (running support / resistance)
// ═══════════════════════════════════════════════════════════════════════════════
point_lb = input.int(4, "Pivot Left Bars",  minval = 1, group = G_SR)
point_rb = input.int(4, "Pivot Right Bars", minval = 1, group = G_SR)
rb       = input.int(5, "Plot Offset Bars", minval = 1, group = G_SR)

point_ph = ta.pivothigh(point_lb, point_rb)
point_pl = ta.pivotlow(point_lb, point_rb)

var float running_ph = na
var float running_pl = na

// v6: int/float no longer auto-cast to bool, so `if point_ph` is a compile
// error. Explicit na test.
if not na(point_ph)
    running_ph := point_ph
if not na(point_pl)
    running_pl := point_pl

plot(running_ph, title = "R", style = plot.style_circles, linewidth = 2, color = color.white,  offset = -rb)
plot(running_pl, title = "S", style = plot.style_circles, linewidth = 2, color = color.purple, offset = -rb)

// ═══════════════════════════════════════════════════════════════════════════════
// 18.  13-MINUTE TREND STATE MACHINE
// ═══════════════════════════════════════════════════════════════════════════════
up10m   = false
up10m_2 = false
down10m   = false
down10m_2 = false

if close_10 + 3 > wma11_10
    up10m_2 := true
if close_10 - 3 < wma11_10
    down10m_2 := true
if close_10 > wma11_10
    up10m   := true
    up10m_2 := false
if close_10 < wma11_10
    down10m   := true
    down10m_2 := false

crossUpWMA13   = ta.crossover(close_13,  wma11_13)
crossDownWMA13 = ta.crossunder(close_13, wma11_13)
crossUpWMA48   = ta.crossover(close_13,  wma48_13)
crossDownWMA48 = ta.crossunder(close_13, wma48_13)
crossUpWMA200   = ta.crossover(close_13,  wma200_13)
crossDownWMA200 = ta.crossunder(close_13, wma200_13)

crossUpWMA13_48   = ta.crossover(wma11_13,  wma48_13)
crossDownWMA13_48 = ta.crossunder(wma11_13, wma48_13)
crossUpWMA48_200   = ta.crossover(wma48_13,  wma200_13)
crossDownWMA48_200 = ta.crossunder(wma48_13, wma200_13)

// Chart-timeframe 11/48 cross (used by the big-reverse filter)
var bool crossup11_48   = false
var bool crossdown11_48 = false
var int  cnt_out_up     = 0
if ta.crossover(wma11_c, wma48_c)
    crossup11_48   := true
    crossdown11_48 := false
    cnt_out_up     := 0
if ta.crossunder(wma11_c, wma48_c)
    crossup11_48   := false
    crossdown11_48 := true
    cnt_out_up     := 0

var bool isUpTrend   = false
var bool isDownTrend = false

if (crossUpWMA13 or crossUpWMA48 or crossUpWMA200) and not isUpTrend
    isUpTrend   := true
    isDownTrend := false
if (crossDownWMA13 or crossDownWMA48 or crossDownWMA200) and not isDownTrend
    isUpTrend   := false
    isDownTrend := true

var int trendUp   = 0
var int trendDown = 0

if isUpTrend
    if crossUpWMA13_48
        trendUp   := 1
        trendDown := 0
    if crossUpWMA48_200
        trendUp   := 2
        trendDown := 0
if isDownTrend
    if crossDownWMA13_48
        trendUp   := 0
        trendDown := 1
    if crossDownWMA48_200
        trendUp   := 0
        trendDown := 2

string txtMarkTrend = ""
string txtOrder     = "N/A"
if isUpTrend
    txtMarkTrend := "Begin Up Trend"
    if trendUp == 1
        txtMarkTrend := "Confirm Strong Uptrend"
        txtOrder     := "BUY"
    else if trendUp == 2
        txtMarkTrend := "STRONGER UPTREND"
        txtOrder     := "BUY"
else if isDownTrend
    txtMarkTrend := "Begin Down Trend"
    // v5 bug: `txtOrder := 'SELL'` was mis-indented one level out, so every
    // down-trend bar reported SELL even before trendDown reached 1 or 2.
    if trendDown == 1
        txtMarkTrend := "Confirm Strong Downtrend"
        txtOrder     := "SELL"
    else if trendDown == 2
        txtMarkTrend := "STRONGER DOWNTREND"
        txtOrder     := "SELL"

// ═══════════════════════════════════════════════════════════════════════════════
// 19.  TOP / BOTTOM EXHAUSTION (WMA200 stretch + MACD)
// ═══════════════════════════════════════════════════════════════════════════════
pctHigh = input.float(10.0, "Highest %", group = G_DIAG)
pctLow  = input.float(-8.0, "Lowest %",  group = G_DIAG)

float pctTop    = 5.0
float pctBottom = -5.0
if timeframe.period == "5"
    pctTop    := 2.5
    pctBottom := -2.5
else if timeframe.period == "180"
    pctTop    := 10.0
    pctBottom := -10.0
else if timeframe.period == "D"
    pctTop    := 15.0
    pctBottom := -15.0

string txtBottom = ""
string txtTop    = ""
bool is_bottom_wma200 = false
bool is_top_wma200    = false
float pct_low_wma200  = 0.0
float pct_high_wma200 = 0.0
if low < wma200_13
    pct_low_wma200 := f_roundUp(f_percent(low - wma200_13, wma200_13), 2)
    if pct_low_wma200 <= pctBottom
        txtBottom := "BOTTOM"
        is_bottom_wma200 := true
else if high > wma200_13
    pct_high_wma200 := f_roundUp(f_percent(high - wma200_13, wma200_13), 2)
    if pct_high_wma200 >= pctTop
        txtTop := "TOP"
        is_top_wma200 := true

is_up_rsi   = rsi - rsi[1] > 5  and ta.crossover(rsi, 40)
is_down_rsi = rsi - rsi[1] < -7 and ta.crossunder(rsi, 70)

bigdrop       = rsi + 8 < rsi[1] and close > wma48_13
bottomsupport = not na(running_pl) and close > running_pl and close > close[1] and rsi > rsi[1] + 10
if bigdrop
    cnt_out_up := cnt_out_up + 1

macd = ta.ema(close, 12) - ta.ema(close, 26)
var bool is_macd_crossover  = false
var bool is_macd_crossunder = false
if ta.crossover(macd, -40)
    is_macd_crossover  := true
    is_macd_crossunder := false
if ta.crossunder(macd, 55)
    is_macd_crossover  := false
    is_macd_crossunder := true

highest48 = ta.highest(high, 48)
lowest48  = ta.lowest(low, 48)
pct_low   = f_roundUp(f_percent(close - highest48, highest48), 2)
pct_high  = f_roundUp(f_percent(close - lowest48,  lowest48),  2)

var bool is_lowest  = false
var bool is_highest = false
if pct_low <= pctLow and close < wma200_13
    if f_roundUp(f_percent(close - wma200_13, wma200_13), 2) <= pctBottom and is_macd_crossover
        is_lowest  := true
        is_highest := false
if close > wma200_13
    is_lowest := false
if pct_high >= pctHigh and close > wma200_13
    if f_roundUp(f_percent(close - wma200_13, wma200_13), 2) >= pctTop and is_macd_crossunder
        is_highest := true
        is_lowest  := false

is_bottom = is_lowest  and bottomsupport
is_top    = is_highest and bigdrop

// ═══════════════════════════════════════════════════════════════════════════════
// 20.  T3-STYLE FILTER  (long / short / profit_long / profit_short)
// ═══════════════════════════════════════════════════════════════════════════════
t3di = (6 - 1.0) / 2.0 + 1.0
t3c1 = 2 / (t3di + 1.0)
t3c2 = 1 - t3c1
t3c3 =  3.0 * (0.4 * 0.4 + 0.4 * 0.4 * 0.4)
t3c4 = -3.0 * (2.0 * 0.4 * 0.4 + 0.4 + 0.4 * 0.4 * 0.4)
t3c5 =  3.0 * 0.4 + 1.0 + 0.4 * 0.4 * 0.4 + 3.0 * 0.4 * 0.4

var float i1 = na
var float i2 = na
var float i3 = na
var float i4 = na
var float i5 = na
var float i6 = na
i1 := t3c1 * close + t3c2 * nz(i1[1])
i2 := t3c1 * i1    + t3c2 * nz(i2[1])
i3 := t3c1 * i2    + t3c2 * nz(i3[1])
i4 := t3c1 * i3    + t3c2 * nz(i4[1])
i5 := t3c1 * i4    + t3c2 * nz(i5[1])
i6 := t3c1 * i5    + t3c2 * nz(i6[1])
cto = -0.4 * 0.4 * 0.4 * i6 + t3c3 * i5 + t3c4 * i4 + t3c5 * i3

long  = (close > cto and close[1] < cto[1] and close > close[1]) or (close[1] > cto[1] and close > close[1] and close[1] < close[2] and close > ema3)
short = (close < cto and close[1] > cto[1] and close < close[1]) or (close[1] < cto[1] and close < close[1] and close[1] > close[2] and close < ema3)
profit_long  = close < close[1] and low  < ema3 and close[1] > ema3[1] and close[2] > ema3[2]
profit_short = close > close[1] and high > ema3 and close[1] < ema3[1] and close[2] < ema3[2]

// ═══════════════════════════════════════════════════════════════════════════════
// 21.  CANDLE PATTERNS
// ═══════════════════════════════════════════════════════════════════════════════
gain1 = math.abs(close - close[1])
gain2 = math.abs(close[1] - close[2])
gainRatio = f_div(gain1 - gain2, gain2)          // v5 divided by gain2 unguarded
bodyPos   = f_div(close - low, high - low)       // v5 divided by (high-low) unguarded

pattern_call = close[1] < close[2] and close[2] < close[3] and low <= low[1] and close >= close[1] and not na(gainRatio) and gainRatio >= 0.50 and wma11_13 > wma48_13 and not na(bodyPos) and bodyPos > 0.70
// (high - low) - (high - close) == close - low, so v5's second branch is just
// bodyPos < 0.25.
pattern_put  = (close[1] > close[2] and close[2] > close[3] and high <= high[1] and close <= close[1] and not na(gainRatio) and gainRatio <= -0.5) or (not na(bodyPos) and bodyPos < 0.25 and wma11_13 < wma48_13)

var int cnt_put = 0
if pattern_put
    cnt_put := cnt_put + 1
if pattern_put and cnt_put == 1
    cnt_out_up := cnt_out_up + 1
if bottomsupport
    cnt_put := 0

call_100 = pattern_call and long
put_100  = pattern_put and cnt_put == 1 and short

// Big reverse
oc   = math.abs(open[1] - close[1])
hl   = math.abs(high[1] - low[1])
ochl = nz(f_div(oc - hl, hl), 1.0)
is_br = open[2] < close[2] and open[1] < close[1] and close < open and close[2] < close[1] and close[1] > close and ochl < 0.1 and low[3] < low[2] and low[2] < low[1]
if is_br
    cnt_out_up := cnt_out_up + 1

is_out_up = false
if cnt_out_up > 2
    is_out_up  := true
    cnt_out_up := 0

// ═══════════════════════════════════════════════════════════════════════════════
// 22.  CANDLE COLORS
// ═══════════════════════════════════════════════════════════════════════════════
showCandleColors = input.bool(true, "Enable Candle Colors", group = G_BAR)
barColorMode     = input.string("Code 2", "Bar Color Source", options = ["Code 2", "T3 Trend"], group = G_BAR, tooltip = "'T3 Trend' uses the long/short/profit colouring that v5 computed but never applied to barcolor().")
minBodyIntraday  = input.float(3.0, "Intraday Strong-Body Points", step = 0.25, group = G_BAR)
minBodyHTF       = input.float(6.0, "Higher-TF Strong-Body Points", step = 0.25, group = G_BAR)

vol  = volume
bull = close > open ? vol : 0
bear = open > close ? vol : 0
vf_dif = ta.wma(bull, 14) - ta.wma(bear, 14)

// -- T3 trend colours (previously dead code) --
var int trend = 0
if long
    trend := 1
if short
    trend := -1
if profit_long
    trend := 2
if profit_short
    trend := -2

color clr_trend = na
var string colors = na
if trend == 1
    clr_trend := vf_dif > 0 ? color.rgb(67, 221, 87) : na
    colors := "G"
else if trend == 2
    clr_trend := vf_dif > 0 ? color.rgb(252, 139, 74) : color.red
    colors := "R"
else if trend == -1
    clr_trend := vf_dif > 0 ? color.rgb(252, 139, 74) : color.rgb(228, 201, 170)
    colors := "O"
else if trend == -2
    clr_trend := vf_dif > 0 ? color.yellow : color.rgb(0, 0, 255)
    colors := "B"

var string trend_up_down = ""
if (colors[2] == "R" or colors[2] == "O" or colors[1] == "R" or colors[1] == "O") and (colors == "B" or colors == "G") and close > close[1]
    trend_up_down := "UP"
if (colors[2] == "B" or colors[2] == "G" or colors[1] == "B" or colors[1] == "G") and (colors == "O" or colors == "R") and close < close[1]
    trend_up_down := "DN"

trendFlipUp = trend_up_down == "UP" and trend_up_down[1] != "UP"
trendFlipDn = trend_up_down == "DN" and trend_up_down[1] != "DN"

// -- "Code 2" colours --
isIntradayPreset = timeframe.period == "1" or timeframe.period == "2" or timeframe.period == "5" or timeframe.period == "10" or timeframe.period == "15" or timeframe.period == "30"

color clrbar = na
bool is_up_candle   = false
bool is_down_candle = false

if isIntradayPreset
    if (close - open) > minBodyIntraday
        clrbar := color.rgb(40, 33, 243)
        is_up_candle := true
    if vol > vol[1] and close > ((high + low) * 0.50)
        clrbar := color.rgb(102, 255, 0)
        is_up_candle := true
    if open >= close[1] and (close[1] - open[1] >= -1.0) and (close - open) >= 1.00
        clrbar := color.rgb(2, 182, 2)
        is_up_candle := true
    if ta.crossover(close, wma200_c)
        clrbar := color.rgb(102, 255, 0)
        is_up_candle := true
    if up10m and not up10m[1]
        clrbar := color.rgb(254, 254, 252)
        is_up_candle := true
    if (close - open) < -minBodyIntraday or ta.crossunder(close, wma200_c)
        clrbar := color.rgb(255, 111, 0)
        is_down_candle := true
    if ((close[1] - open[1] >= 1.75) and (close - open) <= -0.50) or (high[1] >= high[2] and close <= low[1])
        clrbar := color.rgb(206, 0, 0)
        is_down_candle := true
    if down10m and not down10m[1]
        clrbar := color.rgb(4, 249, 216)
        is_down_candle := true
else
    if (close - open) >= minBodyHTF
        clrbar := color.rgb(40, 33, 243)
        is_up_candle := true
    if (close - open) <= -minBodyHTF
        clrbar := color.rgb(255, 111, 0)
        is_down_candle := true

barcolor(showCandleColors ? (barColorMode == "T3 Trend" ? clr_trend : clrbar) : na)

// White candle = the 10-minute up-transition candle
isWhiteCandle = showCandleColors and up10m and not up10m[1]

// ═══════════════════════════════════════════════════════════════════════════════
// 23.  EARLY CALL / PUT (first 45 minutes) + 10:15 → 10:55 SIGNALS
// ═══════════════════════════════════════════════════════════════════════════════
if hasOpen and inFirst45 and not strongLocked and barstate.isconfirmed
    if close >= up15 and ema18Up
        callStrong   := true
        putStrong    := false
        strongLocked := true
    if close <= dn15 and ema18Dn
        putStrong    := true
        callStrong   := false
        strongLocked := true
        dirLabel     := label.new(bar_index, high, "PUT", style = label.style_arrowdown, color = color.red, textcolor = color.white, size = size.huge)

// Green CALL arrow only on a Code-2 white candle
if callStrong and isWhiteCandle and na(dirLabel) and barstate.isconfirmed
    dirLabel := label.new(bar_index, low, "CALL", style = label.style_arrowup, color = color.lime, textcolor = color.white, size = size.huge)

plotshape(ta.crossunder(close, ema50) and barstate.isconfirmed, title = "EMA50 Down", style = shape.triangledown, location = location.abovebar, color = color.red, size = size.large)
plotshape(ta.crossover(close, ema50) and barstate.isconfirmed and isWhiteCandle, title = "EMA50 Up - White Candle Only", style = shape.triangleup, location = location.belowbar, color = color.green, size = size.large)

if post1015 and barstate.isconfirmed and not callNow and not putNow and not na(lowPre1015)
    if low <= lowPre1015
        callNow := true
        label.new(bar_index, low, "CALL NOW", style = label.style_label_up, color = color.lime, textcolor = color.white, size = size.large)

if post1015 and barstate.isconfirmed and not putNow and not callNow and not na(highPre1015) and not isWhiteCandle
    if high < highPre1015
        putNow := true
        label.new(bar_index, high, "PUT NOW", style = label.style_label_down, color = color.red, textcolor = color.white, size = size.large)

// ═══════════════════════════════════════════════════════════════════════════════
// 24.  ABSOLUTE ENTRY (midnight box edge rejection)
// ═══════════════════════════════════════════════════════════════════════════════
boxReady = not na(boxHigh) and not na(boxLow) and not na(boxMid)
rng = high - low
bullBody = close > open
bearBody = close < open
bullRejection = rng > 0 and close > low + rng * 0.55
bearRejection = rng > 0 and close < low + rng * 0.45

touchBoxLow  = boxReady and low  <= (boxLow  + entryTol)
touchBoxHigh = boxReady and high >= (boxHigh - entryTol)

absCallEntry = boxReady and touchBoxLow  and close > boxLow  and bullBody and bullRejection
absPutEntry  = boxReady and touchBoxHigh and close < boxHigh and bearBody and bearRejection

if absCallEntry and barstate.isconfirmed
    absCallFired := true
    absPutFired  := false
    absCallLevel := low
    absPutLevel  := na
    if not na(absEntryLabel)
        label.delete(absEntryLabel)
    absEntryLabel := label.new(bar_index, low, "ABS CALL", style = label.style_label_up, color = color.lime, textcolor = color.white, size = size.large)

if absPutEntry and barstate.isconfirmed
    absPutFired  := true
    absCallFired := false
    absPutLevel  := high
    absCallLevel := na
    if not na(absEntryLabel)
        label.delete(absEntryLabel)
    absEntryLabel := label.new(bar_index, high, "ABS PUT", style = label.style_label_down, color = color.red, textcolor = color.white, size = size.large)

absCallActive = absCallFired and not na(boxLow)  and close >= boxLow
absPutActive  = absPutFired  and not na(boxHigh) and close <= boxHigh

if absCallFired and not na(boxLow) and close < boxLow
    absCallFired := false
    absCallLevel := na
if absPutFired and not na(boxHigh) and close > boxHigh
    absPutFired := false
    absPutLevel := na

// v5 plotted an ABS dot on EVERY bar that met the entry condition, which is why
// the chart fills with overlapping "ABS" circles. Now it prints once per new
// signal, i.e. only when the state actually flips.
absCallSignal = absCallEntry and barstate.isconfirmed and not absCallFired[1]
absPutSignal  = absPutEntry  and barstate.isconfirmed and not absPutFired[1]
plotshape(absCallSignal, title = "Absolute Call Entry", style = shape.circle, location = location.belowbar, color = color.lime, size = size.small, text = "ABS")
plotshape(absPutSignal,  title = "Absolute Put Entry",  style = shape.circle, location = location.abovebar, color = color.red,  size = size.small, text = "ABS")

// ═══════════════════════════════════════════════════════════════════════════════
// 25.  SNAG CALL / PUT ENGINE
// ═══════════════════════════════════════════════════════════════════════════════
strongBodyPts = input.float(6.0,  "Strong Entry Body Points",   step = 0.25, group = G_ENG)
stopPts       = input.float(5.0,  "Trailing Stop Points",       step = 0.25, group = G_ENG)
beginPutBody  = input.float(3.25, "Begin-Put Body Points",      step = 0.25, group = G_ENG)

m = minute(time_close)

var int cnt_call_10m = 0
var int cnt_put_10m  = 0
if wma11_c > wma48_c and close > wma11_c and up10m and close > close[1]
    cnt_call_10m := cnt_call_10m + 1
    cnt_put_10m  := 0
if wma11_c < wma48_c and close < wma48_c and down10m and close < close[1]
    cnt_call_10m := 0
    cnt_put_10m  := cnt_put_10m + 1

is_begin_call = bottomsupport and (close > wma48_13 or close > wma200_13)
is_begin_put  = (bigdrop and (close < wma48_13 or close < wma200_13)) or (ta.crossunder(close, wma48_13) and ta.crossunder(close, wma200_13)) or ta.crossunder(close, wma200_13) or (open - close) >= beginPutBody

// --- CALL side ---
var float call_price      = 0.0
var float last_call_price = 0.0
var float stoploss_call   = 0.0

bool call_ok      = false
bool call_weak    = false
bool call_lite    = false
bool call_confirm = false

// v5 read `(call_price == 0.0 and is_begin_call) or cnt_call_10m == 1` because
// `and` binds tighter than `or`, so an open CALL was silently re-entered (and
// its trailing stop reset) every time cnt_call_10m hit 1. Parenthesised.
if call_price == 0.0 and (is_begin_call or cnt_call_10m == 1)
    if (close - open) >= strongBodyPts or close > uper_10
        call_price      := close
        last_call_price := close
        stoploss_call   := close - stopPts
        if close > uper_10
            call_ok := true
        else
            call_weak := true
    else
        if ((m - 1) % 5 == 0 or m % 5 == 0 or (m + 1) % 5 == 0) and close > wma11_c
            call_lite := true

if call_price != 0.0 and ta.crossover(close, uper_10)
    if not ((is_begin_call or cnt_call_10m == 1) and close > uper_10)
        call_confirm := true

if call_price != 0.0 and last_call_price < close
    last_call_price := close
    stoploss_call   := close - stopPts

bool exit_call = false
if call_price != 0.0 and (bigdrop or ta.crossunder(close, lower_10) or close < stoploss_call)
    exit_call  := true
    call_price := 0.0

// --- PUT side ---
put_res = false
if timeframe.period == "5" and close[6] < close[5] and not na(running_ph) and running_ph >= close[5] and running_ph >= close[4] and running_ph >= close[3] and running_ph >= close[2] and running_ph >= close[1] and close < close[1] and close[1] < close[2] and uper_10[6] < uper_10[5] and lower_10[6] < lower_10[5] and (ta.crossunder(close, uper_10) or ta.crossunder(close[3], uper_10) or ta.crossunder(close, avg_wma21))
    put_res := true
if timeframe.period == "1" and close[5] < close[4] and close[4] < close[3] and not na(running_ph) and running_ph >= close[3] and running_ph >= close[2] and running_ph >= close[1] and close < close[1] and close[1] < close[2] and uper_10[5] < uper_10[4] and lower_10[5] < lower_10[4] and close < avg_wma21
    put_res := true

ready_strong_put = put_res

var float put_price      = 0.0
var float last_put_price = 0.0
var float stoploss_put   = 0.0

bool put_strong = false
bool put_lite   = false

// Symmetry fix: v5 had no `put_price == 0.0` guard here, so an open PUT kept
// re-entering and resetting its own trailing stop on every qualifying bar.
if put_price == 0.0 and (is_begin_put or cnt_put_10m == 1)
    if (open - close) >= strongBodyPts and close < lower_10
        put_price      := close
        last_put_price := close
        stoploss_put   := close + stopPts
        put_strong     := true
    else
        if ((m - 1) % 5 == 0 or m % 5 == 0 or (m + 1) % 5 == 0) and close < wma11_c
            put_lite := true

if put_price != 0.0 and last_put_price > close
    last_put_price := close
    stoploss_put   := close + stopPts

bool exit_put = false
if put_price != 0.0 and (bottomsupport or stoploss_put < close)
    exit_put  := true
    put_price := 0.0

// --- Research signals that v5 computed but never plotted ---
beginput      = high > wma48_10 and close < wma48_10 and rsi < 50 and rsi < rsi[1] + 3
beginput2     = rsi < rsi[1] and rsi[1] < rsi[2] and close < wma11_c - 1
beginputout   = bigdrop or is_out_up
rsima200support  = low < wma200_c and close > wma200_c and rsi > rsi[1] + 5
rsima200support2 = low < wma48_c  and close > wma48_c  and rsi > rsi[1] + 5
rsima200drop     = high > wma200_c and close < wma200_c and rsi > rsi[1] - 6 and rsi < rsi[1] + 3
rsima200drop2    = high > wma48_c  and close < wma48_c  and rsi > rsi[1] - 6 and rsi < rsi[1] + 3
down3below11 = close[2] < wma11_c and close[1] < wma11_c and close < wma11_c

buyacall = (up10m or is_up_rsi) and (rsima200support or rsima200support2)
buyaput  = (beginputout and is_down_rsi and down3below11) or (beginput and is_down_rsi and down3below11) or (down3below11 and is_down_rsi and beginput2) or (down3below11 and rsima200drop) or rsima200drop2

// ═══════════════════════════════════════════════════════════════════════════════
// 26.  DIAGNOSTIC MARKS  (all were commented out in v5 — now switchable)
// ═══════════════════════════════════════════════════════════════════════════════
showEngineMarks  = input.bool(false, "Engine marks (C-OK / C-Weak / CL / EX-C / P-Strong / PL / EX-P)", group = G_DIAG)
showPatternMarks = input.bool(false, "Pattern marks (100% Call/Put, Big Drop, Big Bottom, BR, OUT)", group = G_DIAG)
showExhaustion   = input.bool(false, "Exhaustion marks (TOP / BOTTOM diamonds, R-U / R-D)", group = G_DIAG)
showBuyaMarks    = input.bool(false, "Buy-a-Call / Buy-a-Put marks", group = G_DIAG)

plotshape(showEngineMarks and call_ok,      title = "C-OK",      text = "C-OK",      style = shape.labelup,   location = location.belowbar, color = color.new(#088F8F, 30), textcolor = color.white, size = size.tiny)
plotshape(showEngineMarks and call_weak,    title = "C-Weak",    text = "C-Weak",    style = shape.labelup,   location = location.belowbar, color = color.new(#454B1B, 30), textcolor = color.white, size = size.tiny)
plotshape(showEngineMarks and call_lite,    title = "C-Lite",    text = "CL",        style = shape.labelup,   location = location.belowbar, color = color.gray,             textcolor = color.white, size = size.tiny)
plotshape(showEngineMarks and call_confirm, title = "C-Confirm", text = "C-Conf",    style = shape.labelup,   location = location.belowbar, color = color.new(#088F8F, 0),  textcolor = color.white, size = size.tiny)
plotshape(showEngineMarks and exit_call,    title = "EX-C",      text = "EX-C",      style = shape.labeldown, location = location.abovebar, color = color.new(#023020, 0),  textcolor = color.white, size = size.tiny)
plotshape(showEngineMarks and put_strong,   title = "P-Strong",  text = "P-Strong",  style = shape.labeldown, location = location.abovebar, color = color.new(#EC5800, 30), textcolor = color.white, size = size.tiny)
plotshape(showEngineMarks and put_lite,     title = "P-Lite",    text = "PL",        style = shape.labeldown, location = location.abovebar, color = color.gray,             textcolor = color.white, size = size.tiny)
plotshape(showEngineMarks and exit_put,     title = "EX-P",      text = "EX-P",      style = shape.labelup,   location = location.belowbar, color = color.new(#093020, 0),  textcolor = color.white, size = size.tiny)
plotshape(showEngineMarks and ready_strong_put, title = "RSP",   text = "RSP",       style = shape.labeldown, location = location.abovebar, color = color.yellow,           textcolor = color.black, size = size.tiny)

plotshape(showPatternMarks and call_100,      title = "CALL 100", text = "C100", style = shape.labelup,   location = location.belowbar, color = color.lime,   textcolor = color.black, size = size.tiny)
plotshape(showPatternMarks and put_100,       title = "PUT 100",  text = "P100", style = shape.labeldown, location = location.abovebar, color = color.red,    textcolor = color.white, size = size.tiny)
plotshape(showPatternMarks and bigdrop,       title = "Big Drop",   style = shape.square, location = location.abovebar, color = color.white,  size = size.tiny)
plotshape(showPatternMarks and bottomsupport, title = "Big Bottom", style = shape.xcross, location = location.belowbar, color = color.yellow, size = size.tiny)
plotshape(showPatternMarks and is_br and not crossup11_48, title = "BR",  text = "BR",  style = shape.triangledown, location = location.abovebar, color = color.red, textcolor = color.white, size = size.tiny)
plotshape(showPatternMarks and is_out_up,     title = "OUT", text = "OUT", style = shape.triangledown, location = location.abovebar, color = color.red, textcolor = color.white, size = size.tiny)

plotshape(showExhaustion and is_bottom,   title = "Bottom", style = shape.diamond, location = location.belowbar, color = color.yellow, size = size.tiny)
plotshape(showExhaustion and is_top,      title = "TOP",    style = shape.diamond, location = location.abovebar, color = color.white,  size = size.tiny)
plotshape(showExhaustion and is_up_rsi,   title = "R-U", text = "R-U", style = shape.labelup,   location = location.belowbar, color = color.green, textcolor = color.white, size = size.tiny)
plotshape(showExhaustion and is_down_rsi, title = "R-D", text = "R-D", style = shape.labeldown, location = location.abovebar, color = color.red,   textcolor = color.white, size = size.tiny)

plotshape(showBuyaMarks and buyacall, title = "Buy a Call", text = "BC", style = shape.labelup,   location = location.belowbar, color = color.new(color.green, 20), textcolor = color.white, size = size.tiny)
plotshape(showBuyaMarks and buyaput,  title = "Buy a Put",  text = "BP", style = shape.labeldown, location = location.abovebar, color = color.new(color.red, 20),   textcolor = color.white, size = size.tiny)

// ═══════════════════════════════════════════════════════════════════════════════
// 27.  PIVOT S/R LINES + CROSS ANNOTATIONS
//      v5 rebuilt six lines and six labels on EVERY bar (line.new + delete of
//      the previous id) and never deleted the U-/D- cross labels at all, so the
//      500-label budget filled up on an active day. Now: persistent objects
//      updated in place, cross labels emitted once per confirmed cross.
// ═══════════════════════════════════════════════════════════════════════════════
showSR     = input.bool(true, "Show Pivot S/R Lines", group = G_SR)
line_len   = 10
line_start = 4
line_len2  = line_len / 2 + 2

float h1_val = 0.0
float h2_val = 0.0
float h3_val = 0.0
float l1_val = 0.0
float l2_val = 0.0
float l3_val = 0.0

if barstate.islast
    for bk = 0 to 60
        float ph = running_ph[bk]
        float pl = running_pl[bk]
        if not na(ph)
            if h1_val == 0.0
                h1_val := ph
            else if h2_val == 0.0 and math.abs(ph - h1_val) > 3
                h2_val := ph
            else if h3_val == 0.0 and h2_val != 0.0 and math.abs(ph - h2_val) > 3 and math.abs(ph - h1_val) > 3
                h3_val := ph
        if not na(pl)
            if l1_val == 0.0
                l1_val := pl
            else if l2_val == 0.0 and math.abs(pl - l1_val) > 3
                l2_val := pl
            else if l3_val == 0.0 and l2_val != 0.0 and math.abs(pl - l2_val) > 3 and math.abs(pl - l1_val) > 3
                l3_val := pl

// v5 bug: the S2 label printed l3_val, so S2 and S3 always showed the same
// number on the chart (visible in the screenshot: "S2: 7,819.25 / S3: 7,819.25").
// Fixed by indexing the levels here.
srVals = array.from(h1_val, h2_val, h3_val, l1_val, l2_val, l3_val)
var srTags = array.from("R1", "R2", "R3", "S1", "S2", "S3")

var array<line>  srLines  = array.new<line>(6, na)
var array<label> srLabels = array.new<label>(6, na)

if barstate.islast and showSR
    for k = 0 to 5
        float lvl = array.get(srVals, k)
        if lvl != 0.0 and not na(lvl)
            string tag = array.get(srTags, k)
            color  col = k < 3 ? color.white : color.blue
            line  ln = array.get(srLines, k)
            label lb = array.get(srLabels, k)
            if na(ln)
                array.set(srLines, k, line.new(bar_index + line_start, lvl, bar_index + line_len, lvl, width = 2, color = col))
            else
                line.set_xy1(ln, bar_index + line_start, lvl)
                line.set_xy2(ln, bar_index + line_len, lvl)
            if na(lb)
                array.set(srLabels, k, label.new(bar_index + line_len2, lvl + 1, tag + ": " + str.tostring(lvl, format.mintick), style = label.style_text_outline, color = color.new(#454B1B, 50), textcolor = color.white))
            else
                label.set_xy(lb, bar_index + line_len2, lvl + 1)
                label.set_text(lb, tag + ": " + str.tostring(lvl, format.mintick))

// Level-cross annotations. These only ever fire on the live bar (h1..l3 are
// computed on barstate.islast only) — same as v5 — but they are now gated on a
// confirmed bar so one cross produces exactly one label instead of one per tick.
float maxCrossUp = 0.0
float maxCrossDn = 0.0

if showSR and barstate.isconfirmed
    for k = 0 to 5
        float lvl = array.get(srVals, k)
        if lvl != 0.0 and not na(lvl)
            string tag = array.get(srTags, k)
            float  pts = math.abs(close - open)
            if (open < lvl or close[1] < lvl) and close > lvl and pts > 0
                maxCrossUp := math.max(maxCrossUp, pts)
                label.new(bar_index + 6, lvl, "U-" + tag + ": " + str.tostring(pts, "#.##"), style = label.style_text_outline, color = color.new(#454B1B, 50), textcolor = color.yellow)
            if (open > lvl or close[1] > lvl) and close < lvl and pts > 0
                maxCrossDn := math.max(maxCrossDn, pts)
                label.new(bar_index + 6, lvl, "D-" + tag + ": " + str.tostring(pts, "#.##"), style = label.style_text_outline, color = color.new(#454B1B, 50), textcolor = color.red)

strongSRCrossUp = maxCrossUp >= cross_up_point
strongSRCrossDn = maxCrossDn >= cross_down_point

// ═══════════════════════════════════════════════════════════════════════════════
// 28.  RIGHT-SIDE STATUS PANEL
// ═══════════════════════════════════════════════════════════════════════════════
string strongDayText = "INSIDE BOX"
color  strongDayBg   = color.gray

if not na(boxHigh) and close > boxHigh
    strongDayText := "CALL STRONG DAY"
    strongDayBg   := color.green
else if not na(boxLow) and close < boxLow
    strongDayText := "PUT STRONG DAY"
    strongDayBg   := color.red
else if absCallActive
    strongDayText := "ABS CALL ACTIVE"
    strongDayBg   := color.lime
else if absPutActive
    strongDayText := "ABS PUT ACTIVE"
    strongDayBg   := color.red

// v5 rewrote this cell on every historical bar; only the last bar is visible.
// Rows 2 and 3 surface txtMarkTrend / txtOrder and the WMA200 stretch state,
// all of which v5 computed and then discarded.
string trendRow = txtMarkTrend == "" ? "NO TREND  |  N/A" : txtMarkTrend + "  |  " + txtOrder
color  trendBg  = txtOrder == "BUY" ? color.new(color.green, 30) : txtOrder == "SELL" ? color.new(color.red, 30) : color.new(color.gray, 30)

string stretchRow = is_top_wma200 ? "TOP  " + str.tostring(pct_high_wma200, "#.##") + "% over WMA200" : is_bottom_wma200 ? "BOTTOM  " + str.tostring(pct_low_wma200, "#.##") + "% under WMA200" : "WMA200 stretch: normal"
color  stretchBg  = is_top_wma200 ? color.new(color.orange, 30) : is_bottom_wma200 ? color.new(color.aqua, 30) : color.new(color.gray, 60)

if barstate.islast
    table.cell(rightPanel, 0, 0, strongDayText, text_color = color.white, bgcolor = strongDayBg, text_size = size.large)
    table.cell(rightPanel, 0, 1, trendRow,      text_color = color.white, bgcolor = trendBg,     text_size = size.normal)
    table.cell(rightPanel, 0, 2, stretchRow,    text_color = color.white, bgcolor = stretchBg,   text_size = size.normal)

// ═══════════════════════════════════════════════════════════════════════════════
// 29.  SESSION SNAPSHOTS + WEBHOOK ALERT
// ═══════════════════════════════════════════════════════════════════════════════
enableWebhook = input.bool(true, "Send webhook JSON alert()", group = G_ALERT)
alertTicker   = input.string("SPX", "Ticker field in the JSON payload", group = G_ALERT)

var float wma200_last       = na
var float open200_6h30      = na
var float wma200_yesterday  = na
var float close200_yesterday = na
var float open15_6h30       = na
var float open15_6h45       = na

if hCls == 13 and mCls == 0
    wma200_yesterday   := wma200_2
    close200_yesterday := close_1d

if hCls == 6 and mCls == 30
    wma200_last  := wma200_2
    open200_6h30 := close
    open15_6h30  := open_15

if hCls == 6 and mCls <= 45
    open15_6h45 := close_15

wma200_diff   = close_2  - wma200_2
wma200_diff15 = close_15 - wma200_15
change_15m    = open15_6h45 - open15_6h30

if enableWebhook and barstate.isconfirmed
    alert('{"wma200":' + str.tostring(wma200_2) + ',"ticker":"' + alertTicker + '","wma200_15":' + str.tostring(wma200_15) + ',"wma200[1]":' + str.tostring(wma200_2[1]) + ',"wma200[2]":' + str.tostring(wma200_2[2]) + ',"close15":' + str.tostring(close_15) + ',"change_15m":' + str.tostring(change_15m) + ',"open200_6h30":' + str.tostring(open200_6h30) + ',"wma200_last":' + str.tostring(wma200_last) + ',"rsi":' + str.tostring(rsi_2) + ',"rsi15":' + str.tostring(rsi_15) + ',"wma200_diff":' + str.tostring(wma200_diff) + ',"wma200_diff15":' + str.tostring(wma200_diff15) + ',"current_price":' + str.tostring(close) + ',"open15_6h45":' + str.tostring(open15_6h45) + ',"close200_yesterday":' + str.tostring(close200_yesterday) + ',"change":' + str.tostring(close_2 - close200_yesterday) + '}', alert.freq_once_per_bar)

// ═══════════════════════════════════════════════════════════════════════════════
// 30.  OPENING-WINDOW CALL / PUT MARKER  (06:00 → 07:15)
//      v5 kept two int arrays only to remember one timestamp each. Same
//      behaviour, one variable each.
// ═══════════════════════════════════════════════════════════════════════════════
var int callSignalTime = na
var int putSignalTime  = na

if hCls >= 6 and (hCls < 7 or (hCls == 7 and mCls <= 15))
    if close_15 > close_15[1] and close_15 > wma200_15
        putSignalTime := na
        if na(callSignalTime)
            callSignalTime := time
    if close_15 < close_15[1] and close_15 < wma200_15
        callSignalTime := na
        if na(putSignalTime)
            putSignalTime := time

is_call = not na(callSignalTime) and time == callSignalTime
is_put  = not na(putSignalTime)  and time == putSignalTime

plotshape(is_call ? visLow     : na, title = "CALL", text = "CALL", style = shape.triangleup,   location = location.absolute, color = color.green, size = size.normal, textcolor = color.white)
plotshape(is_put  ? highest_1d : na, title = "PUT",  text = "PUT",  style = shape.triangledown, location = location.absolute, color = color.red,   size = size.normal, textcolor = color.white)

// ═══════════════════════════════════════════════════════════════════════════════
// 31.  WMA200 DISTANCE EXTREMES (±40 points)
// ═══════════════════════════════════════════════════════════════════════════════
diffPoints   = input.float(40.0, "WMA200 Distance Trigger (points)", step = 1.0, group = G_DIAG)
ema_diff_200 = close - wma200_c

var int call2SignalTime = na
var int put2SignalTime  = na

if ema_diff_200 < -diffPoints
    put2SignalTime := na
    if na(call2SignalTime)
        call2SignalTime := time
if ema_diff_200 > diffPoints
    call2SignalTime := na
    if na(put2SignalTime)
        put2SignalTime := time

is_call2 = not na(call2SignalTime) and time == call2SignalTime
is_put2  = not na(put2SignalTime)  and time == put2SignalTime

plotshape(is_call2 ? visLow     : na, title = "Diff below -N", text = "Diff <-40p", style = shape.labelup,   location = location.absolute, color = color.blue,  size = size.normal, textcolor = color.white)
plotshape(is_put2  ? highest_1d : na, title = "Diff above +N", text = "Diff >40p",  style = shape.labeldown, location = location.absolute, color = color.white, size = size.normal, textcolor = color.black)

// ═══════════════════════════════════════════════════════════════════════════════
// 32.  ALERT CONDITIONS
//      v5 computed a dozen is_alert_* flags and never exposed a single one.
// ═══════════════════════════════════════════════════════════════════════════════
alertcondition(absCallSignal,   "ABS CALL entry",        "ABS CALL entry at the midnight box low")
alertcondition(absPutSignal,    "ABS PUT entry",         "ABS PUT entry at the midnight box high")
alertcondition(call_ok,         "Snag C-OK",             "Snag strong CALL entry (above upper band)")
alertcondition(call_weak,       "Snag C-Weak",           "Snag weak CALL entry")
alertcondition(call_lite,       "Snag C-Lite",           "Snag lite CALL entry")
alertcondition(call_confirm,    "Snag C-Confirm",        "Snag CALL confirmed by upper band cross")
alertcondition(exit_call,       "Snag exit CALL",        "Snag CALL exit (stop / band cross / big drop)")
alertcondition(put_strong,      "Snag P-Strong",         "Snag strong PUT entry (below lower band)")
alertcondition(put_lite,        "Snag P-Lite",           "Snag lite PUT entry")
alertcondition(exit_put,        "Snag exit PUT",         "Snag PUT exit (stop / bottom support)")
alertcondition(ready_strong_put,"Snag ready strong PUT", "Snag ready-strong-PUT setup")
alertcondition(almaBuy,         "ALMA/GAMA buy",         "ALMA crossed above the Gaussian adaptive MA")
alertcondition(almaSell,        "ALMA/GAMA sell",        "ALMA crossed below the Gaussian adaptive MA")
alertcondition(trendFlipUp,     "Colour trend flip UP",  "Bar colour sequence flipped bearish to bullish")
alertcondition(trendFlipDn,     "Colour trend flip DOWN","Bar colour sequence flipped bullish to bearish")
alertcondition(strongSRCrossUp, "Strong S/R cross up",   "Price crossed a pivot level upward with a large body")
alertcondition(strongSRCrossDn, "Strong S/R cross down", "Price crossed a pivot level downward with a large body")
alertcondition(is_bottom,       "Exhaustion bottom",     "WMA200 stretch bottom with MACD reset")
alertcondition(is_top,          "Exhaustion top",        "WMA200 stretch top with MACD reset")
alertcondition(buyacall,        "Buy a Call",            "RSI + WMA200/48 reclaim")
alertcondition(buyaput,         "Buy a Put",             "RSI + WMA200/48 loss")

// ═══════════════════════════════════════════════════════════════════════════════
//  CHANGELOG  v5 → v6
// ═══════════════════════════════════════════════════════════════════════════════
//  LANGUAGE / v6 COMPLIANCE
//   1.  //@version=6.
//   2.  v6 removed automatic int/float → bool casting. `if point_ph`,
//       `if point_pl`, `running_pl and ...`, `running_ph and ...` were all
//       compile errors; replaced with explicit `not na(...)` tests.
//   3.  Dropped all three library imports. PineCoders/VisibleChart was
//       imported twice (v4 unaliased + v2 as pcVC) and PineCoders/Time was
//       never referenced. The only thing actually used — VisibleChart.low() —
//       is now four lines of native chart.left/right_visible_bar_time code.
//   4.  `str.format("{0}", timeframe.period) == "5"` → `timeframe.period == "5"`
//       (13 occurrences).
//   5.  `input.float()` was declared inside a ternary branch in the GAMA
//       block; hoisted to top level.
//   6.  The ALMA `offset` global was shadowed by the `for offset = 0 to 60`
//       loop counter in the S/R block; renamed.
//
//  REPAINTING / LOOK-AHEAD  (the reason this repo exists)
//   7.  Every intraday request.security() used lookahead_on with no history
//       offset, i.e. it read higher-timeframe bars that had not closed yet.
//       All 13m/15m/10m/2m requests now go through f_htf(), which uses
//       lookahead_off, so a chart bar only sees HTF data that existed at that
//       moment. "Strict HTF" additionally drops the forming HTF bar.
//       EXPECT DIFFERENT HISTORICAL SIGNALS — the old ones were not tradable.
//   8.  close_1d requested daily `close` (not `close[1]`) with lookahead_on,
//       so "close200_yesterday" was today's unknown settlement. Fixed.
//   9.  Session windows mixed America/Los_Angeles, "UTC-8" and "UTC-7", so
//       roughly half the day's logic was an hour off during DST. One
//       timezone input drives everything now.
//
//  LOGIC BUGS
//  10.  S2 label printed l3_val — S2 and S3 always showed the same price on
//       the chart. Fixed to l2_val.
//  11.  `if (call_price == 0.0 and is_begin_call or cnt_call_10m == 1)`
//       parses as `(A and B) or C`, so an open CALL was re-entered and its
//       trailing stop reset whenever cnt_call_10m hit 1. Parenthesised, and
//       the same guard added to the PUT side for symmetry.
//  12.  `txtOrder := 'SELL'` was indented one level too far out, so it fired
//       for every down-trend bar rather than only for a confirmed one.
//  13.  Unguarded divisions by gain2, (high-low), sumOfWeights, stdev and
//       src_sm could produce inf/na and silently disable patterns. All go
//       through f_div()/f_percent() now, and GAMA's sigma has a floor.
//  14.  The S/R scan assigned na into h1_val/l1_val when the pivot buffer was
//       empty, which then poisoned every `!= 0.0` test downstream.
//  15.  Open-reset deleted its line objects but left the ids non-na, so later
//       `if not na(openLine)` guards operated on dead drawings.
//
//  DRAWING BUDGET / PERFORMANCE
//  16.  ABS entry dots printed on every qualifying bar (the "ABS" pile-up on
//       the chart). Now one dot per state change.
//  17.  U-R1..D-S3 cross labels were never deleted and were re-created on
//       every real-time tick — the 500-label budget filled up on an active
//       day. Now one label per confirmed cross.
//  18.  Six S/R lines and six S/R labels were destroyed and rebuilt every
//       bar; they are persistent objects updated in place.
//  19.  h1_up_point..l3_down_point were `var` floats that latched on and kept
//       re-emitting labels forever; they are per-bar values now. This also
//       deleted ~250 lines of copy-pasted "reset the other five" blocks.
//  20.  The VWAP box teardown was duplicated verbatim three times → one
//       teardown block covering new-day / flip-up / flip-down.
//  21.  The right-side status cell was rewritten on every historical bar;
//       now only on barstate.islast.
//  22.  VWAP trend-line history capped at 400 (was 450) so the 6 S/R lines
//       plus the midnight/open/10:15 lines still fit under the 500 limit.
//  23.  Removed the three empty tables (panel / panel2 / panel5) that drew
//       blank cells at top_right and bottom_right.
//  24.  Removed `plot(na, display = display.none)`.
//  25.  De-duplicated ta.wma(close, 200) (computed as both ma200 and wma_200),
//       ta.wma(close, 11) and ta.wma(close, 48); replaced two hand-written
//       10-term averages with ta.sma(...,10). request.security call count
//       dropped from 24 to 14.
//
//  DEAD CODE REMOVED  (say the word and any of it comes back)
//  26.  tzOffsetInMs(), f_nDecimals()/_n, stoch/k/d/k10/d10, cci, rsiWMA,
//       the Chande momentum block (chandeMO/cL/cS), ispullback_call and
//       ispullback_put (their second OR-branch was a subset of the first, and
//       nothing read them), pullbackcall, pullbackrsi,
//       pct_rsi, rsi14/rsiL/rsiS, no_call/no_put + diff1..diff5,
//       is_away_call/is_away_put + the away_call/away_put inputs,
//       sell_top/buy_bottom/input_in/input_out inputs, sideaway/sideway/
//       sideway_short/sideway_long, avg/avg2/start_put/start_call,
//       is_strong_buy/is_strong_sell, lv/hv/l_val/h_val/start_time/end_time,
//       l_ema_diff/h_ema_diff, open_ema/open_ema_diff/start_time_ema,
//       lowest_1d, last_close_1d, wma200_5, wma50_15, the duplicate
//       running_ph/running_pl plots at offset 0, and the no-op
//       `if pattern_put \n pattern_put := false`.
//
//  NEW / RESTORED
//  27.  Everything the script computed but never showed is now switchable
//       under "11 · Diagnostics": the Snag engine marks (C-OK, C-Weak, CL,
//       C-Confirm, EX-C, P-Strong, PL, EX-P, RSP), the pattern marks
//       (C100/P100, Big Drop, Big Bottom, BR, OUT), the exhaustion diamonds
//       and R-U/R-D, and Buy-a-Call / Buy-a-Put.
//  28.  21 alertcondition() entries. v5 had none — the is_alert_* flags went
//       nowhere.
//  29.  The T3 trend bar colouring (clr_bar) was computed and then thrown
//       away because barcolor() only ever received the Code-2 colour. It is
//       now selectable via "Bar Color Source".
//  30.  Hard-coded magic numbers are inputs: entry body points (6), trailing
//       stop (5), begin-put body (3.25), intraday/HTF strong body (3 / 6),
//       the ±40 WMA200 distance, and the webhook ticker string (was the
//       literal "SPX" even on ES/NQ charts).
// ═══════════════════════════════════════════════════════════════════════════════

Overview

SPX Midnight Box + Snag Logic draws the midnight high, mid, and low, flags edge rejections within an absolute tolerance, and plots optional open-range and 10:15 levels plus chart weighted moving averages. A colour-coded VWAP trend line and an RSI panel sit with the price. The Snag engine tracks a higher-timeframe weighted-moving-average state and marks call or put entries, trailing stops, and exits. Session timezone, visibility toggles, and strict higher-timeframe handling are the main settings. The author states higher-timeframe reads use lookahead off.

Details

Published by

dearvn on GitHub

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

Trend, Support & resistance, Sessions & time, Screeners & dashboards

Markets

Indices

Timeframes

M15-H1

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Topics

futures-markettradingviewtradovatetrading-futureses1best-indicator-tradingviewnq1trading-futurerus-stock-market

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Session TimezonestringAmerica/Los_Angelestz
Absolute Entry Tolerancefloat2.0entryTol
Show EMAsbooltrueshowEMAs
Show Open Range LevelsbooltrueshowOpenLevels
Show 10:15 Levelsbooltrueshow1015Levels
Plot chart-timeframe WMA 11 / 48 / 200booltrueshowChartWMAs
Strict HTF (ignore the forming HTF bar)boolfalsestrictHTF
RSI Panel Lengthint14rsiPanelLength
Show VWAP Confirmed 30-Point BoxbooltrueshowVWAPBoxes
VWAP Box Pointsfloat30.0vwapBoxPoints
VWAP Sourcesourcehlc3vwapSrc
Show VWAP Green/Orange/Red Trend LinebooltrueshowVWAPTrendLine
Show all 40 settings
SettingTypeDefaultVariable
VWAP Trend Line History Barsint250vwapTrendKeepBars
VWAP Strong Down Thresholdfloat3.0vwapStrongDownThreshold
Sourcesourceclosesrc_sm
Smoothingint1smooth
Lookbackint25length1
Lengthint14gamaLength
Adaptive ParametersbooltruegamaAdaptive
Volatility Periodint20volatilityPeriod
Standard Deviation (non-adaptive)float1.0gamaFixedSigma
Strong S/R Cross Up Pointsfloat10.0cross_up_point
Strong S/R Cross Down Pointsfloat10.0cross_down_point
VWMA Band Multiplierfloat3.0bandMult
Pivot Left Barsint4point_lb
Pivot Right Barsint4point_rb
Plot Offset Barsint5rb
Highest %float10.0pctHigh
Lowest %float-8.0pctLow
Enable Candle ColorsbooltrueshowCandleColors
Bar Color SourcestringCode 2barColorMode
Intraday Strong-Body Pointsfloat3.0minBodyIntraday
Higher-TF Strong-Body Pointsfloat6.0minBodyHTF
Strong Entry Body Pointsfloat6.0strongBodyPts
Trailing Stop Pointsfloat5.0stopPts
Begin-Put Body Pointsfloat3.25beginPutBody
Engine marks (C-OK / C-Weak / CL / EX-C / P-Strong / PL / EX-P)boolfalseshowEngineMarks
Pattern marks (100% Call/Put, Big Drop, Big Bottom, BR, OUT)boolfalseshowPatternMarks
Exhaustion marks (TOP / BOTTOM diamonds, R-U / R-D)boolfalseshowExhaustion
Buy-a-Call / Buy-a-Put marksboolfalseshowBuyaMarks
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

Market Monitor 5m

by chris-c-thomas
14 stars

Market Monitor 5m is an intraday context overlay designed for 5-minute chart analysis. It calculates directional bias using exponential moving averages, VWAP, TTM Squeeze volatility state, NYSE TICK market breadth, and ATR volatility regimes. The script plots

Indicator v6 Screeners & dashboardsSupport & resistance Repaints
Updated 6 months ago · MIT GitHub repository of Market Monitor 5m
575 stars

This indicator builds Market Profile Time Price Opportunity (TPO) structures across defined session timeframes directly on the main price chart. It visualizes the Point of Control (POC) and Value Area boundaries using dynamic box drawings. An integrated multi-

Indicator v5 Support & resistanceScreeners & dashboards
Updated 1 year ago · MPL-2.0 GitHub repository of Market Profile with Screener

Golden Bullet Strategy

by TradersPost
218 stars

The Golden Bullet Strategy is an overlay signal indicator based on Smart Money Concepts and time-based liquidity models. It identifies liquidity sweeps of a designated reference session range during a specified hunt window. It then confirms Market Structure Sh

Indicator v6 SMC / ICT / price actionSessions & time
Updated 3 months ago · None stated GitHub repository of Golden Bullet Strategy
92 stars

This indicator classifies the market into gamma regime types (negative gamma trend, positive gamma mean-reversion, or transition) by calculating a composite score based on daily ATR ratios, session VWAP crossover frequencies, and opening range displacement. It

Indicator v5 Volatility & bandsTrend
Updated 8 weeks ago · Custom licence GitHub repository of Moon Dev GEX Regime & Trap

Session Edge Probabilities

by TraderOracle
61 stars

Session Edge Probabilities tracks eight session-based trading rules using statistical probabilities evaluated on historical chart data. It utilizes a fixed calculation timeframe to evaluate opening ranges, midnight levels, afternoon reversal windows, and price

Indicator v6 Sessions & timeScreeners & dashboards
Updated 9 days ago · None stated GitHub repository of Session Edge Probabilities
56 stars

This indicator marks forex trading sessions and opening ranges on the price chart. It can draw Sydney, Tokyo, Frankfurt, London, and New York session boxes or five- and fifteen-minute opening ranges, plus an optional opening channel and a day-open range. Day r

Indicator v5 Sessions & timeSupport & resistance Repaints
Updated 2 months ago · None stated GitHub repository of Day Trading Booster by DGT

More indicators

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers