TradingView indicators

Four Pine v5 scripts, ready to paste.

Start with the SPX Edge Dashboard — one all-in-one indicator that runs the whole strategy with toggles for each module. Or use the three standalone scripts (Module 1 / 2 / 3) if you only want to focus on one part of the system. All four expose named alerts for every actionable signal.

How to install: In TradingView, open any SPX chart, click Pine Editor at the bottom, paste a script, click Save, then click Add to chart. Repeat for each module on the relevant chart (daily for Module 1, 5-min for Module 2 and Module 3). To set up alerts, right-click the indicator on the chart → Add alert on … → pick the named condition (e.g. "M2: Sell PUT spread") → choose your notification channel.

All-in-one — SPX Edge Dashboard

Single indicator that runs the whole strategy. Toggle each module on/off independently. A bottom-right status table shows the regime, yesterday's NR7 flag, today's gap class, and the recommended setup for each module — updated live as the day unfolds. Recommended for traders who want one TradingView indicator instead of three.

⬇ Download .pine
Pine version: v5
Recommended timeframe: 5-min intraday (Module 1 daily-only signals fire when chart is daily)
File: SPX_Edge_Dashboard.pine
//@version=5
// =====================================================================
//  SPX EDGE DASHBOARD — Master indicator
//
//  One indicator that runs the whole strategy. Loads on a 5-min SPX chart
//  and tells you, in real time:
//
//    1.  Which REGIME we're in (StrongUp / Mixed / StrongDown)
//    2.  Whether yesterday was an NR7 (premium-selling skip flag)
//    3.  Today's GAP class (continuation vs flat vs counter-trend)
//    4.  Which MODULES are active, and what setup is recommended
//    5.  All the relevant levels & signals on chart
//
//  Each of the three modules can be toggled independently in the inputs.
//
//  Best used on:    SPX, 5-min chart
//  Also works on:   ES, SPY, XSP and on 1/3/15-min timeframes
//  Module 1 (swing) signals only fire on the daily timeframe — change the
//  chart to Daily to see those.
// =====================================================================
indicator("SPX Edge Dashboard", overlay=true,
          max_lines_count=500, max_labels_count=200)

// =====================================================================
//  INPUTS
// =====================================================================
grpDisp  = "Display"
showRegime    = input.bool(true,  "Tint background by regime",        group=grpDisp)
panelPos      = input.string("bottom_right", "Status panel position", options=["top_left","top_right","bottom_left","bottom_right","middle_right"], group=grpDisp)
panelSize     = input.string("normal", "Panel text size",             options=["tiny","small","normal","large"], group=grpDisp)

grpM1 = "Module 1 — Swing Long"
showM1        = input.bool(true,  "Enable Module 1",                  group=grpM1)
showSMAs      = input.bool(true,  "Show 20 / 50 / 200 SMAs",          group=grpM1)
showM1Signals = input.bool(true,  "Show entry/exit markers",          group=grpM1)
showNR7Mark   = input.bool(true,  "Mark NR7 days",                    group=grpM1)

grpM2 = "Module 2 — 0DTE Credit Spread"
showM2        = input.bool(true,  "Enable Module 2",                  group=grpM2)
showLevels    = input.bool(true,  "Show prior-day projection levels", group=grpM2)
keepHistory   = input.bool(true,  "Keep historical days' levels",     group=grpM2)
gapBigPct     = input.float(0.5,  "Big gap threshold (%)", step=0.1,  group=grpM2)
gapSmallPct   = input.float(0.1,  "Small gap threshold (%)", step=0.05, group=grpM2)

grpM3 = "Module 3 — ORB Long"
showM3        = input.bool(true,  "Enable Module 3",                  group=grpM3)
orMinutes     = input.int(15,     "Opening Range minutes", minval=5, maxval=60, step=5, group=grpM3)
sessStart     = input.session("0930-1600", "Cash session (exchange time)", group=grpM3)
showOR        = input.bool(true,  "Show OR high/low",                 group=grpM3)
showM3Signal  = input.bool(true,  "Show ORB entry marker",            group=grpM3)

// =====================================================================
//  DAILY-CONTEXT DATA (yesterday's OHLC, daily SMAs, NR7)
//  These return the most recent CLOSED daily values — safe for plotting.
// =====================================================================
priorHigh    = request.security(syminfo.tickerid, "D", high[1],  lookahead=barmerge.lookahead_on)
priorLow     = request.security(syminfo.tickerid, "D", low[1],   lookahead=barmerge.lookahead_on)
priorClose   = request.security(syminfo.tickerid, "D", close[1], lookahead=barmerge.lookahead_on)
priorRange   = priorHigh - priorLow

// daily SMAs (use yesterday's value so it's known at today's open)
dSMA20  = request.security(syminfo.tickerid, "D", ta.sma(close, 20)[1],  lookahead=barmerge.lookahead_on)
dSMA50  = request.security(syminfo.tickerid, "D", ta.sma(close, 50)[1],  lookahead=barmerge.lookahead_on)
dSMA200 = request.security(syminfo.tickerid, "D", ta.sma(close, 200)[1], lookahead=barmerge.lookahead_on)

aboveAll = priorClose > dSMA20 and priorClose > dSMA50 and priorClose > dSMA200
belowAll = priorClose < dSMA20 and priorClose < dSMA50 and priorClose < dSMA200
mixed    = not aboveAll and not belowAll

regimeStr = aboveAll ? "StrongUp" : belowAll ? "StrongDown" : "Mixed"

// NR7: yesterday's range = smallest of last 7 daily ranges (as of yesterday's close)
priorRangeMin7 = request.security(syminfo.tickerid, "D",
  ta.lowest(high - low, 7)[1],
  lookahead=barmerge.lookahead_on)
isNR7 = not na(priorRange) and not na(priorRangeMin7) and priorRange == priorRangeMin7

// =====================================================================
//  TODAY'S OPEN + GAP CLASS (captured at first bar of the day on intraday)
// =====================================================================
isNewDay = ta.change(time("D")) != 0
var float todayOpen = na
if isNewDay
    todayOpen := open
gapPct = (todayOpen - priorClose) / priorClose * 100
gapClass = na(gapPct) ? "—" :
           gapPct >  gapBigPct   ? "GapUp_Big"   :
           gapPct >  gapSmallPct ? "GapUp_Small" :
           gapPct < -gapBigPct   ? "GapDn_Big"   :
           gapPct < -gapSmallPct ? "GapDn_Small" : "Flat"

// Distance from each SMA (in %)
distSMA20  = (priorClose - dSMA20)  / dSMA20  * 100
distSMA50  = (priorClose - dSMA50)  / dSMA50  * 100
distSMA200 = (priorClose - dSMA200) / dSMA200 * 100

// =====================================================================
//  REGIME BACKGROUND
// =====================================================================
regimeColor = aboveAll ? color.new(color.green, 92) :
              belowAll ? color.new(color.red,   92) :
                         color.new(color.gray,  95)
bgcolor(showRegime ? regimeColor : na, title="Regime")

// =====================================================================
//  MODULE 1 — SMAs and signals (signals only fire on daily timeframe)
// =====================================================================
sma20  = ta.sma(close, 20)
sma50  = ta.sma(close, 50)
sma200 = ta.sma(close, 200)

plot(showM1 and showSMAs ? sma20  : na, "SMA 20",  color=color.new(color.aqua,    0), linewidth=2)
plot(showM1 and showSMAs ? sma50  : na, "SMA 50",  color=color.new(color.orange,  0), linewidth=2)
plot(showM1 and showSMAs ? sma200 : na, "SMA 200", color=color.new(color.purple,  0), linewidth=2)

// NR7 marker (fires on daily timeframe — current day's range vs lowest 7)
todayRange = high - low
nr7Today = todayRange == ta.lowest(todayRange, 7)
isDaily  = timeframe.isdaily
plotshape(showM1 and showNR7Mark and isDaily and nr7Today, title="NR7",
          style=shape.triangledown, location=location.abovebar,
          color=color.new(color.yellow, 0), size=size.tiny)

// M1 entry/exit signals (daily timeframe only)
m1AboveAll = isDaily and (close > sma20 and close > sma50 and close > sma200)
pullbackLong = m1AboveAll and low <= sma20 and close > sma20
isFriday = dayofweek == dayofweek.friday
fridayLong = m1AboveAll and isFriday and barstate.isconfirmed
addAfterNR7 = m1AboveAll and nr7Today

plotshape(showM1 and showM1Signals and pullbackLong, title="M1: 20-DMA Pullback Long",
          style=shape.triangleup, location=location.belowbar, color=color.new(color.lime, 0), size=size.small, text="20MA")
plotshape(showM1 and showM1Signals and fridayLong, title="M1: Friday StrongUp",
          style=shape.triangleup, location=location.belowbar, color=color.new(color.green, 0), size=size.small, text="FRI")
plotshape(showM1 and showM1Signals and addAfterNR7, title="M1: Add after NR7",
          style=shape.diamond, location=location.belowbar, color=color.new(color.aqua, 0), size=size.tiny, text="NR7+")

// M1 exits
exitBelow20 = isDaily and close < sma20 and close[1] >= sma20[1]
ret = (close - close[1]) / close[1] * 100
bigUp = isDaily and ret > 2.0
plotshape(showM1 and showM1Signals and exitBelow20, title="M1: Exit < 20-DMA",
          style=shape.xcross, location=location.abovebar, color=color.new(color.red, 0), size=size.small, text="EXIT")
plotshape(showM1 and showM1Signals and bigUp, title="M1: Exit +2% day",
          style=shape.xcross, location=location.abovebar, color=color.new(color.orange, 0), size=size.small, text="+2%")

// =====================================================================
//  MODULE 2 — Per-day projection lines
// =====================================================================
var line lPDH    = na
var line lPDL    = na
var line lPDmid  = na
var line lPDHp25 = na
var line lPDHp50 = na
var line lPDHp1  = na
var line lPDLm25 = na
var line lPDLm50 = na
var line lPDLm1  = na

cPDH = color.new(color.green, 0)
cPDL = color.new(color.red,   0)
cMid = color.new(color.gray,  50)
cP25 = color.new(color.green, 50)
cP50 = color.new(color.green, 30)
cP1  = color.new(color.green, 10)
cM25 = color.new(color.red,   50)
cM50 = color.new(color.red,   30)
cM1  = color.new(color.red,   10)

if showM2 and showLevels and isNewDay and not na(priorHigh)
    if not keepHistory
        line.delete(lPDH)
        line.delete(lPDL)
        line.delete(lPDmid)
        line.delete(lPDHp25)
        line.delete(lPDHp50)
        line.delete(lPDHp1)
        line.delete(lPDLm25)
        line.delete(lPDLm50)
        line.delete(lPDLm1)
    midY = (priorHigh + priorLow) / 2
    lPDH    := line.new(bar_index, priorHigh, bar_index, priorHigh, color=cPDH, width=2)
    lPDL    := line.new(bar_index, priorLow,  bar_index, priorLow,  color=cPDL, width=2)
    lPDmid  := line.new(bar_index, midY,      bar_index, midY,      color=cMid, width=1, style=line.style_dotted)
    lPDHp25 := line.new(bar_index, priorHigh + 0.25 * priorRange, bar_index, priorHigh + 0.25 * priorRange, color=cP25, width=1, style=line.style_dotted)
    lPDHp50 := line.new(bar_index, priorHigh + 0.50 * priorRange, bar_index, priorHigh + 0.50 * priorRange, color=cP50, width=1, style=line.style_dotted)
    lPDHp1  := line.new(bar_index, priorHigh + 1.00 * priorRange, bar_index, priorHigh + 1.00 * priorRange, color=cP1,  width=1, style=line.style_dotted)
    lPDLm25 := line.new(bar_index, priorLow  - 0.25 * priorRange, bar_index, priorLow  - 0.25 * priorRange, color=cM25, width=1, style=line.style_dotted)
    lPDLm50 := line.new(bar_index, priorLow  - 0.50 * priorRange, bar_index, priorLow  - 0.50 * priorRange, color=cM50, width=1, style=line.style_dotted)
    lPDLm1  := line.new(bar_index, priorLow  - 1.00 * priorRange, bar_index, priorLow  - 1.00 * priorRange, color=cM1,  width=1, style=line.style_dotted)

if showM2 and showLevels and not isNewDay and not na(lPDH)
    line.set_x2(lPDH,    bar_index)
    line.set_x2(lPDL,    bar_index)
    line.set_x2(lPDmid,  bar_index)
    line.set_x2(lPDHp25, bar_index)
    line.set_x2(lPDHp50, bar_index)
    line.set_x2(lPDHp1,  bar_index)
    line.set_x2(lPDLm25, bar_index)
    line.set_x2(lPDLm50, bar_index)
    line.set_x2(lPDLm1,  bar_index)

// =====================================================================
//  MODULE 3 — Opening Range + ORB long entry
// =====================================================================
inSession  = not na(time(timeframe.period, sessStart))
isFirstBar = inSession and not inSession[1]
orBars     = math.max(1, math.floor(orMinutes / math.max(timeframe.multiplier, 1)))
inOR       = inSession and ta.barssince(isFirstBar) < orBars

var float orHigh   = na
var float orLow    = na
var bool  orDone   = false
var bool  brokeUp  = false
var float entryPx  = na
var int   entryBar = na

if isFirstBar
    orHigh   := high
    orLow    := low
    orDone   := false
    brokeUp  := false
    entryPx  := na
    entryBar := na

if inOR and not isFirstBar
    orHigh := math.max(orHigh, high)
    orLow  := math.min(orLow,  low)

if inSession and not inOR and not orDone
    orDone := true

m3Trigger = false
if orDone and inSession and not brokeUp and high > orHigh
    brokeUp  := true
    entryPx  := orHigh
    entryBar := bar_index
    m3Trigger := true

m3FilterOK = aboveAll and gapPct > -0.5
m3StrongFilter = m3FilterOK and todayOpen > priorHigh

plot(showM3 and showOR and orDone ? orHigh : na, "OR High",
     color=color.new(color.aqua, 0),    linewidth=2, style=plot.style_linebr)
plot(showM3 and showOR and orDone ? orLow  : na, "OR Low",
     color=color.new(color.fuchsia, 0), linewidth=2, style=plot.style_linebr)

m3EntryValid = m3Trigger and m3FilterOK
plotshape(showM3 and showM3Signal and m3EntryValid, title="M3: ORB Long Entry",
          style=shape.triangleup, location=location.belowbar,
          color=color.new(color.green, 0), size=size.normal, text="LONG")

// =====================================================================
//  DECISION LOGIC — what to do today
// =====================================================================
// M1 status
m1Status = ""
m1Color  = color.gray
if not showM1
    m1Status := "off"
    m1Color  := color.gray
else if aboveAll
    m1Status := isNR7 ? "ADD ½ unit (NR7)" : "ACTIVE — buy 20MA pullback"
    m1Color  := color.lime
else if mixed
    m1Status := "Half size — pullbacks only"
    m1Color  := color.yellow
else
    m1Status := "STAND DOWN (StrongDown)"
    m1Color  := color.red

// M2 status
m2Status = ""
m2Color  = color.gray
if not showM2
    m2Status := "off"
    m2Color  := color.gray
else if isNR7
    m2Status := "🚫 SKIP — NR7 yesterday"
    m2Color  := color.orange
else if gapClass == "GapUp_Big" and aboveAll
    m2Status := "PUT spread @ PDL−0.25R"
    m2Color  := color.lime
else if gapClass == "GapDn_Big" and belowAll
    m2Status := "CALL spread @ PDH+0.25R"
    m2Color  := color.lime
else if (gapClass == "Flat" or gapClass == "GapUp_Small" or gapClass == "GapDn_Small") and (aboveAll or belowAll)
    m2Status := "IRON CONDOR ±R"
    m2Color  := color.lime
else if (gapClass == "GapUp_Big" and belowAll) or (gapClass == "GapDn_Big" and aboveAll)
    m2Status := "Counter-trend gap — half size"
    m2Color  := color.yellow
else
    m2Status := "Mixed regime — half size"
    m2Color  := color.yellow

// M3 status
m3Status = ""
m3Color  = color.gray
if not showM3
    m3Status := "off"
    m3Color  := color.gray
else if not m3FilterOK
    m3Status := belowAll ? "STAND DOWN (StrongDown)" :
                gapPct < -0.5 ? "Skip — big gap-down" :
                "Skip — filters failed"
    m3Color  := color.red
else if not orDone
    m3Status := "⏳ OR forming…"
    m3Color  := color.silver
else if not brokeUp
    m3Status := m3StrongFilter ? "Wait — open > PDH ✓ (high conviction)" : "Wait for break > OR High"
    m3Color  := color.aqua
else
    m3Status := "📈 LONG entered " + str.tostring(entryPx, "#.##") + " — exit MOC"
    m3Color  := color.lime

// =====================================================================
//  STATUS PANEL (table)
// =====================================================================
posMap = panelPos == "top_left"     ? position.top_left :
         panelPos == "top_right"    ? position.top_right :
         panelPos == "bottom_left"  ? position.bottom_left :
         panelPos == "middle_right" ? position.middle_right :
                                       position.bottom_right

sizeMap = panelSize == "tiny"  ? size.tiny :
          panelSize == "small" ? size.small :
          panelSize == "large" ? size.large :
                                  size.normal

var table panel = table.new(posMap, 2, 13,
  bgcolor=color.new(#0e1116, 8),
  border_width=1, border_color=color.new(color.gray, 60),
  frame_width=1, frame_color=color.new(color.gray, 40))

// Helpers
regimeFG = aboveAll ? color.lime : belowAll ? color.red : color.silver
nr7FG    = isNR7 ? color.orange : color.silver
gapFG    = gapClass == "GapUp_Big" ? color.lime :
           gapClass == "GapDn_Big" ? color.red :
           gapClass == "Flat"      ? color.silver :
           color.aqua

dSMAOK = aboveAll ? color.lime : belowAll ? color.red : color.yellow

if barstate.islast
    // Header row
    table.cell(panel, 0, 0, " SPX EDGE DASHBOARD ",        text_color=color.white, bgcolor=color.new(#1F4E78, 0), text_size=sizeMap, text_halign=text.align_center)
    table.cell(panel, 1, 0, " " + regimeStr + " ",         text_color=regimeFG,    bgcolor=color.new(#1F4E78, 0), text_size=sizeMap, text_halign=text.align_center)

    // SMAs section
    table.cell(panel, 0, 1, " 20-DMA ",                    text_color=color.silver, text_size=sizeMap, text_halign=text.align_left)
    table.cell(panel, 1, 1, " " + (priorClose > dSMA20  ? "▲ " : "▼ ") + str.tostring(distSMA20,  "+0.0") + "% ", text_color=priorClose > dSMA20 ? color.lime : color.red, text_size=sizeMap, text_halign=text.align_right)

    table.cell(panel, 0, 2, " 50-DMA ",                    text_color=color.silver, text_size=sizeMap, text_halign=text.align_left)
    table.cell(panel, 1, 2, " " + (priorClose > dSMA50  ? "▲ " : "▼ ") + str.tostring(distSMA50,  "+0.0") + "% ", text_color=priorClose > dSMA50 ? color.lime : color.red, text_size=sizeMap, text_halign=text.align_right)

    table.cell(panel, 0, 3, " 200-DMA ",                   text_color=color.silver, text_size=sizeMap, text_halign=text.align_left)
    table.cell(panel, 1, 3, " " + (priorClose > dSMA200 ? "▲ " : "▼ ") + str.tostring(distSMA200, "+0.0") + "% ", text_color=priorClose > dSMA200 ? color.lime : color.red, text_size=sizeMap, text_halign=text.align_right)

    // Yesterday section
    table.cell(panel, 0, 4, " Yesterday ",                 text_color=color.aqua, bgcolor=color.new(#1c232c, 0), text_size=sizeMap, text_halign=text.align_left)
    table.cell(panel, 1, 4, "",                            bgcolor=color.new(#1c232c, 0))

    table.cell(panel, 0, 5, " Range R ",                   text_color=color.silver, text_size=sizeMap, text_halign=text.align_left)
    table.cell(panel, 1, 5, " " + str.tostring(priorRange, "#.##") + " ", text_color=color.white, text_size=sizeMap, text_halign=text.align_right)

    table.cell(panel, 0, 6, " NR7? ",                      text_color=color.silver, text_size=sizeMap, text_halign=text.align_left)
    table.cell(panel, 1, 6, " " + (isNR7 ? "YES — skip M2" : "no") + " ", text_color=nr7FG, text_size=sizeMap, text_halign=text.align_right)

    // Today section
    table.cell(panel, 0, 7, " Today ",                     text_color=color.aqua, bgcolor=color.new(#1c232c, 0), text_size=sizeMap, text_halign=text.align_left)
    table.cell(panel, 1, 7, "",                            bgcolor=color.new(#1c232c, 0))

    table.cell(panel, 0, 8, " Gap ",                       text_color=color.silver, text_size=sizeMap, text_halign=text.align_left)
    table.cell(panel, 1, 8, " " + str.tostring(gapPct, "+0.00") + "%  " + gapClass + " ", text_color=gapFG, text_size=sizeMap, text_halign=text.align_right)

    // Modules section
    table.cell(panel, 0, 9, " Active modules ",            text_color=color.aqua, bgcolor=color.new(#1c232c, 0), text_size=sizeMap, text_halign=text.align_left)
    table.cell(panel, 1, 9, "",                            bgcolor=color.new(#1c232c, 0))

    table.cell(panel, 0, 10, " M1  Swing Long ",           text_color=color.white, text_size=sizeMap, text_halign=text.align_left)
    table.cell(panel, 1, 10, " " + m1Status + " ",         text_color=m1Color,    text_size=sizeMap, text_halign=text.align_right)

    table.cell(panel, 0, 11, " M2  0DTE Cr Spread ",       text_color=color.white, text_size=sizeMap, text_halign=text.align_left)
    table.cell(panel, 1, 11, " " + m2Status + " ",         text_color=m2Color,    text_size=sizeMap, text_halign=text.align_right)

    table.cell(panel, 0, 12, " M3  ORB Long ",             text_color=color.white, text_size=sizeMap, text_halign=text.align_left)
    table.cell(panel, 1, 12, " " + m3Status + " ",         text_color=m3Color,    text_size=sizeMap, text_halign=text.align_right)

// =====================================================================
//  ALERTS — one named alert per actionable signal
// =====================================================================
alertcondition(pullbackLong,                           "M1: 20-DMA Pullback Long",  "M1: SPX touched 20-DMA in StrongUp regime — long entry")
alertcondition(fridayLong,                             "M1: Friday StrongUp Long",  "M1: Friday close in StrongUp — long entry")
alertcondition(addAfterNR7,                            "M1: Add after NR7",         "M1: NR7 in regime — add half unit")
alertcondition(exitBelow20,                            "M1: Exit below 20-DMA",     "M1: Close below 20-DMA — exit long")
alertcondition(bigUp,                                  "M1: Exit +2% day",          "M1: +2% single-day move — mean-revert exit")
alertcondition(isNR7,                                  "M2: SKIP NR7",              "M2: NR7 detected — skip 0DTE today")
alertcondition(gapClass == "GapUp_Big" and aboveAll,   "M2: Sell PUT spread",       "M2: Big gap-up + StrongUp — sell put credit spread below PDL")
alertcondition(gapClass == "GapDn_Big" and belowAll,   "M2: Sell CALL spread",      "M2: Big gap-down + StrongDown — sell call credit spread above PDH")
alertcondition((gapClass == "Flat" or gapClass == "GapUp_Small" or gapClass == "GapDn_Small") and (aboveAll or belowAll), "M2: Iron Condor", "M2: Flat / small gap — iron condor at PDH+0.25R / PDL-0.25R")
alertcondition(m3EntryValid,                           "M3: ORB Long Entry",        "M3: ORB break above OR high in StrongUp regime — long entry, exit MOC")

Module 1 — Swing Long

Daily chart. Long-only trend-regime continuation. Plots 20/50/200-day SMAs, regime background, NR7 markers, plus pullback / Friday-in-regime / NR7-add entry triangles and close-below-20DMA / +2% exit X's.

⬇ Download .pine
Pine version: v5
Recommended timeframe: Daily
File: Module1_SwingLong.pine
//@version=5
// =====================================================================
//  SPX Module 1 — Swing Long
//  Trend-regime momentum system. Long-only.
//
//  Signals plotted:
//   • Background colour    — regime (StrongUp / StrongDown / Mixed)
//   • SMAs                 — 20 / 50 / 200 day
//   • Yellow ▽ above bar   — NR7 (today's range = smallest of last 7)
//   • Lime ▲ "20MA"        — pullback to 20-DMA in StrongUp regime
//   • Green ▲ "FRI"        — Friday close in StrongUp regime
//   • Aqua ◇  "NR7+"       — add half unit after NR7 in regime
//   • Red ✕   "EXIT"       — close below 20-DMA from above
//   • Orange ✕ "+2%"       — single-day +2% move (mean-revert exit)
// =====================================================================
indicator("SPX Module 1 — Swing Long", overlay=true, max_labels_count=200)

// === Inputs ===
showSMAs     = input.bool(true,  "Show SMAs (20 / 50 / 200)")
showRegimeBG = input.bool(true,  "Tint background by regime")
showNR7      = input.bool(true,  "Mark NR7 days")
showEntries  = input.bool(true,  "Show entry signals")
showExits    = input.bool(true,  "Show exit signals")

// === Regime ===
sma20  = ta.sma(close, 20)
sma50  = ta.sma(close, 50)
sma200 = ta.sma(close, 200)

aboveAll = close > sma20 and close > sma50 and close > sma200
belowAll = close < sma20 and close < sma50 and close < sma200
mixed    = not aboveAll and not belowAll

regimeColor = aboveAll ? color.new(color.green, 92) : belowAll ? color.new(color.red, 92) : color.new(color.gray, 95)
bgcolor(showRegimeBG ? regimeColor : na, title="Regime")

// === SMA plots ===
plot(showSMAs ? sma20  : na, "SMA20",  color=color.new(color.aqua,    0), linewidth=2)
plot(showSMAs ? sma50  : na, "SMA50",  color=color.new(color.orange,  0), linewidth=2)
plot(showSMAs ? sma200 : na, "SMA200", color=color.new(color.purple,  0), linewidth=2)

// === NR7 ===
todayRange = high - low
nr7 = todayRange == ta.lowest(todayRange, 7)
plotshape(showNR7 and nr7, title="NR7", style=shape.triangledown, location=location.abovebar, color=color.new(color.yellow, 0), size=size.tiny)

// === Entry signals ===
// (1) Pullback to 20-DMA inside StrongUp regime
pullbackLong = aboveAll and low <= sma20 and close > sma20
plotshape(showEntries and pullbackLong, title="20-DMA Pullback Long",
          style=shape.triangleup, location=location.belowbar,
          color=color.new(color.lime, 0), size=size.small, text="20MA")

// (2) Friday close in StrongUp
isFriday = dayofweek == dayofweek.friday
fridayLong = aboveAll and isFriday and barstate.isconfirmed
plotshape(showEntries and fridayLong, title="Friday StrongUp Long",
          style=shape.triangleup, location=location.belowbar,
          color=color.new(color.green, 0), size=size.small, text="FRI")

// (3) Add half-unit after NR7 in regime
addAfterNR7 = aboveAll and nr7
plotshape(showEntries and addAfterNR7, title="Add after NR7",
          style=shape.diamond, location=location.belowbar,
          color=color.new(color.aqua, 0), size=size.tiny, text="NR7+")

// === Exit signals ===
exitBelow20 = close < sma20 and close[1] >= sma20[1]
plotshape(showExits and exitBelow20, title="Exit: close < 20-DMA",
          style=shape.xcross, location=location.abovebar,
          color=color.new(color.red, 0), size=size.small, text="EXIT")

ret = (close - close[1]) / close[1] * 100
bigUp = ret > 2.0
plotshape(showExits and bigUp, title="Exit: +2% day",
          style=shape.xcross, location=location.abovebar,
          color=color.new(color.orange, 0), size=size.small, text="+2%")

// === Status label on most recent bar ===
var label statusLbl = na
if barstate.islast
    label.delete(statusLbl)
    regimeStr = aboveAll ? "STRONG UP" : belowAll ? "STRONG DOWN" : "MIXED"
    qualifier = nr7 ? "  |  NR7" : ""
    statusLbl := label.new(bar_index, high, text="Module 1  —  " + regimeStr + qualifier,
                           style=label.style_label_down,
                           color=color.new(color.black, 20),
                           textcolor=color.white, size=size.normal)

// === Alerts ===
alertcondition(pullbackLong, "M1: 20-DMA Pullback Long",  "M1: SPX touched 20-DMA in StrongUp — long entry")
alertcondition(fridayLong,   "M1: Friday StrongUp Long",  "M1: Friday close in StrongUp — long entry")
alertcondition(addAfterNR7,  "M1: Add after NR7",         "M1: NR7 in regime — add half unit")
alertcondition(exitBelow20,  "M1: Exit below 20-DMA",     "M1: Close below 20-DMA — exit long")
alertcondition(bigUp,        "M1: Exit +2% day",          "M1: +2% single-day — mean-revert exit")

Module 2 — 0DTE Credit Spread

Intraday chart (5-min or 15-min). Plots prior-day projection levels (PDH, PDL, mid, ±0.25R/0.5R/1R) as discrete per-day segments, regime tint, and an always-visible bottom-left status panel that recommends today's structure (put spread / call spread / iron condor / skip).

⬇ Download .pine
Pine version: v5
Recommended timeframe: 5-min or 15-min intraday
File: Module2_0DTE_CreditSpread.pine
//@version=5
// =====================================================================
//  SPX Module 2 — 0DTE Credit Spread Setup Identifier
//
//  Per-day prior-range projection levels + always-visible setup panel.
//
//  What it plots:
//   • Background tint        — daily regime (StrongUp / StrongDown / Mixed)
//   • PDH / PDL              — prior-day high (green) / prior-day low (red)
//   • PD Mid                 — prior-day midpoint (dotted grey)
//   • +0.25R / +0.5R / +1R   — projections above PDH (faint green dotted)
//   • -0.25R / -0.5R / -1R   — projections below PDL (faint red dotted)
//   • Bottom-left table      — regime + gap class + suggested structure,
//                              always visible on top of bars
//
//  R = prior day's range (PDH − PDL).
//  Each day's lines are isolated to that trading day — they do NOT
//  connect across days.
//
//  Best used on a 5-min or 15-min intraday SPX chart.
// =====================================================================
indicator("SPX Module 2 — 0DTE Credit Spread", overlay=true,
          max_lines_count=500, max_labels_count=10)

// =====================================================================
//  Inputs
// =====================================================================
showLevels   = input.bool(true,  "Show prior-day projection levels")
showRegime   = input.bool(true,  "Tint background by regime")
showSetupTbl = input.bool(true,  "Show suggested-setup panel")
keepHistory  = input.bool(true,  "Keep historical days' levels on chart")
gapBigPct    = input.float(0.5,  "Big gap threshold (%)", step=0.1)
gapSmallPct  = input.float(0.1,  "Small gap threshold (%)", step=0.05)

// =====================================================================
//  Daily data (prior-day OHLC + daily SMAs + NR7 flag)
// =====================================================================
priorHigh  = request.security(syminfo.tickerid, "D", high[1],  lookahead=barmerge.lookahead_on)
priorLow   = request.security(syminfo.tickerid, "D", low[1],   lookahead=barmerge.lookahead_on)
priorClose = request.security(syminfo.tickerid, "D", close[1], lookahead=barmerge.lookahead_on)
priorRange = priorHigh - priorLow

dSMA20  = request.security(syminfo.tickerid, "D", ta.sma(close, 20)[1],  lookahead=barmerge.lookahead_on)
dSMA50  = request.security(syminfo.tickerid, "D", ta.sma(close, 50)[1],  lookahead=barmerge.lookahead_on)
dSMA200 = request.security(syminfo.tickerid, "D", ta.sma(close, 200)[1], lookahead=barmerge.lookahead_on)
aboveAll = priorClose > dSMA20 and priorClose > dSMA50 and priorClose > dSMA200
belowAll = priorClose < dSMA20 and priorClose < dSMA50 and priorClose < dSMA200

priorRangeMin7 = request.security(syminfo.tickerid, "D",
  ta.lowest(high - low, 7)[1],
  lookahead=barmerge.lookahead_on)
isNR7 = not na(priorRange) and not na(priorRangeMin7) and priorRange == priorRangeMin7

// =====================================================================
//  Day boundary + today's open + gap classification
// =====================================================================
isNewDay = ta.change(time("D")) != 0
var float todayOpen = na
if isNewDay
    todayOpen := open

gapPct = (todayOpen - priorClose) / priorClose * 100
gapClass = gapPct >  gapBigPct   ? "GapUp_Big"   :
           gapPct >  gapSmallPct ? "GapUp_Small" :
           gapPct < -gapBigPct   ? "GapDn_Big"   :
           gapPct < -gapSmallPct ? "GapDn_Small" : "Flat"

// =====================================================================
//  Background regime tint
// =====================================================================
regimeColor = aboveAll ? color.new(color.green, 92) :
              belowAll ? color.new(color.red,   92) :
                         color.new(color.gray,  95)
bgcolor(showRegime ? regimeColor : na, title="Regime")

// =====================================================================
//  Per-day projection LINES (drawn fresh each day, not connected across days)
// =====================================================================
var line lPDH    = na
var line lPDL    = na
var line lPDmid  = na
var line lPDHp25 = na
var line lPDHp50 = na
var line lPDHp1  = na
var line lPDLm25 = na
var line lPDLm50 = na
var line lPDLm1  = na

cPDH   = color.new(color.green,  0)
cPDL   = color.new(color.red,    0)
cMid   = color.new(color.gray,  50)
cP25   = color.new(color.green, 50)
cP50   = color.new(color.green, 30)
cP1    = color.new(color.green, 10)
cM25   = color.new(color.red,   50)
cM50   = color.new(color.red,   30)
cM1    = color.new(color.red,   10)

// On a NEW day: optionally delete prior lines, then create new ones for today
if showLevels and isNewDay and not na(priorHigh)
    if not keepHistory
        line.delete(lPDH)
        line.delete(lPDL)
        line.delete(lPDmid)
        line.delete(lPDHp25)
        line.delete(lPDHp50)
        line.delete(lPDHp1)
        line.delete(lPDLm25)
        line.delete(lPDLm50)
        line.delete(lPDLm1)
    midY = (priorHigh + priorLow) / 2
    lPDH    := line.new(bar_index, priorHigh, bar_index, priorHigh, color=cPDH, width=2)
    lPDL    := line.new(bar_index, priorLow,  bar_index, priorLow,  color=cPDL, width=2)
    lPDmid  := line.new(bar_index, midY,      bar_index, midY,      color=cMid, width=1, style=line.style_dotted)
    lPDHp25 := line.new(bar_index, priorHigh + 0.25 * priorRange, bar_index, priorHigh + 0.25 * priorRange, color=cP25, width=1, style=line.style_dotted)
    lPDHp50 := line.new(bar_index, priorHigh + 0.50 * priorRange, bar_index, priorHigh + 0.50 * priorRange, color=cP50, width=1, style=line.style_dotted)
    lPDHp1  := line.new(bar_index, priorHigh + 1.00 * priorRange, bar_index, priorHigh + 1.00 * priorRange, color=cP1,  width=1, style=line.style_dotted)
    lPDLm25 := line.new(bar_index, priorLow  - 0.25 * priorRange, bar_index, priorLow  - 0.25 * priorRange, color=cM25, width=1, style=line.style_dotted)
    lPDLm50 := line.new(bar_index, priorLow  - 0.50 * priorRange, bar_index, priorLow  - 0.50 * priorRange, color=cM50, width=1, style=line.style_dotted)
    lPDLm1  := line.new(bar_index, priorLow  - 1.00 * priorRange, bar_index, priorLow  - 1.00 * priorRange, color=cM1,  width=1, style=line.style_dotted)

// Within the day: extend each line's right end to the current bar
if showLevels and not isNewDay and not na(lPDH)
    line.set_x2(lPDH,    bar_index)
    line.set_x2(lPDL,    bar_index)
    line.set_x2(lPDmid,  bar_index)
    line.set_x2(lPDHp25, bar_index)
    line.set_x2(lPDHp50, bar_index)
    line.set_x2(lPDHp1,  bar_index)
    line.set_x2(lPDLm25, bar_index)
    line.set_x2(lPDLm50, bar_index)
    line.set_x2(lPDLm1,  bar_index)

// =====================================================================
//  Suggested-setup logic
// =====================================================================
var string setupHeader = ""
var string setupBody   = ""
setupColor = color.lime
if isNR7
    setupHeader := "🚫  SKIP — NR7 day yesterday"
    setupBody   := "96.6% chance today expands beyond prior range"
    setupColor  := color.orange
else if gapClass == "GapUp_Big" and aboveAll
    setupHeader := "✓  PUT credit spread"
    setupBody   := "Short strike near PDL − 0.25R\n(only ~10% of big-gap-up days touch PDL)"
    setupColor  := color.lime
else if gapClass == "GapDn_Big" and belowAll
    setupHeader := "✓  CALL credit spread"
    setupBody   := "Short strike near PDH + 0.25R\n(only ~10% of big-gap-down days touch PDH)"
    setupColor  := color.lime
else if (gapClass == "Flat" or gapClass == "GapUp_Small" or gapClass == "GapDn_Small") and (aboveAll or belowAll)
    setupHeader := "✓  IRON CONDOR"
    setupBody   := "Shorts at PDH + 0.25R and PDL − 0.25R\nWidth ≥ ±1% spot, target 50% credit"
    setupColor  := color.lime
else if gapClass == "GapUp_Big" and belowAll
    setupHeader := "⚠  Counter-trend gap up"
    setupBody   := "Gap UP into StrongDown — reduce or skip"
    setupColor  := color.yellow
else if gapClass == "GapDn_Big" and aboveAll
    setupHeader := "⚠  Counter-trend gap down"
    setupBody   := "Gap DOWN into StrongUp — reduce or skip"
    setupColor  := color.yellow
else
    setupHeader := "⚖  Mixed regime"
    setupBody   := "Half size or skip"
    setupColor  := color.silver

// =====================================================================
//  Status TABLE — bottom-left, always on top of bars
// =====================================================================
var table panel = table.new(position.bottom_left, 1, 7,
  bgcolor=color.new(#0e1116, 5),
  border_width=1, border_color=color.new(color.gray, 60),
  frame_width=1, frame_color=color.new(color.gray, 40))

if barstate.islast and showSetupTbl
    regimeStr = aboveAll ? "StrongUp" : belowAll ? "StrongDown" : "Mixed"
    regimeFG  = aboveAll ? color.lime : belowAll ? color.red : color.silver
    table.cell(panel, 0, 0, " Module 2 — 0DTE Credit Spread ",
      text_color=color.white, bgcolor=color.new(#1F4E78, 0),
      text_size=size.normal, text_halign=text.align_center)
    table.cell(panel, 0, 1, " Regime: " + regimeStr + (isNR7 ? "  |  NR7" : "") + " ",
      text_color=regimeFG, text_size=size.small, text_halign=text.align_left)
    table.cell(panel, 0, 2, " Gap: " + gapClass + "  (" + str.tostring(gapPct, "#.##") + "%) ",
      text_color=color.white, text_size=size.small, text_halign=text.align_left)
    table.cell(panel, 0, 3, " PDH: " + str.tostring(priorHigh, "#.##") + "    PDL: " + str.tostring(priorLow, "#.##") + "    R: " + str.tostring(priorRange, "#.##") + " ",
      text_color=color.white, text_size=size.small, text_halign=text.align_left)
    table.cell(panel, 0, 4, "  ────────────────────────  ",
      text_color=color.gray, text_size=size.small, text_halign=text.align_center)
    table.cell(panel, 0, 5, " " + setupHeader + " ",
      text_color=setupColor, text_size=size.normal, text_halign=text.align_left)
    table.cell(panel, 0, 6, " " + setupBody + " ",
      text_color=color.new(color.white, 20), text_size=size.small, text_halign=text.align_left)

// =====================================================================
//  Alerts
// =====================================================================
alertcondition(isNR7,                                   "M2: SKIP NR7",         "M2: NR7 detected — skip 0DTE today")
alertcondition(gapClass == "GapUp_Big" and aboveAll,    "M2: Sell PUT spread",  "M2: Big gap-up + StrongUp — sell put credit spread below PDL")
alertcondition(gapClass == "GapDn_Big" and belowAll,    "M2: Sell CALL spread", "M2: Big gap-down + StrongDown — sell call credit spread above PDH")
alertcondition((gapClass == "Flat" or gapClass == "GapUp_Small" or gapClass == "GapDn_Small") and (aboveAll or belowAll), "M2: Iron Condor", "M2: Flat / small gap — iron condor at PDH+0.25R / PDL-0.25R")

Module 3 — ORB Long

5-min chart. Auto-detects the 15-min opening range, applies regime + gap + open-above-PDH filters, and only fires the "LONG" entry shape if all filters pass. Stop = OR low. Exit at MOC.

⬇ Download .pine
Pine version: v5
Recommended timeframe: 5-min intraday
File: Module3_ORB_Long.pine
//@version=5
// =====================================================================
//  SPX Module 3 — Opening Range Breakout (Long Only)
//
//  What it plots:
//   • Background tint     — daily regime
//   • Aqua / fuchsia line — 15-min OR high / low (drawn after OR completes)
//   • Green ▲ "LONG"      — first break above OR high, only when filters pass
//   • Status label        — regime, gap, OR levels, setup status
//
//  Filter rules built in:
//   • Regime must be StrongUp (above 20 / 50 / 200 daily SMA)
//   • Gap must NOT be a big gap-down (gapPct ≥ -0.5%)
//   • "High-conviction" tag if today's open prints above prior-day high
//
//  Best used on a 5-min SPX chart.
// =====================================================================
indicator("SPX Module 3 — ORB Long", overlay=true, max_labels_count=10)

// === Inputs ===
orMinutes  = input.int(15, "Opening Range minutes", minval=5, maxval=60, step=5)
sessStart  = input.session("0930-1600", "Cash session (exchange time)")
showRegime = input.bool(true, "Tint background by regime")
showOR     = input.bool(true, "Show OR high/low")
showEntry  = input.bool(true, "Show breakout entry")

// === Session detection ===
inSession  = not na(time(timeframe.period, sessStart))
isFirstBar = inSession and not inSession[1]
orBars     = math.max(1, math.floor(orMinutes / math.max(timeframe.multiplier, 1)))
inOR       = inSession and ta.barssince(isFirstBar) < orBars

// === OR state machine ===
var float orHigh    = na
var float orLow     = na
var bool  orDone    = false
var bool  brokeUp   = false
var float entryPx   = na
var int   entryBar  = na
var float todayOpen = na

if isFirstBar
    orHigh    := high
    orLow     := low
    orDone    := false
    brokeUp   := false
    entryPx   := na
    entryBar  := na
    todayOpen := open

if inOR and not isFirstBar
    orHigh := math.max(orHigh, high)
    orLow  := math.min(orLow,  low)

if inSession and not inOR and not orDone
    orDone := true

// First break above OR high after OR completes
trigger = false
if orDone and inSession and not brokeUp and high > orHigh
    brokeUp  := true
    entryPx  := orHigh
    entryBar := bar_index
    trigger  := true

// === Daily regime + gap (from daily timeframe) ===
priorClose = request.security(syminfo.tickerid, "D", close[1], lookahead=barmerge.lookahead_on)
priorHigh  = request.security(syminfo.tickerid, "D", high[1],  lookahead=barmerge.lookahead_on)
dSMA20  = request.security(syminfo.tickerid, "D", ta.sma(close, 20)[1],  lookahead=barmerge.lookahead_on)
dSMA50  = request.security(syminfo.tickerid, "D", ta.sma(close, 50)[1],  lookahead=barmerge.lookahead_on)
dSMA200 = request.security(syminfo.tickerid, "D", ta.sma(close, 200)[1], lookahead=barmerge.lookahead_on)
aboveAll = priorClose > dSMA20 and priorClose > dSMA50 and priorClose > dSMA200
belowAll = priorClose < dSMA20 and priorClose < dSMA50 and priorClose < dSMA200

gapPct       = (todayOpen - priorClose) / priorClose * 100
filterOK     = aboveAll and gapPct > -0.5
strongFilter = filterOK and todayOpen > priorHigh

regimeColor = aboveAll ? color.new(color.green, 92) :
              belowAll ? color.new(color.red,   92) :
                         color.new(color.gray,  95)
bgcolor(showRegime ? regimeColor : na)

// === Plots ===
plot(showOR and orDone ? orHigh : na, "OR High",
     color=color.new(color.aqua, 0), linewidth=2, style=plot.style_linebr)
plot(showOR and orDone ? orLow  : na, "OR Low",
     color=color.new(color.fuchsia, 0), linewidth=2, style=plot.style_linebr)

validTrigger = trigger and filterOK
plotshape(showEntry and validTrigger, title="ORB Long Entry",
          style=shape.triangleup, location=location.belowbar,
          color=color.new(color.green, 0), size=size.normal, text="LONG")

// === Status label on latest bar ===
var label statusLbl = na
if barstate.islast
    label.delete(statusLbl)
    regimeStr = aboveAll ? "StrongUp" : belowAll ? "StrongDown" : "Mixed"
    setupTxt  = filterOK
                  ? (strongFilter ? "✅ HIGH-CONVICTION (open above PDH)"
                                   : "✅ STANDARD long-only setup")
                  : "🚫 SKIP — filters failed"
    statusTxt = brokeUp ? ("📈 ENTERED at " + str.tostring(entryPx, "#.##") +
                           " — exit MOC, stop OR Low")
                        : (orDone ? "⏳ Waiting for break above OR High..."
                                  : "⏳ OR forming...")
    fullText  = "Module 3 — ORB Long\n" +
      "─────────────────\n" +
      "Regime: " + regimeStr + "\n" +
      "Gap:    " + str.tostring(gapPct, "#.##") + "%\n" +
      "OR Hi:  " + str.tostring(orHigh, "#.##") + "\n" +
      "OR Lo:  " + str.tostring(orLow,  "#.##") + "\n" +
      "─────────────────\n" + setupTxt + "\n" + statusTxt
    statusLbl := label.new(bar_index, high, text=fullText,
      style=label.style_label_down,
      color=color.new(color.black, 20),
      textcolor=color.white, size=size.normal)

// === Alerts ===
alertcondition(validTrigger, "M3: ORB Long Entry",
               "M3: ORB break above OR high in StrongUp regime — long entry, exit MOC")