Strategy · Pine Script v6 · On the chart

MTF Confluence Spot Strategy

by MuhammadMuhamid

A long-only spot strategy where each filter has its own timeframe and on/off switch, and a trade needs every enabled filter to pass.

View on GitHub

GitHub stars1on the repository
Pine versionv6current
Lines of code1,632
Settings40inputs
LicenceNone statedfile: MPL-2.0
Last update13 days ago17 Oct 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 strategy, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart. The trades and results appear in the Strategy Tester tab; adjust the settings with the gear icon.

Source Code

1,632 lines of Pine Script v6 from platform/Mystrategy/MTF_Confluence_Spot_Strategy.pine, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

MTF_Confluence_Spot_Strategy.pine
//@version=6
// =====================================================================================================================
//  MTF CONFLUENCE SPOT STRATEGY  v2  —  modular, per-filter timeframes, toggle-selected confluence
// =====================================================================================================================
//  LONG ONLY. SPOT ONLY. No strategy.short anywhere in this file.
//
//  v2 CHANGES vs v1
//  ----------------
//   * Every gate (G1-G4) and every structure filter (S1-S7) now has its OWN timeframe input. Nothing is hardwired
//     to 1H or 15m any more — set each filter to whatever TF you want to test.
//   * Every filter lives in its OWN numbered section with its own enable toggle.
//   * The Confluence Weights / score / threshold engine is GONE. Selection is now pure AND-of-enabled-filters:
//     a filter you switch on must pass; a filter you switch off is ignored entirely. What you tick is what you get.
//   * Sections ported from "MA + R:R Strategy" so both scripts tune the same way:
//       1 General · 1b Profit-run limit · 6e Volume filter · 6f Higher-high structure · 7 Entries-Long ·
//       9b R:R swing-low stop + ratio TP · 11 Peak/spike filters · 20 AlphaTrend · Heikin-Ashi signal option
//   * Two selectable stop/TP engines: the MA_RR-style R:R bracket (default) or the v1 structural-stop engine.
//
//  ATTRIBUTION (mandatory — do not strip)
//  -------------------------------------
//    "Supertrend"                                  — TradingView / Everget style ATR trailing stop
//    "Support and Resistance (High Volume Boxes)"  — (c) ChartPrime        — Mozilla Public License 2.0
//    "Liquidity Sweeps [LuxAlgo]"                  — (c) LuxAlgo           — CC BY-NC-SA 4.0
//    "Pivot Point SuperTrend"                      — (c) LonesomeTheBlue   — Mozilla Public License 2.0
//    "Buyside & Sellside Liquidity [LuxAlgo]"      — (c) LuxAlgo           — CC BY-NC-SA 4.0
//    "Supply and Demand Visible Range [LuxAlgo]"   — (c) LuxAlgo           — CC BY-NC-SA 4.0
//    "Pivot Points High Low [LuxAlgo]"             — (c) LuxAlgo           — CC BY-NC-SA 4.0
//    "Bollinger Bands" / "Relative Strength Index" — (c) TradingView built-ins
//    "Volume Flow Indicator [LazyBear]"            — (c) LazyBear
//    "AlphaTrend"                                  — (c) KivancOzbilgic
//  CC BY-NC-SA 4.0 is NON-COMMERCIAL: this script may not be sold, licensed for money, or bundled into a paid
//  product or service. Personal / non-commercial use only. Derivatives must be shared alike.
//
//  DELIBERATE DEVIATIONS (see README)
//  ----------------------------------
//   1) Supply & Demand is rewritten from "visible range" to a FIXED ROLLING LOOKBACK, and intrabar volume
//      distribution is replaced with bar-level volume spread across the bins each bar's high-low range spans.
//      Zones are close to, but not identical to, the LuxAlgo original. This is what makes it backtestable.
//   2) HTF components draw nothing — Pine cannot create boxes/lines/labels inside request.security(). HTF values
//      drive the LOGIC; the chart-timeframe instance of each component drives the VISUALS.
//   3) RSI / VFI have no separate pane (overlay strategy). Both are in the Data Window and the debug table.
// =====================================================================================================================

strategy("MTF Confluence Spot Strategy", "MTF Conf", overlay = true,
     default_qty_type        = strategy.cash,
     default_qty_value       = 1000,
     initial_capital         = 10000,
     currency                = currency.USD,
     commission_type         = strategy.commission.percent,
     commission_value        = 0.1,
     slippage                = 2,
     pyramiding              = 0,
     calc_on_every_tick      = false,
     calc_on_order_fills     = false,
     process_orders_on_close = true,
     max_bars_back           = 1000,
     max_boxes_count         = 500,
     max_lines_count         = 500,
     max_labels_count        = 500)


// =====================================================================================================================
//  GROUPS
// =====================================================================================================================
string G_GEN   = "1 General"
string G_RUN   = "1b Profit-run limit (winning cluster cap)"
string G_RISK  = "2 Risk & position sizing"
string G_G1    = "3 G1 — Supertrend regime"
string G_G2    = "4 G2 — RSI regime"
string G_G3    = "5 G3 — VFI (volume flow) regime"
string G_G4    = "6 G4 — Price at/above SR support"
string G_VOL   = "6e Volume filter"
string G_HH    = "6f Higher-high structure"
string G_S1    = "7 S1 — Liquidity sweep [LuxAlgo]"
string G_S2    = "8 S2 — Demand zone (Supply/Demand)"
string G_S3    = "9 S3 — Support box [ChartPrime]"
string G_S4    = "10 S4 — Supertrend structure"
string G_S5    = "11 S5 — Pivot Point Supertrend"
string G_S6    = "12 S6 — Sellside liquidity [LuxAlgo]"
string G_S7    = "13 S7 — Pivot low [LuxAlgo]"
string G_TRIG  = "14 Entry trigger (Bollinger / Supertrend)"
string G_ENT   = "7b Entries — Long"
string G_RR    = "9b R:R — swing-low stop + ratio TP"
string G_STRUCT= "9c Structure-stop engine (alternative)"
string G_SPIKE = "11b Peak / spike filters (anti-chase)"
string G_AT    = "20 Indicator — AlphaTrend"
string G_EXIT  = "21 Exits — signal based"
string G_VIS   = "30 Visualization"
string G_DBG   = "31 Debug"


// =====================================================================================================================
//  1 GENERAL
// =====================================================================================================================
useBarConfirm        = input.bool(true, "Signals on confirmed bar only", group = G_GEN,
     tooltip = "OFF = entry conditions can go true every tick → same-bar stop/re-entry loops and many webhooks. Keep ON for live.")
ordersOnConfirmedBar = input.bool(true, "New entries: bar close only (3Commas)", group = G_GEN,
     tooltip = "When ON, only strategy.entry waits for barstate.isconfirmed. Exits are not delayed.")
cooldownBarsAfterExit= input.int(0, "Min bars after exit before new entry", minval = 0, maxval = 200, group = G_GEN)

htfClosed = input.bool(true, "HTF reads: last CLOSED bar only (no repaint)", group = G_GEN,
     tooltip = "ON  = every higher-timeframe value is taken from the last CLOSED bar ([1] offset). Backtest matches live. STRONGLY recommended.\nOFF = reads the in-progress HTF bar: signals will look better historically and repaint live. Only use to eyeball what a filter is doing.")
useHeikin = input.bool(false, "Signals from Heikin Ashi candles", group = G_GEN,
     tooltip = "Feeds Heikin-Ashi OHLC into the entry TRIGGER (Bollinger / Supertrend flip) instead of real candles. Smoother, later, fewer false triggers. Stops, targets and fills always use REAL prices.")

useChoppyFilter = input.bool(false, "Pause entries after N consecutive losses", group = G_GEN,
     tooltip = "After N losing trades in a row, block new entries for the pause below. Detects choppy / distribution phases that still look bullish on the macro filters.")
maxConsecLoss   = input.int(2,  "Max consecutive losses before pause", minval = 1, maxval = 10, group = G_GEN)
choppyPauseBars = input.int(20, "Pause duration (bars) after max losses hit", minval = 1, maxval = 500, group = G_GEN,
     tooltip = "On a 5m chart 20 bars = 100 minutes.")

signal_delivery  = input.string("Off", "Signal delivery", options = ["3Commas Signal Bot", "Custom webhook bot", "Off"], group = G_GEN,
     tooltip = "3Commas: add a TradingView alert manually with 3Commas JSON + webhook. Custom: JSON via Pine alert().")
string ALERT_SECRET   = input.string("REPLACE_ME", "[Custom bot] secret → WEBHOOK_SECRET", group = G_GEN, display = display.none)
float  alert_buy_usdt = input.float(800.0, "[Custom bot] BUY quote USDT", minval = 1, step = 1, group = G_GEN, display = display.none)
float  alert_sell_base= input.float(0.001, "[Custom bot] SELL base qty", minval = 0.00000001, step = 0.001, group = G_GEN, display = display.none)
string alert_symbol   = input.string("ZECUSDT", "[Custom bot] symbol", group = G_GEN, display = display.none)
use_custom_webhook    = signal_delivery == "Custom webhook bot"


// =====================================================================================================================
//  1b PROFIT-RUN LIMIT
// =====================================================================================================================
useRunLimit     = input.bool(false, "Stop entries after a profitable winning run", group = G_RUN,
     tooltip = "After N consecutive winners that together made at least the profit % below, pause new entries. Targets the last 'top' trade of a winning cluster. A loss resets the streak.")
runLimTrades    = input.int(5,   "Winning trades in the run", minval = 1, maxval = 50, group = G_RUN)
runLimProfitPct = input.float(9.0,"Min combined profit % of the run", step = 0.5, minval = 0, group = G_RUN)
runLimPauseBars = input.int(30,  "Pause new entries for N bars after cap hit", minval = 1, maxval = 5000, group = G_RUN)


// =====================================================================================================================
//  2 RISK & POSITION SIZING
// =====================================================================================================================
sizingMode = input.string("Fixed USDT", "Sizing Mode", options = ["Fixed USDT", "Risk % of Equity"], group = G_RISK)
tradeAmt   = input.float(1000.0, "Trade Amount (USDT)", group = G_RISK, minval = 1, step = 50)
riskPct    = input.float(1.0,    "Risk % of Equity",    group = G_RISK, minval = 0.01, maxval = 100, step = 0.1)
atrLenRisk = input.int(14, "ATR length (risk)", minval = 1, group = G_RISK)
stopEngine = input.string("R:R (swing low + ratio)", "Stop / TP engine",
     options = ["R:R (swing low + ratio)", "Structure (S1-S7 levels)"], group = G_RISK,
     tooltip = "R:R  = MA_RR-style bracket: stop below the recent swing low, target at a reward:risk ratio. Simple, predictable, easy to optimise.\nStructure = v1 engine: the stop is taken from whichever enabled S1-S7 filter level sits closest below entry, with R-multiple partials.")


// =====================================================================================================================
//  3 G1 — SUPERTREND REGIME
// =====================================================================================================================
useG1     = input.bool(true, "G1 · Enable — Supertrend must be bullish", group = G_G1)
g1_tf     = input.timeframe("60", "G1 timeframe", group = G_G1, tooltip = "Blank = chart timeframe.")
g1_atrLen = input.int(10,  "G1 ATR period", minval = 1, group = G_G1)
g1_mult   = input.float(3.0,"G1 ATR multiplier", step = 0.1, minval = 0.1, group = G_G1)
g1_chgAtr = input.bool(true,"G1 change ATR calculation method?", group = G_G1)

// =====================================================================================================================
//  4 G2 — RSI REGIME
// =====================================================================================================================
useG2    = input.bool(true, "G2 · Enable — RSI above threshold", group = G_G2)
g2_tf    = input.timeframe("60", "G2 timeframe", group = G_G2)
g2_len   = input.int(14, "G2 RSI length", minval = 1, group = G_G2)
g2_thr   = input.float(50.0, "G2 RSI threshold", minval = 0, maxval = 100, step = 1, group = G_G2)
g2_max   = input.float(100.0,"G2 RSI max (blocks overbought chasing)", minval = 0, maxval = 100, step = 1, group = G_G2,
     tooltip = "Set to e.g. 70 to refuse entries when RSI is already exhausted. 100 = no upper bound.")

// =====================================================================================================================
//  5 G3 — VFI REGIME
// =====================================================================================================================
useG3     = input.bool(true, "G3 · Enable — VFI above level", group = G_G3)
g3_tf     = input.timeframe("60", "G3 timeframe", group = G_G3)
g3_len    = input.int(130, "G3 VFI length", minval = 1, group = G_G3,
     tooltip = "LazyBear default 130. Needs ~130 CLOSED bars of this timeframe before it means anything; until then the gate reads false.")
g3_coef   = input.float(0.2, "G3 VFI coef", step = 0.05, group = G_G3)
g3_vcoef  = input.float(2.5, "G3 VFI max volume cutoff", step = 0.1, group = G_G3)
g3_sig    = input.int(5, "G3 VFI signal length", minval = 1, group = G_G3)
g3_smooth = input.bool(false, "G3 smooth VFI", group = G_G3)
g3_level  = input.float(0.0, "G3 VFI must be above", step = 0.5, group = G_G3)

// =====================================================================================================================
//  6 G4 — PRICE AT/ABOVE SR SUPPORT
// =====================================================================================================================
useG4      = input.bool(false, "G4 · Enable — price at/above SR support", group = G_G4)
g4_tf      = input.timeframe("60", "G4 timeframe", group = G_G4)
g4_lb      = input.int(20, "G4 SR lookback (pivot length)", minval = 1, group = G_G4,
     tooltip = "Pivot confirmation delay = this many bars of the G4 timeframe.")
g4_volLen  = input.int(2,  "G4 delta-volume filter length", minval = 1, group = G_G4)
g4_boxW    = input.float(1.0, "G4 box width", minval = 0, step = 0.1, group = G_G4)
g4_atrMult = input.float(1.0, "G4 tolerance (ATR mult below support)", minval = 0, step = 0.1, group = G_G4)

// =====================================================================================================================
//  6e VOLUME FILTER
// =====================================================================================================================
useVolumeFilter = input.bool(false, "Require above-average volume on entry", group = G_VOL)
volTf           = input.timeframe("15", "Volume timeframe (blank = chart)", group = G_VOL)
volMaLen        = input.int(50, "Volume SMA length", minval = 5, group = G_VOL)
volMultMin      = input.float(1.5, "Min volume (× SMA)", step = 0.1, minval = 0.1, group = G_VOL,
     tooltip = "1.0 = at-least-average volume; 1.5 = 50% above average.")

// =====================================================================================================================
//  6f HIGHER-HIGH STRUCTURE
// =====================================================================================================================
useHhStructure = input.bool(false, "Require higher-high structure", group = G_HH,
     tooltip = "Last confirmed pivot high on the chosen TF must be above the prior pivot high.")
hhTf           = input.timeframe("15", "Higher-high timeframe", group = G_HH)
hhPivotLen     = input.int(10, "Pivot length (HH TF)", minval = 3, maxval = 30, group = G_HH)

// =====================================================================================================================
//  7 S1 — LIQUIDITY SWEEP
// =====================================================================================================================
useS1     = input.bool(true, "S1 · Enable — bullish liquidity sweep", group = G_S1)
s1_tf     = input.timeframe("15", "S1 timeframe", group = G_S1)
s1_len    = input.int(5, "S1 swings (pivot length)", minval = 1, group = G_S1,
     tooltip = "Pivot confirmation delay = this many bars of the S1 timeframe. 5 bars on 15m = 75 minutes.")
s1_maxAge = input.int(12, "S1 max bars since sweep", minval = 1, group = G_S1,
     tooltip = "The sweep only counts as active if it confirmed within this many CLOSED bars of the S1 timeframe.")

// =====================================================================================================================
//  8 S2 — DEMAND ZONE
// =====================================================================================================================
useS2    = input.bool(true, "S2 · Enable — inside / just above demand zone", group = G_S2)
s2_tf    = input.timeframe("15", "S2 timeframe", group = G_S2)
s2_lb    = input.int(300, "S2 lookback bars (rolling window)", minval = 20, maxval = 900, group = G_S2,
     tooltip = "Replaces LuxAlgo's visible-range window with a fixed rolling window so results are reproducible.")
s2_res   = input.int(50, "S2 resolution (profile bins)", minval = 2, maxval = 200, group = G_S2)
s2_thr   = input.float(10.0, "S2 threshold %", minval = 0, maxval = 100, step = 0.5, group = G_S2)
s2_every = input.int(10, "S2 recalculate every N bars", minval = 1, group = G_S2,
     tooltip = "The volume profile is expensive. Recomputing every N bars bounds execution time; levels are held between recomputes.")
s2_prox  = input.float(1.0, "S2 proximity above demand top (ATR mult)", minval = 0, step = 0.25, group = G_S2)

// =====================================================================================================================
//  9 S3 — SUPPORT BOX
// =====================================================================================================================
useS3     = input.bool(true, "S3 · Enable — at / above support box", group = G_S3)
s3_tf     = input.timeframe("15", "S3 timeframe", group = G_S3)
s3_lb     = input.int(20, "S3 SR lookback (pivot length)", minval = 1, group = G_S3)
s3_volLen = input.int(2,  "S3 delta-volume filter length", minval = 1, group = G_S3)
s3_boxW   = input.float(1.0, "S3 box width", minval = 0, step = 0.1, group = G_S3)
s3_prox   = input.float(1.5, "S3 proximity above support top (ATR mult)", minval = 0, step = 0.25, group = G_S3)

// =====================================================================================================================
//  10 S4 — SUPERTREND STRUCTURE
// =====================================================================================================================
useS4     = input.bool(true, "S4 · Enable — Supertrend bullish", group = G_S4)
s4_tf     = input.timeframe("15", "S4 timeframe", group = G_S4)
s4_atrLen = input.int(10, "S4 ATR period", minval = 1, group = G_S4)
s4_mult   = input.float(3.0, "S4 ATR multiplier", step = 0.1, minval = 0.1, group = G_S4)
s4_chgAtr = input.bool(true, "S4 change ATR calculation method?", group = G_S4)

// =====================================================================================================================
//  11 S5 — PIVOT POINT SUPERTREND
// =====================================================================================================================
useS5    = input.bool(true, "S5 · Enable — Pivot Point Supertrend bullish", group = G_S5)
s5_tf    = input.timeframe("15", "S5 timeframe", group = G_S5)
s5_prd   = input.int(2, "S5 pivot point period", minval = 1, maxval = 50, group = G_S5)
s5_factor= input.float(3.0, "S5 ATR factor", minval = 0.1, step = 0.1, group = G_S5)
s5_pd    = input.int(10, "S5 ATR period", minval = 1, group = G_S5)

// =====================================================================================================================
//  12 S6 — SELLSIDE LIQUIDITY
// =====================================================================================================================
useS6     = input.bool(true, "S6 · Enable — sellside liquidity resting below", group = G_S6)
s6_tf     = input.timeframe("15", "S6 timeframe", group = G_S6)
s6_len    = input.int(7, "S6 detection length", minval = 3, maxval = 13, group = G_S6)
s6_margin = input.float(6.9, "S6 margin", minval = 4, maxval = 9, step = 0.1, group = G_S6)
s6_vis    = input.int(3, "S6 # levels kept", minval = 1, maxval = 50, group = G_S6)
s6_prox   = input.float(5.0, "S6 max distance to level (ATR mult)", minval = 0, step = 0.5, group = G_S6)

// =====================================================================================================================
//  13 S7 — PIVOT LOW
// =====================================================================================================================
useS7  = input.bool(true, "S7 · Enable — above last confirmed pivot low", group = G_S7)
s7_tf  = input.timeframe("15", "S7 timeframe", group = G_S7)
s7_len = input.int(20, "S7 pivot length", minval = 1, group = G_S7,
     tooltip = "LuxAlgo's original default is 50. 20 cuts the confirmation lag: 20 bars on 15m = 5 hours.")

// =====================================================================================================================
//  14 ENTRY TRIGGER
// =====================================================================================================================
useTrigger  = input.bool(true, "Enable entry trigger", group = G_TRIG,
     tooltip = "OFF = entries fire on the first bar where all enabled filters agree, with no timing trigger at all. Expect many more trades.")
trig_tf     = input.timeframe("", "Trigger timeframe (blank = chart)", group = G_TRIG)
trigMode    = input.string("Bollinger Reclaim", "Trigger mode", group = G_TRIG,
     options = ["Bollinger Reclaim", "Bollinger Basis Cross", "Supertrend Flip", "BB + Supertrend (both)"])
bbReclaimLb = input.int(3, "BB reclaim lookback (bars)", minval = 1, group = G_TRIG)
bb_len      = input.int(20, "BB length", minval = 1, group = G_TRIG)
bb_maTyp    = input.string("SMA", "BB basis MA type", options = ["SMA", "EMA", "SMMA (RMA)", "WMA", "VWMA"], group = G_TRIG)
bb_mult     = input.float(2.0, "BB stdev", minval = 0.001, maxval = 50, step = 0.1, group = G_TRIG)
trig_stLen  = input.int(10, "Trigger Supertrend ATR period", minval = 1, group = G_TRIG)
trig_stMult = input.float(3.0, "Trigger Supertrend multiplier", step = 0.1, minval = 0.1, group = G_TRIG)
reqST5      = input.bool(true, "Require trigger-TF Supertrend bullish", group = G_TRIG)

// =====================================================================================================================
//  7b ENTRIES — LONG
// =====================================================================================================================
long_en          = input.bool(true, "Enable longs", group = G_ENT)
useBodyFilter    = input.bool(true, "Body filter: reject bearish entry bar", group = G_ENT,
     tooltip = "Skips entries where the trigger bar is bearish AND its body (open−close) exceeds the multiple of ATR below. A big bearish close on the setup bar is a momentum warning.")
entryBodyAtrMult = input.float(0.4, "Body filter: max bearish body size (× ATR)", step = 0.05, minval = 0.05, group = G_ENT)
useOneTradePerSignal = input.bool(false, "One trade per AlphaTrend / trigger buy signal", group = G_ENT,
     tooltip = "After a long is taken, no new long until a FRESH buy event fires on an enabled indicator.")

// =====================================================================================================================
//  9b R:R — SWING-LOW STOP + RATIO TP
// =====================================================================================================================
rrSwingLb    = input.int(20, "Swing low lookback (bars)", minval = 2, maxval = 200, group = G_RR)
rrBufAtr     = input.float(1.0, "SL buffer below swing low (× ATR)", step = 0.05, minval = 0.0, group = G_RR)
rrRatio      = input.float(2.0, "Reward : Risk ratio (TP)", step = 0.25, minval = 0.25, group = G_RR)
minSlDistAtr = input.float(0.25, "Floor: min SL distance from entry (× ATR)", step = 0.05, minval = 0.05, group = G_RR)
rrUsePartialTp = input.bool(false, "Take partial TPs (%) — TP1 / TP2 + runner", group = G_RR)
rrTp1Pct  = input.float(2.0,  "TP1 — price move %", step = 0.1, minval = 0.1, group = G_RR, inline = "rtp1")
rrTp1Size = input.float(40.0, "close % of pos",     step = 5, minval = 1, maxval = 100, group = G_RR, inline = "rtp1")
rrTp2Pct  = input.float(4.0,  "TP2 — price move %", step = 0.1, minval = 0.1, group = G_RR, inline = "rtp2")
rrTp2Size = input.float(30.0, "close % of pos",     step = 5, minval = 1, maxval = 100, group = G_RR, inline = "rtp2")
rrUseTrailSl  = input.bool(false, "Trailing stop (%)", group = G_RR)
rrTrailPct    = input.float(3.0, "Trailing stop distance (%)", step = 0.1, minval = 0.1, group = G_RR)
rrTrailActPct = input.float(0.0, "Trailing: activate after +% in profit (0 = once green)", step = 0.1, minval = 0.0, group = G_RR)
rrUseBE       = input.bool(true, "Move stop to break-even after +R", group = G_RR)
rrBeAfterR    = input.float(1.0, "Break-even trigger (R)", step = 0.25, minval = 0.1, group = G_RR)
rrBeOffR      = input.float(0.1, "Break-even offset (R)", step = 0.05, minval = 0, group = G_RR)
plotRR        = input.bool(true, "Plot R:R levels (live SL + TP)", group = G_RR)

// =====================================================================================================================
//  9c STRUCTURE-STOP ENGINE (alternative)
// =====================================================================================================================
slSource   = input.string("Nearest structure", "Stop source", group = G_STRUCT,
     options = ["Nearest structure", "Furthest structure", "S1 sweep low", "S2 demand bottom", "S3 support bottom", "ATR"])
slAtrMult  = input.float(2.0, "ATR stop multiplier", minval = 0.1, step = 0.1, group = G_STRUCT)
slBufAtr   = input.float(0.25,"SL buffer (ATR mult)", minval = 0, step = 0.05, group = G_STRUCT)
minStopPct = input.float(0.3, "Min stop distance %", minval = 0, step = 0.05, group = G_STRUCT)
maxStopPct = input.float(3.0, "Max stop distance %", minval = 0.1, step = 0.1, group = G_STRUCT)
tooTight   = input.string("Widen", "On too-tight stop", options = ["Widen", "Skip Trade"], group = G_STRUCT)
stTp1R   = input.float(1.0, "TP1 R multiple", minval = 0.1, step = 0.1, group = G_STRUCT)
stTp1Pct = input.float(40.0,"TP1 % of position", minval = 1, maxval = 99, step = 1, group = G_STRUCT)
stTp2R   = input.float(2.0, "TP2 R multiple", minval = 0.1, step = 0.1, group = G_STRUCT)
stTp2Pct = input.float(30.0,"TP2 % of position", minval = 1, maxval = 99, step = 1, group = G_STRUCT)
stTp3R   = input.float(3.0, "TP3 R multiple (runner)", minval = 0.1, step = 0.1, group = G_STRUCT)
stUsePartials = input.bool(true, "Enable partial take profit", group = G_STRUCT)
pctBasis = input.string("Original Position", "Partial % basis", options = ["Original Position", "Remaining Position"], group = G_STRUCT,
     tooltip = "Pine's qty_percent is a % of the position REMAINING when the exit executes. 'Original Position' converts your numbers for you.")

// =====================================================================================================================
//  11b PEAK / SPIKE FILTERS
// =====================================================================================================================
usePeakRun   = input.bool(false, "Block entries after parabolic run", group = G_SPIKE,
     tooltip = "Reject a long when price has already risen more than the limit over the last N bars (blow-off top).")
runLb        = input.int(12, "Run lookback (bars)", minval = 2, maxval = 500, group = G_SPIKE)
runMode      = input.string("Percent", "Run measured in", options = ["Percent", "ATR"], group = G_SPIKE)
runMaxPct    = input.float(25.0, "Max rise over lookback (%)", step = 0.5, minval = 0.1, group = G_SPIKE)
runMaxAtr    = input.float(6.0,  "Max rise over lookback (× ATR)", step = 0.5, minval = 0.1, group = G_SPIKE)
useAtrSpike  = input.bool(false, "Block entries on ATR volatility spikes", group = G_SPIKE)
atrSpikeAvgLen  = input.int(20, "ATR spike: average lookback (bars)", minval = 2, group = G_SPIKE)
atrSpikeMaxMult = input.float(1.8, "ATR spike: max ATR (× average)", step = 0.1, minval = 1.0, group = G_SPIKE)
useWickFilter = input.bool(false, "Block entries with large upper wick (rejection)", group = G_SPIKE)
wickMaxAtr    = input.float(2.0, "Max upper wick (× ATR)", step = 0.05, minval = 0.05, group = G_SPIKE)
useExtFilter = input.bool(false, "Block entries too far above MA (over-extension)", group = G_SPIKE)
extRefLen    = input.int(10, "Reference EMA length", minval = 1, group = G_SPIKE)
extMode      = input.string("ATR", "Extension distance mode", options = ["ATR", "Percent"], group = G_SPIKE)
extMaxAtr    = input.float(2.0, "Max distance above EMA (× ATR)", step = 0.1, minval = 0.1, group = G_SPIKE)
extMaxPct    = input.float(5.0, "Max distance above EMA (%)", step = 0.1, minval = 0.1, group = G_SPIKE)

// =====================================================================================================================
//  20 INDICATOR — ALPHATREND
// =====================================================================================================================
at_en      = input.bool(false, "Enable AlphaTrend", group = G_AT)
at_primary = input.bool(false, "AlphaTrend is PRIMARY (entry trigger)", group = G_AT,
     tooltip = "ON = an AlphaTrend buy crossover TRIGGERS entries (replaces the section-14 trigger). OFF = confirming FILTER only (AlphaTrend must be rising).")
at_coeff   = input.float(1.0, "Multiplier", step = 0.1, group = G_AT)
at_ap      = input.int(50, "Common Period", minval = 1, group = G_AT)
at_src     = input.source(close, "Source", group = G_AT)
at_novol   = input.bool(false, "Change calc (no volume data)?", group = G_AT)
at_tf      = input.timeframe("", "Signal timeframe (blank = chart)", group = G_AT)
at_useExit = input.bool(false, "Exit long on AlphaTrend SELL", group = G_AT)
plotAlphaTrend = input.bool(false, "Plot AlphaTrend lines", group = G_AT)

// =====================================================================================================================
//  21 EXITS
// =====================================================================================================================
exitOnG1Flip = input.bool(false, "Exit when G1 Supertrend turns bearish", group = G_EXIT)
exitOnS4Flip = input.bool(false, "Exit when S4 Supertrend turns bearish", group = G_EXIT)
maxBarsTrade = input.int(0, "Max bars in trade (0 = off)", minval = 0, group = G_EXIT)
useHlBreakExit = input.bool(false, "Exit on confirmed break of last pivot low", group = G_EXIT)
hlBreakTf      = input.timeframe("15", "HL break pivot TF", group = G_EXIT)
hlBreakPivLen  = input.int(10, "HL break pivot length", minval = 3, maxval = 30, group = G_EXIT)

// =====================================================================================================================
//  30 VISUALS  /  31 DEBUG
// =====================================================================================================================
maxDrawBars  = input.int(500, "Max historical bars for drawings", group = G_VIS, minval = 50,
     tooltip = "Signal logic always runs on every bar. Only DRAWING is restricted to the most recent N bars.")
showST       = input.bool(true,  "Show chart-TF Supertrend", group = G_VIS)
showSTfill   = input.bool(false, "     Supertrend highlighter", group = G_VIS)
showSTsig    = input.bool(true,  "     Supertrend buy/sell labels", group = G_VIS)
showSR       = input.bool(true,  "Show SR high-volume boxes [ChartPrime]", group = G_VIS)
showLS       = input.bool(true,  "Show liquidity sweeps [LuxAlgo]", group = G_VIS)
showPPST     = input.bool(false, "Show Pivot Point Supertrend", group = G_VIS)
showBSL      = input.bool(false, "Show buyside & sellside liquidity (heavy)", group = G_VIS)
showSD       = input.bool(false, "Show supply & demand zones (heavy)", group = G_VIS)
showPPHL     = input.bool(false, "Show pivot points high/low (heavy)", group = G_VIS)
showBB       = input.bool(true,  "Show Bollinger Bands", group = G_VIS)
showEntryLbl = input.bool(true,  "Label entry bars", group = G_VIS)
capSR   = input.int(40, "Cap · SR boxes",          group = G_VIS, minval = 1, maxval = 200)
capLS   = input.int(60, "Cap · liq-sweep objects", group = G_VIS, minval = 1, maxval = 200)
capBSL  = input.int(30, "Cap · BSL objects",       group = G_VIS, minval = 1, maxval = 200)
capPP   = input.int(60, "Cap · pivot HL objects",  group = G_VIS, minval = 1, maxval = 200)
capLbl  = input.int(50, "Cap · entry labels",      group = G_VIS, minval = 1, maxval = 200)

showTable = input.bool(true, "Show debug table", group = G_DBG)
tblSize   = input.string("Small", "Table text size", options = ["Tiny", "Small", "Normal"], group = G_DBG)
showFunnel= input.bool(true, "Publish entry-funnel counters (data window)", group = G_DBG,
     tooltip = "Cumulative counts of how many bars pass each stage. Read these when you get too few or too many trades — they show which filter is doing the rejecting.")


// =====================================================================================================================
//  UDTs
// =====================================================================================================================
type sr_zone
    box   bx
    bool  isSup

type bsl_lvl
    line  ln
    line  lne
    box   bxt
    float prc
    float top
    float bot
    bool  brk

type sd_area
    box  bx
    line avg


// =====================================================================================================================
//  HELPERS
// =====================================================================================================================
// Blank timeframe input = chart timeframe (request.security needs a real TF string).
f_tfn(simple string t) => t == "" ? timeframe.period : t

// Non-repaint selector. htfClosed ON  -> value of the LAST CLOSED bar of the requested TF ([1] offset).
//                       htfClosed OFF -> in-progress bar (repaints live; for visual inspection only).
// Every request.security() in this script uses lookahead = barmerge.lookahead_off. There is no lookahead_on.
f_c(float x)  => htfClosed ? x[1] : x
f_cb(bool x)  => htfClosed ? x[1] : x

f_b2f(bool b) => b ? 1.0 : 0.0

// ---------------------------------------------------------------------------------------------------------------{
//  Supertrend — port of "Supertrend" (v4 -> v6)
// ---------------------------------------------------------------------------------------------------------------{
f_supertrend(simple int periods, float src, simple float mult, simple bool changeATR) =>
    float atrClassic = ta.sma(ta.tr, periods)
    float atrStd     = ta.atr(periods)
    float st_atr     = changeATR ? atrStd : atrClassic
    float st_up  = src - (mult * st_atr)
    float st_up1 = nz(st_up[1], st_up)
    st_up := close[1] > st_up1 ? math.max(st_up, st_up1) : st_up
    float st_dn  = src + (mult * st_atr)
    float st_dn1 = nz(st_dn[1], st_dn)
    st_dn := close[1] < st_dn1 ? math.min(st_dn, st_dn1) : st_dn
    int st_trend = 1
    st_trend := nz(st_trend[1], st_trend)
    st_trend := st_trend == -1 and close > st_dn1 ? 1 : st_trend == 1 and close < st_up1 ? -1 : st_trend
    float st_line = st_trend == 1 ? st_up : st_dn
    bool  st_buy  = st_trend ==  1 and st_trend[1] == -1
    bool  st_sell = st_trend == -1 and st_trend[1] ==  1
    [st_trend, st_line, st_up, st_dn, st_buy, st_sell]

// ---------------------------------------------------------------------------------------------------------------{
//  Pivot Point Supertrend — (c) LonesomeTheBlue (v4 -> v6)
//  PIVOT LAG: ta.pivothigh/low(prd, prd) confirm `prd` bars AFTER the pivot forms. Real; not removed with offsets.
// ---------------------------------------------------------------------------------------------------------------{
f_pivotSupertrend(simple int prd, simple float factor, simple int pd) =>
    float ppst_ph  = ta.pivothigh(prd, prd)
    float ppst_pl  = ta.pivotlow(prd, prd)
    float ppst_atr = ta.atr(pd)
    var float ppst_center = na
    float ppst_lastpp = not na(ppst_ph) ? ppst_ph : not na(ppst_pl) ? ppst_pl : na
    if not na(ppst_lastpp)
        ppst_center := na(ppst_center) ? ppst_lastpp : (ppst_center * 2.0 + ppst_lastpp) / 3.0
    float ppst_Up = ppst_center - (factor * ppst_atr)
    float ppst_Dn = ppst_center + (factor * ppst_atr)
    float ppst_TUp   = na
    float ppst_TDown = na
    int   ppst_Trend = 0
    ppst_TUp   := close[1] > ppst_TUp[1]   ? math.max(ppst_Up, ppst_TUp[1])   : ppst_Up
    ppst_TDown := close[1] < ppst_TDown[1] ? math.min(ppst_Dn, ppst_TDown[1]) : ppst_Dn
    ppst_Trend := close > ppst_TDown[1] ? 1 : close < ppst_TUp[1] ? -1 : nz(ppst_Trend[1], 1)
    float ppst_tsl  = ppst_Trend == 1 ? ppst_TUp : ppst_TDown
    bool  ppst_buy  = ppst_Trend ==  1 and ppst_Trend[1] == -1
    bool  ppst_sell = ppst_Trend == -1 and ppst_Trend[1] ==  1
    [ppst_Trend, ppst_tsl, ppst_center, ppst_buy, ppst_sell]

// ---------------------------------------------------------------------------------------------------------------{
//  VFI — "Volume Flow Indicator [LazyBear]" (v4 -> v6)
//  iff->ternary, stdev->ta.stdev, sma->ta.sma, ema->ta.ema, log->math.log, sum->math.sum
// ---------------------------------------------------------------------------------------------------------------{
f_vfi(simple int length, simple float coef, simple float vcoef, simple int signalLength, simple bool smooth) =>
    float vfi_typ    = hlc3
    float vfi_inter  = math.log(vfi_typ) - math.log(vfi_typ[1])
    float vfi_vinter = ta.stdev(vfi_inter, 30)
    float vfi_cutoff = coef * vfi_vinter * close
    float vfi_vave   = ta.sma(volume, length)[1]
    float vfi_vmax   = vfi_vave * vcoef
    float vfi_vc     = volume < vfi_vmax ? volume : vfi_vmax
    float vfi_mf     = vfi_typ - vfi_typ[1]
    float vfi_vcp    = vfi_mf > vfi_cutoff ? vfi_vc : vfi_mf < -vfi_cutoff ? -vfi_vc : 0.0
    float vfi_sum    = math.sum(vfi_vcp, length)          // extracted from the ternary: must run every bar
    float vfi_raw    = vfi_vave == 0 ? 0.0 : vfi_sum / vfi_vave
    float vfi_sm     = ta.sma(vfi_raw, 3)                 // evaluated unconditionally on purpose
    float vfi_val    = smooth ? vfi_sm : vfi_raw
    float vfi_ma     = ta.ema(vfi_val, signalLength)
    bool  vfi_ready  = bar_index > length + 40
    [vfi_val, vfi_ma, vfi_ready]

// ---------------------------------------------------------------------------------------------------------------{
//  SR high-volume boxes — (c) ChartPrime (v5 -> v6), NUMERIC ONLY (drawings are done by the caller).
// ---------------------------------------------------------------------------------------------------------------{
f_srBoxes(simple int lookback, simple int volLen, simple float boxWidth) =>
    var bool sr_isBuyVol = true
    sr_isBuyVol := close > open ? true : close < open ? false : sr_isBuyVol
    float sr_vol   = sr_isBuyVol ? volume : -volume
    float sr_volHi = ta.highest(sr_vol / 2.5, volLen)
    float sr_volLo = ta.lowest(sr_vol / 2.5, volLen)
    float sr_ph    = ta.pivothigh(close, lookback, lookback)
    float sr_pl    = ta.pivotlow(close, lookback, lookback)
    float sr_atr   = ta.atr(200)
    float sr_wid   = sr_atr * boxWidth
    float sr_volRef   = ta.highest(sr_vol, 25)
    float sr_volRefLo = ta.lowest(sr_vol, 25)
    var float sr_supTop = na
    var float sr_supBot = na
    var float sr_resBot = na
    var float sr_resTop = na
    var bool  sr_supOk  = false
    var bool  sr_resOk  = false
    bool sr_newSup = not na(sr_pl) and sr_vol > sr_volHi
    bool sr_newRes = not na(sr_ph) and sr_vol < sr_volLo
    if sr_newSup
        sr_supTop := sr_pl
        sr_supBot := sr_pl - sr_wid
        sr_supOk  := true
    if sr_newRes
        sr_resBot := sr_ph
        sr_resTop := sr_ph + sr_wid
        sr_resOk  := true
    bool sr_brkRes  = ta.crossover(low, sr_resTop)
    bool sr_holdRes = ta.crossunder(high, sr_resBot)
    bool sr_holdSup = ta.crossover(low, sr_supTop)
    bool sr_brkSup  = ta.crossunder(high, sr_supBot)
    if sr_brkSup
        sr_supOk := false
    if sr_holdSup
        sr_supOk := true
    if sr_brkRes
        sr_resOk := false
    if sr_holdRes
        sr_resOk := true
    [sr_supTop, sr_supBot, sr_supOk, sr_resBot, sr_resTop, sr_resOk, sr_newSup, sr_newRes, sr_vol,
     sr_brkSup, sr_brkRes, sr_holdSup, sr_holdRes, sr_volRef, sr_volRefLo]

// ---------------------------------------------------------------------------------------------------------------{
//  Liquidity Sweeps — (c) LuxAlgo (v5 -> v6), NUMERIC ONLY, 'Only Wicks' mode.
//  A stored pivot is MITIGATED once price closes through it; a SWEEP is registered when price pierces it with a
//  wick but closes back on the original side.  PIVOT LAG = `len` bars.
// ---------------------------------------------------------------------------------------------------------------{
f_liquiditySweeps(simple int len) =>
    float ls_ph = ta.pivothigh(len, len)
    float ls_pl = ta.pivotlow(len, len)
    var array<float> ls_lPrc = array.new<float>(0)
    var array<int>   ls_lBix = array.new<int>(0)
    var array<bool>  ls_lWic = array.new<bool>(0)
    var array<float> ls_hPrc = array.new<float>(0)
    var array<int>   ls_hBix = array.new<int>(0)
    var array<bool>  ls_hWic = array.new<bool>(0)
    var float ls_bullLow = na
    var int   ls_bullBar = na
    var float ls_bullLvl = na
    var float ls_bearHigh = na
    var int   ls_bearBar = na
    bool ls_bullNow = false
    bool ls_bearNow = false
    if not na(ls_pl)
        ls_lPrc.unshift(ls_pl), ls_lBix.unshift(bar_index - len), ls_lWic.unshift(false)
    if not na(ls_ph)
        ls_hPrc.unshift(ls_ph), ls_hBix.unshift(bar_index - len), ls_hWic.unshift(false)
    if ls_lPrc.size() > 0
        for i = ls_lPrc.size() - 1 to 0
            float p = ls_lPrc.get(i)
            bool mit = false
            if close < p
                mit := true
            else if low < p and close > p and not ls_lWic.get(i)
                ls_bullLow := low
                ls_bullLvl := p
                ls_bullBar := bar_index
                ls_bullNow := true
                ls_lWic.set(i, true)
            if mit or bar_index - ls_lBix.get(i) > 2000
                ls_lPrc.remove(i), ls_lBix.remove(i), ls_lWic.remove(i)
    if ls_hPrc.size() > 0
        for i = ls_hPrc.size() - 1 to 0
            float p = ls_hPrc.get(i)
            bool mit = false
            if close > p
                mit := true
            else if high > p and close < p and not ls_hWic.get(i)
                ls_bearHigh := high
                ls_bearBar  := bar_index
                ls_bearNow  := true
                ls_hWic.set(i, true)
            if mit or bar_index - ls_hBix.get(i) > 2000
                ls_hPrc.remove(i), ls_hBix.remove(i), ls_hWic.remove(i)
    int ls_bullAge = na(ls_bullBar) ? 100000 : bar_index - ls_bullBar
    int ls_bearAge = na(ls_bearBar) ? 100000 : bar_index - ls_bearBar
    [ls_bullAge, ls_bullLow, ls_bullLvl, ls_bullNow, ls_bearAge, ls_bearHigh, ls_bearNow]

// ---------------------------------------------------------------------------------------------------------------{
//  Buyside & Sellside Liquidity — (c) LuxAlgo (v5 -> v6), NUMERIC ONLY.
//  Keeps the original zigzag + "3 or more swing points clustered inside atr/margin" detection.
// ---------------------------------------------------------------------------------------------------------------{
f_buysideSellside(simple int liqLen, simple float marginIn, simple int visLiq) =>
    float bsl_liqMar = 10.0 / marginIn
    float bsl_atr = ta.atr(10)
    float bsl_ph  = ta.pivothigh(liqLen, 1)
    float bsl_pl  = ta.pivotlow(liqLen, 1)
    var array<int>   zz_d = array.new<int>(50, 0)
    var array<int>   zz_x = array.new<int>(50, 0)
    var array<float> zz_y = array.new<float>(50, na)
    var array<float> bl_prc = array.new<float>(0)
    var array<float> bl_top = array.new<float>(0)
    var array<float> bl_bot = array.new<float>(0)
    var array<int>   bl_bar = array.new<int>(0)
    var array<bool>  bl_brk = array.new<bool>(0)
    var array<float> sl_prc = array.new<float>(0)
    var array<float> sl_top = array.new<float>(0)
    var array<float> sl_bot = array.new<float>(0)
    var array<int>   sl_bar = array.new<int>(0)
    var array<bool>  sl_brk = array.new<bool>(0)
    int   bsl_x2 = bar_index - 1
    bool  bsl_newBuy  = false
    bool  bsl_newSell = false
    float bsl_evPrc = na
    float bsl_evTop = na
    float bsl_evBot = na
    int   bsl_evBar = na
    if not na(bsl_ph)
        int   d1 = zz_d.get(0)
        float y1 = zz_y.get(0)
        float y2 = nz(high[1])
        if d1 < 1
            zz_d.unshift(1), zz_x.unshift(bsl_x2), zz_y.unshift(y2), zz_d.pop(), zz_x.pop(), zz_y.pop()
        else if d1 == 1 and bsl_ph > y1
            zz_x.set(0, bsl_x2), zz_y.set(0, y2)
        int   cnt  = 0
        float stP  = 0.0
        int   stB  = 0
        float minP = 0.0
        float maxP = 10e6
        for i = 0 to 49
            if zz_d.get(i) == 1
                if zz_y.get(i) > bsl_ph + (bsl_atr / bsl_liqMar)
                    break
                else if zz_y.get(i) > bsl_ph - (bsl_atr / bsl_liqMar) and zz_y.get(i) < bsl_ph + (bsl_atr / bsl_liqMar)
                    cnt += 1
                    stB := zz_x.get(i)
                    stP := zz_y.get(i)
                    if zz_y.get(i) > minP
                        minP := zz_y.get(i)
                    if zz_y.get(i) < maxP
                        maxP := zz_y.get(i)
        if cnt > 2
            float tp = math.avg(minP, maxP) + (bsl_atr / bsl_liqMar)
            float bt = math.avg(minP, maxP) - (bsl_atr / bsl_liqMar)
            if bl_prc.size() > 0 and bl_bar.get(0) == stB
                bl_prc.set(0, stP), bl_top.set(0, tp), bl_bot.set(0, bt)
            else
                bl_prc.unshift(stP), bl_top.unshift(tp), bl_bot.unshift(bt), bl_bar.unshift(stB), bl_brk.unshift(false)
                bsl_newBuy := true
                bsl_evPrc  := stP
                bsl_evTop  := tp
                bsl_evBot  := bt
                bsl_evBar  := stB
                if bl_prc.size() > visLiq
                    bl_prc.pop(), bl_top.pop(), bl_bot.pop(), bl_bar.pop(), bl_brk.pop()
    if not na(bsl_pl)
        int   d2 = zz_d.get(0)
        float w1 = zz_y.get(0)
        float w2 = nz(low[1])
        if d2 > -1
            zz_d.unshift(-1), zz_x.unshift(bsl_x2), zz_y.unshift(w2), zz_d.pop(), zz_x.pop(), zz_y.pop()
        else if d2 == -1 and bsl_pl < w1
            zz_x.set(0, bsl_x2), zz_y.set(0, w2)
        int   cnt2  = 0
        float stP2  = 0.0
        int   stB2  = 0
        float minP2 = 0.0
        float maxP2 = 10e6
        for i = 0 to 49
            if zz_d.get(i) == -1
                if zz_y.get(i) < bsl_pl - (bsl_atr / bsl_liqMar)
                    break
                else if zz_y.get(i) > bsl_pl - (bsl_atr / bsl_liqMar) and zz_y.get(i) < bsl_pl + (bsl_atr / bsl_liqMar)
                    cnt2 += 1
                    stB2 := zz_x.get(i)
                    stP2 := zz_y.get(i)
                    if zz_y.get(i) > minP2
                        minP2 := zz_y.get(i)
                    if zz_y.get(i) < maxP2
                        maxP2 := zz_y.get(i)
        if cnt2 > 2
            float tp2 = math.avg(minP2, maxP2) + (bsl_atr / bsl_liqMar)
            float bt2 = math.avg(minP2, maxP2) - (bsl_atr / bsl_liqMar)
            if sl_prc.size() > 0 and sl_bar.get(0) == stB2
                sl_prc.set(0, stP2), sl_top.set(0, tp2), sl_bot.set(0, bt2)
            else
                sl_prc.unshift(stP2), sl_top.unshift(tp2), sl_bot.unshift(bt2), sl_bar.unshift(stB2), sl_brk.unshift(false)
                bsl_newSell := true
                bsl_evPrc   := stP2
                bsl_evTop   := tp2
                bsl_evBot   := bt2
                bsl_evBar   := stB2
                if sl_prc.size() > visLiq
                    sl_prc.pop(), sl_top.pop(), sl_bot.pop(), sl_bar.pop(), sl_brk.pop()
    if bl_prc.size() > 0
        for i = 0 to bl_prc.size() - 1
            if not bl_brk.get(i) and high > bl_top.get(i)
                bl_brk.set(i, true)
    if sl_prc.size() > 0
        for i = 0 to sl_prc.size() - 1
            if not sl_brk.get(i) and low < sl_bot.get(i)
                sl_brk.set(i, true)
    float bsl_sellNear = na
    if sl_prc.size() > 0
        for i = 0 to sl_prc.size() - 1
            if not sl_brk.get(i) and sl_prc.get(i) < close
                if na(bsl_sellNear) or sl_prc.get(i) > bsl_sellNear
                    bsl_sellNear := sl_prc.get(i)
    float bsl_buyNear = na
    if bl_prc.size() > 0
        for i = 0 to bl_prc.size() - 1
            if not bl_brk.get(i) and bl_prc.get(i) > close
                if na(bsl_buyNear) or bl_prc.get(i) < bsl_buyNear
                    bsl_buyNear := bl_prc.get(i)
    [bsl_sellNear, bsl_buyNear, bsl_newBuy, bsl_newSell, bsl_evPrc, bsl_evTop, bsl_evBot, bsl_evBar]

// ---------------------------------------------------------------------------------------------------------------{
//  Supply & Demand — REWRITE of "Supply and Demand Visible Range [LuxAlgo]".
//  The original binds its window to chart.left/right_visible_bar_time, so zones depend on your scroll position and
//  the backtest changes on every reload. Here the window is a FIXED ROLLING LOOKBACK of `lookback` bars.
//  SIMPLIFICATION: instead of request.security_lower_tf() intrabar highs/lows, each bar's volume is spread EVENLY
//  across every profile bin its high-low range spans. Good enough for zone placement, far cheaper, one less call.
//  COST CONTROL: the profile is rebuilt only every `every` bars; levels are held in between.
// ---------------------------------------------------------------------------------------------------------------{
f_supplyDemand(simple int lookback, simple int bins, simple float thrPct, simple int every) =>
    float sd_hh  = ta.highest(high, lookback)
    float sd_ll  = ta.lowest(low, lookback)
    float sd_tot = math.sum(nz(volume), lookback)
    var array<float> sd_prof = array.new<float>(bins, 0.0)
    var float sd_supTop = na
    var float sd_supBot = na
    var float sd_demTop = na
    var float sd_demBot = na
    var float sd_equi   = na
    bool sd_go = bar_index >= lookback and bar_index % every == 0 and sd_hh > sd_ll and sd_tot > 0
    if sd_go
        for i = 0 to bins - 1
            sd_prof.set(i, 0.0)
        float bsz = (sd_hh - sd_ll) / bins
        for j = 0 to lookback - 1
            int loI = math.max(0, math.min(bins - 1, int(math.floor((low[j]  - sd_ll) / bsz))))
            int hiI = math.max(0, math.min(bins - 1, int(math.floor((high[j] - sd_ll) / bsz))))
            float share = nz(volume[j]) / (hiI - loI + 1)
            for k = loI to hiI
                sd_prof.set(k, sd_prof.get(k) + share)
        float cumS = 0.0
        int   idxS = 0
        for i = bins - 1 to 0
            cumS += sd_prof.get(i)
            idxS := i
            if cumS / sd_tot * 100.0 > thrPct
                break
        float cumD = 0.0
        int   idxD = bins - 1
        for i = 0 to bins - 1
            cumD += sd_prof.get(i)
            idxD := i
            if cumD / sd_tot * 100.0 > thrPct
                break
        sd_supTop := sd_hh
        sd_supBot := sd_ll + idxS * bsz
        sd_demBot := sd_ll
        sd_demTop := sd_ll + (idxD + 1) * bsz
        sd_equi   := math.avg(sd_hh, sd_ll)
    [sd_supTop, sd_supBot, sd_demTop, sd_demBot, sd_equi]

// ---------------------------------------------------------------------------------------------------------------{
//  Pivot Points High Low — (c) LuxAlgo, regular pivots only.
// ---------------------------------------------------------------------------------------------------------------{
f_pivotPointsHL(simple int length) =>
    float pp_ph = ta.pivothigh(length, length)
    float pp_pl = ta.pivotlow(length, length)
    var float pp_lastLow  = na
    var float pp_lastHigh = na
    if not na(pp_pl)
        pp_lastLow := pp_pl
    if not na(pp_ph)
        pp_lastHigh := pp_ph
    [pp_lastLow, pp_lastHigh, pp_ph, pp_pl]

// Last / prior confirmed pivot high (higher-high structure filter)
f_hhph_last(simple int len_) =>
    float p = ta.pivothigh(len_, len_)
    ta.valuewhen(not na(p), p, 0)
f_hhph_prior(simple int len_) =>
    float p = ta.pivothigh(len_, len_)
    ta.valuewhen(not na(p), p, 1)
f_lastPivotLow(simple int len_) =>
    float p = ta.pivotlow(len_, len_)
    ta.valuewhen(not na(p), p, 0)

// ---------------------------------------------------------------------------------------------------------------{
//  Bollinger Bands (TradingView built-in)
// ---------------------------------------------------------------------------------------------------------------{
f_bbMa(float source, simple int length, simple string mtype) =>
    float o = switch mtype
        "SMA"        => ta.sma(source, length)
        "EMA"        => ta.ema(source, length)
        "SMMA (RMA)" => ta.rma(source, length)
        "WMA"        => ta.wma(source, length)
        "VWMA"       => ta.vwma(source, length)
        =>              ta.sma(source, length)
    o

// ---------------------------------------------------------------------------------------------------------------{
//  AlphaTrend — (c) KivancOzbilgic. Returns [buyEvent, sellEvent, bullish, bearish, line, lag].
// ---------------------------------------------------------------------------------------------------------------{
f_alphatrend() =>
    float aAtr = ta.sma(ta.tr, at_ap)
    float aUp  = low  - aAtr * at_coeff
    float aDn  = high + aAtr * at_coeff
    float aMom = at_novol ? ta.rsi(at_src, at_ap) : ta.mfi(hlc3, at_ap)
    var float at = 0.0
    at := (aMom >= 50) ? (aUp < nz(at[1]) ? nz(at[1]) : aUp) : (aDn > nz(at[1]) ? nz(at[1]) : aDn)
    bool buyRaw  = ta.crossover(at, at[2])
    bool sellRaw = ta.crossunder(at, at[2])
    int  k1 = ta.barssince(buyRaw)
    int  k2 = ta.barssince(sellRaw)
    int  o1 = ta.barssince(buyRaw[1])
    int  o2 = ta.barssince(sellRaw[1])
    [buyRaw and o1 > k2, sellRaw and o2 > k1, at > at[2], at < at[2], at, at[2]]


// =====================================================================================================================
//  PER-FILTER HTF PACKS  —  one request.security() per section, each with its own timeframe.
//  All use lookahead = barmerge.lookahead_off; the f_c()/f_cb() selector applies the closed-bar [1] offset.
// =====================================================================================================================

f_packG1() =>
    [t, l, u, d, b, s] = f_supertrend(g1_atrLen, hl2, g1_mult, g1_chgAtr)
    float tf_ = t
    float lf_ = l
    [f_c(tf_), f_c(lf_)]

f_packG2() =>
    float r = ta.rsi(close, g2_len)
    f_c(r)

f_packG3() =>
    [v, m, rdy] = f_vfi(g3_len, g3_coef, g3_vcoef, g3_sig, g3_smooth)
    float vf = v
    float rf = f_b2f(rdy)
    [f_c(vf), f_c(rf)]

f_packSR(simple int lb, simple int vl, simple float bw) =>
    [supTop, supBot, supOk, resBot, resTop, resOk, nSup, nRes, dvol, bSup, bRes, hSup, hRes, vRef, vRefLo] = f_srBoxes(lb, vl, bw)
    float a = supTop
    float b = supBot
    float c = f_b2f(supOk)
    float d = ta.atr(atrLenRisk)
    [f_c(a), f_c(b), f_c(c), f_c(d)]

f_packS1() =>
    [age, lo, lvl, now, bAge, bHi, bNow] = f_liquiditySweeps(s1_len)
    float a = age
    float b = lo
    [f_c(a), f_c(b)]

f_packS2() =>
    [supTop, supBot, demTop, demBot, equi] = f_supplyDemand(s2_lb, s2_res, s2_thr, s2_every)
    float a = demTop
    float b = demBot
    float c = ta.atr(atrLenRisk)
    [f_c(a), f_c(b), f_c(c)]

f_packS4() =>
    [t, l, u, d, b, s] = f_supertrend(s4_atrLen, hl2, s4_mult, s4_chgAtr)
    float tf_ = t
    float lf_ = l
    [f_c(tf_), f_c(lf_)]

f_packS5() =>
    [t, tsl, ctr, b, s] = f_pivotSupertrend(s5_prd, s5_factor, s5_pd)
    float tf_ = t
    float sf_ = tsl
    [f_c(tf_), f_c(sf_)]

f_packS6() =>
    [sellNear, buyNear, nBuy, nSell, evP, evT, evB, evX] = f_buysideSellside(s6_len, s6_margin, s6_vis)
    float a = sellNear
    float b = ta.atr(atrLenRisk)
    [f_c(a), f_c(b)]

f_packS7() =>
    float p = f_lastPivotLow(s7_len)
    f_c(p)

// Entry trigger, evaluated entirely on the trigger timeframe so the cross is detected on THAT TF's closes.
f_packTrigger() =>
    // Heikin-Ashi close = ohlc4. Only the TRIGGER may use it; stops, targets and fills always use real prices.
    float haC = (open + high + low + close) / 4
    float src = useHeikin ? haC : close
    float basis = f_bbMa(src, bb_len, bb_maTyp)
    float dev   = bb_mult * ta.stdev(src, bb_len)
    float upper = basis + dev
    float lower = basis - dev
    [stT, stL, stU, stD, stB, stS] = f_supertrend(trig_stLen, hl2, trig_stMult, true)
    bool below = false
    for i = 1 to bbReclaimLb
        if src[i] < lower[i]
            below := true
    bool reclaim = ta.crossover(src, lower) or (src > lower and below and src[1] <= lower[1])
    bool basisX  = ta.crossover(src, basis)
    bool stFlip  = stT == 1 and stT[1] == -1
    bool bothT   = reclaim and stT == 1
    bool raw = switch trigMode
        "Bollinger Reclaim"      => reclaim
        "Bollinger Basis Cross"  => basisX
        "Supertrend Flip"        => stFlip
        "BB + Supertrend (both)" => bothT
        =>                          false
    bool trg = raw and (not reqST5 or stT == 1)
    float bf = basis
    float uf = upper
    float lf = lower
    float tf_ = stT
    [f_cb(trg), f_c(bf), f_c(uf), f_c(lf), f_c(tf_)]

f_packVol() =>
    float v  = volume
    float vs = ta.sma(volume, volMaLen)
    [f_c(v), f_c(vs)]

f_packHH() =>
    float a = f_hhph_last(hhPivotLen)
    float b = f_hhph_prior(hhPivotLen)
    [f_c(a), f_c(b)]


// =====================================================================================================================
//  HTF FETCH  —  request.security call budget (Pine caps a script at 40):
//    G1, G2, G3, G4, S1, S2, S3, S4, S5, S6, S7, Trigger, Volume, HH, AlphaTrend, HL-break exit  =  16 calls
// =====================================================================================================================
[g1TrendF, g1Line]        = request.security(syminfo.tickerid, f_tfn(g1_tf), f_packG1(), barmerge.gaps_off, barmerge.lookahead_off)
float g2Rsi               = request.security(syminfo.tickerid, f_tfn(g2_tf), f_packG2(), barmerge.gaps_off, barmerge.lookahead_off)
[g3Vfi, g3RdyF]           = request.security(syminfo.tickerid, f_tfn(g3_tf), f_packG3(), barmerge.gaps_off, barmerge.lookahead_off)
[g4SupTop, g4SupBot, g4SupOkF, g4Atr] = request.security(syminfo.tickerid, f_tfn(g4_tf), f_packSR(g4_lb, g4_volLen, g4_boxW), barmerge.gaps_off, barmerge.lookahead_off)
[s1Age, s1Low]            = request.security(syminfo.tickerid, f_tfn(s1_tf), f_packS1(), barmerge.gaps_off, barmerge.lookahead_off)
[s2DemTop, s2DemBot, s2Atr] = request.security(syminfo.tickerid, f_tfn(s2_tf), f_packS2(), barmerge.gaps_off, barmerge.lookahead_off)
[s3SupTop, s3SupBot, s3SupOkF, s3Atr] = request.security(syminfo.tickerid, f_tfn(s3_tf), f_packSR(s3_lb, s3_volLen, s3_boxW), barmerge.gaps_off, barmerge.lookahead_off)
[s4TrendF, s4Line]        = request.security(syminfo.tickerid, f_tfn(s4_tf), f_packS4(), barmerge.gaps_off, barmerge.lookahead_off)
[s5TrendF, s5Tsl]         = request.security(syminfo.tickerid, f_tfn(s5_tf), f_packS5(), barmerge.gaps_off, barmerge.lookahead_off)
[s6SellLvl, s6Atr]        = request.security(syminfo.tickerid, f_tfn(s6_tf), f_packS6(), barmerge.gaps_off, barmerge.lookahead_off)
float s7PivLow            = request.security(syminfo.tickerid, f_tfn(s7_tf), f_packS7(), barmerge.gaps_off, barmerge.lookahead_off)
[trigFired, trigBasis, trigUpper, trigLower, trigStF] = request.security(syminfo.tickerid, f_tfn(trig_tf), f_packTrigger(), barmerge.gaps_off, barmerge.lookahead_off)
[volCur, volSma]          = request.security(syminfo.tickerid, f_tfn(volTf), f_packVol(), barmerge.gaps_off, barmerge.lookahead_off)
[hhLastPH, hhPriorPH]     = request.security(syminfo.tickerid, f_tfn(hhTf), f_packHH(), barmerge.gaps_off, barmerge.lookahead_off)
[atEvtLong, atEvtShort, atStateLong, atStateShort, AlphaTrend, AlphaTrendLag] = request.security(syminfo.tickerid, f_tfn(at_tf), f_alphatrend(), barmerge.gaps_off, barmerge.lookahead_off)
float hlBreakPivLow       = request.security(syminfo.tickerid, f_tfn(hlBreakTf), f_lastPivotLow(hlBreakPivLen), barmerge.gaps_off, barmerge.lookahead_off)

bool g1Bull  = g1TrendF == 1
bool g3Ready = g3RdyF   == 1
bool g4SupOk = g4SupOkF == 1
bool s3SupOk = s3SupOkF == 1
bool s4Bull  = s4TrendF == 1
bool s5Bull  = s5TrendF == 1


// =====================================================================================================================
//  CHART-TIMEFRAME CALCULATIONS (these are the instances allowed to DRAW)
// =====================================================================================================================
float atr = ta.atr(atrLenRisk)

[loc_stTrend, loc_stLine, loc_stUp, loc_stDn, loc_stBuy, loc_stSell] = f_supertrend(trig_stLen, hl2, trig_stMult, true)
[loc_ppTrend, loc_ppTsl, loc_ppCenter, loc_ppBuy, loc_ppSell]        = f_pivotSupertrend(s5_prd, s5_factor, s5_pd)
float locBbBasis = f_bbMa(close, bb_len, bb_maTyp)
float locBbDev   = bb_mult * ta.stdev(close, bb_len)
float locBbUpper = locBbBasis + locBbDev
float locBbLower = locBbBasis - locBbDev

[v_supTop, v_supBot, v_supOk, v_resBot, v_resTop, v_resOk, v_newSup, v_newRes, v_dvol,
 v_brkSup, v_brkRes, v_holdSup, v_holdRes, v_volRef, v_volRefLo] = f_srBoxes(s3_lb, s3_volLen, s3_boxW)
[v_lsAge, v_lsLow, v_lsLvl, v_lsBullNow, v_lsBearAge, v_lsBearHigh, v_lsBearNow] = f_liquiditySweeps(s1_len)
[v_sellNear, v_buyNear, v_newBuyLiq, v_newSellLiq, v_evPrc, v_evTop, v_evBot, v_evBar] = f_buysideSellside(s6_len, s6_margin, s6_vis)
[v_sdSupTop, v_sdSupBot, v_sdDemTop, v_sdDemBot, v_sdEqui] = f_supplyDemand(s2_lb, s2_res, s2_thr, s2_every)
[v_ppLow, v_ppHigh, v_ppPhRaw, v_ppPlRaw] = f_pivotPointsHL(s7_len)

bool inDrawWindow = last_bar_index - bar_index <= maxDrawBars


// =====================================================================================================================
//  FILTER EVALUATION  —  pure AND of ENABLED filters. A filter that is off is ignored completely.
//  Each S filter also publishes a stop-loss candidate level for the structure-stop engine.
// =====================================================================================================================

// --- Gates -------------------------------------------------------------------------------------------------------
bool g1_ok = g1Bull
bool g2_ok = g2Rsi > g2_thr and g2Rsi <= g2_max
bool g3_ok = g3Ready and g3Vfi > g3_level
bool g4_ok = not na(g4SupBot) and g4SupOk and close >= g4SupBot - g4_atrMult * nz(g4Atr)

// --- Structure filters + their stop candidates ---------------------------------------------------------------------
bool  s1_ok  = not na(s1Low) and s1Age <= s1_maxAge
float s1_lvl = s1Low

bool  s2_ok  = not na(s2DemTop) and close >= s2DemBot and close <= s2DemTop + s2_prox * nz(s2Atr)
float s2_lvl = s2DemBot

bool  s3_ok  = not na(s3SupTop) and s3SupOk and close >= s3SupBot and close <= s3SupTop + s3_prox * nz(s3Atr)
float s3_lvl = s3SupBot

bool  s4_ok  = s4Bull
float s4_lvl = s4Line

bool  s5_ok  = s5Bull
float s5_lvl = s5Tsl

bool  s6_ok  = not na(s6SellLvl) and close > s6SellLvl and (close - s6SellLvl) <= s6_prox * nz(s6Atr)
float s6_lvl = s6SellLvl

bool  s7_ok  = not na(s7PivLow) and close > s7PivLow
float s7_lvl = s7PivLow

// --- Extra filters ---------------------------------------------------------------------------------------------------
bool volOk = not useVolumeFilter or (not na(volSma) and volSma > 0 and volCur >= volMultMin * volSma)
bool hhOk  = not useHhStructure or (not na(hhLastPH) and not na(hhPriorPH) and hhLastPH > hhPriorPH)
bool bodyOk = not useBodyFilter or close >= open or (open - close) <= entryBodyAtrMult * atr

float extRefEma = ta.ema(close, extRefLen)
float extLimit  = extMode == "ATR" ? extMaxAtr * atr : extRefEma * extMaxPct / 100.0
bool  extOk     = not useExtFilter or na(extRefEma) or close <= extRefEma + extLimit
float runRise   = close - close[runLb]
float runCapPct = close[runLb] * runMaxPct / 100.0
bool  runOk     = not usePeakRun or na(close[runLb]) or (runMode == "Percent" ? runRise <= runCapPct : runRise <= runMaxAtr * atr)
float atrAvgSpk = ta.sma(atr, atrSpikeAvgLen)
bool  atrSpkOk  = not useAtrSpike or na(atrAvgSpk) or atrAvgSpk <= 0 or atr <= atrSpikeMaxMult * atrAvgSpk
float upperWick = high - math.max(open, close)
bool  wickOk    = not useWickFilter or upperWick <= wickMaxAtr * atr
bool  spikeOk   = extOk and runOk and atrSpkOk and wickOk

// --- The confluence gate: every ENABLED filter must pass ---------------------------------------------------------
bool gatesOk =
     (not useG1 or g1_ok) and (not useG2 or g2_ok) and (not useG3 or g3_ok) and (not useG4 or g4_ok)
bool structOk =
     (not useS1 or s1_ok) and (not useS2 or s2_ok) and (not useS3 or s3_ok) and (not useS4 or s4_ok) and
     (not useS5 or s5_ok) and (not useS6 or s6_ok) and (not useS7 or s7_ok)
bool filtersOk = gatesOk and structOk and volOk and hhOk and bodyOk and spikeOk and
     (not at_en or at_primary or atStateLong)

// Count of enabled/passing filters — shown in the table so you can see how selective the current setup is.
int nEnabled = (useG1 ? 1 : 0) + (useG2 ? 1 : 0) + (useG3 ? 1 : 0) + (useG4 ? 1 : 0) +
     (useS1 ? 1 : 0) + (useS2 ? 1 : 0) + (useS3 ? 1 : 0) + (useS4 ? 1 : 0) +
     (useS5 ? 1 : 0) + (useS6 ? 1 : 0) + (useS7 ? 1 : 0)
int nPassing = (useG1 and g1_ok ? 1 : 0) + (useG2 and g2_ok ? 1 : 0) + (useG3 and g3_ok ? 1 : 0) + (useG4 and g4_ok ? 1 : 0) +
     (useS1 and s1_ok ? 1 : 0) + (useS2 and s2_ok ? 1 : 0) + (useS3 and s3_ok ? 1 : 0) + (useS4 and s4_ok ? 1 : 0) +
     (useS5 and s5_ok ? 1 : 0) + (useS6 and s6_ok ? 1 : 0) + (useS7 and s7_ok ? 1 : 0)


// =====================================================================================================================
//  TRIGGER
//  AlphaTrend set to PRIMARY replaces the section-14 trigger. Otherwise the section-14 trigger fires and AlphaTrend
//  (if enabled) acts as a confirming filter inside filtersOk above.
// =====================================================================================================================
bool primaryFired =
     at_en and at_primary ? atEvtLong :
     useTrigger          ? trigFired  :
                           true


// =====================================================================================================================
//  CIRCUIT BREAKERS  (consecutive-loss pause, profit-run cap, cooldown, one-trade-per-signal)
// =====================================================================================================================
var int consecLosses   = 0
var int choppyUntilBar = 0
if barstate.isconfirmed and strategy.closedtrades > nz(strategy.closedtrades[1])
    float lastPnl = strategy.closedtrades.profit(strategy.closedtrades - 1)
    if lastPnl < 0
        consecLosses += 1
        if consecLosses >= maxConsecLoss
            choppyUntilBar := bar_index + choppyPauseBars
            consecLosses   := 0
    else
        consecLosses := 0
bool choppyOk = not useChoppyFilter or bar_index > choppyUntilBar

var int   runWinStreak    = 0
var float runStreakPnlPct = 0.0
var int   runPauseUntil   = 0
if barstate.isconfirmed and strategy.closedtrades > nz(strategy.closedtrades[1])
    int   _li   = strategy.closedtrades - 1
    float _pnl  = strategy.closedtrades.profit(_li)
    float _ev   = strategy.closedtrades.entry_price(_li) * math.abs(strategy.closedtrades.size(_li))
    float _pnlP = _ev != 0 ? _pnl / _ev * 100.0 : 0.0
    if _pnl > 0
        runWinStreak    += 1
        runStreakPnlPct += _pnlP
        if useRunLimit and runWinStreak >= runLimTrades and runStreakPnlPct >= runLimProfitPct
            runPauseUntil   := bar_index + runLimPauseBars
            runWinStreak    := 0
            runStreakPnlPct := 0.0
    else
        runWinStreak    := 0
        runStreakPnlPct := 0.0
bool runLimitOk = not useRunLimit or bar_index > runPauseUntil

var int lastExitToFlatBar = na
if strategy.position_size == 0 and strategy.position_size[1] != 0
    lastExitToFlatBar := bar_index
int  barsSinceExitFlat = na(lastExitToFlatBar) ? 999999 : bar_index - lastExitToFlatBar
bool cooldownExitOk    = cooldownBarsAfterExit <= 0 or na(lastExitToFlatBar) or barsSinceExitFlat > cooldownBarsAfterExit

bool anyIndBuyTrig = (at_en and atEvtLong) or (useTrigger and trigFired)
var bool indSigArmed = false
if anyIndBuyTrig
    indSigArmed := true
bool oneTradeGateOk = not useOneTradePerSignal or indSigArmed

bool confirmed  = not useBarConfirm or barstate.isconfirmed
bool entryBarOk = not ordersOnConfirmedBar or barstate.isconfirmed


// =====================================================================================================================
//  STRUCTURE-STOP ENGINE — candidate collection (used when stopEngine == "Structure (S1-S7 levels)")
// =====================================================================================================================
var array<float> cand = array.new<float>(0)
cand.clear()
if useS1 and s1_ok and not na(s1_lvl) and s1_lvl < close
    cand.push(s1_lvl)
if useS2 and s2_ok and not na(s2_lvl) and s2_lvl < close
    cand.push(s2_lvl)
if useS3 and s3_ok and not na(s3_lvl) and s3_lvl < close
    cand.push(s3_lvl)
if useS4 and s4_ok and not na(s4_lvl) and s4_lvl < close
    cand.push(s4_lvl)
if useS5 and s5_ok and not na(s5_lvl) and s5_lvl < close
    cand.push(s5_lvl)
if useS6 and s6_ok and not na(s6_lvl) and s6_lvl < close
    cand.push(s6_lvl)
if useS7 and s7_ok and not na(s7_lvl) and s7_lvl < close
    cand.push(s7_lvl)

float nearestStruct  = cand.size() > 0 ? cand.max() : na    // highest level below price = tightest stop
float furthestStruct = cand.size() > 0 ? cand.min() : na    // lowest level below price  = widest stop
float atrStopLvl     = close - slAtrMult * atr

float chosenLevel = switch slSource
    "Nearest structure"  => nearestStruct
    "Furthest structure" => furthestStruct
    "S1 sweep low"       => (s1_ok and not na(s1_lvl) and s1_lvl < close ? s1_lvl : na)
    "S2 demand bottom"   => (s2_ok and not na(s2_lvl) and s2_lvl < close ? s2_lvl : na)
    "S3 support bottom"  => (s3_ok and not na(s3_lvl) and s3_lvl < close ? s3_lvl : na)
    "ATR"                => atrStopLvl
    =>                      nearestStruct

bool  usedAtrFallback = na(chosenLevel) and slSource != "ATR"
float baseLevel   = na(chosenLevel) ? atrStopLvl : chosenLevel
float structStop  = baseLevel - slBufAtr * atr
float structDist  = na(structStop) or close <= 0 ? na : (close - structStop) / close * 100.0
bool  tooTightNow = not na(structDist) and structDist < minStopPct
bool  tooWideNow  = not na(structDist) and structDist > maxStopPct
if tooTightNow and tooTight == "Widen"
    structStop := close * (1.0 - minStopPct / 100.0)

// R:R engine stop (MA_RR style): below the recent swing low, floored to a minimum ATR distance.
float rrSwingLow = ta.lowest(low, rrSwingLb)
float rrStopCalc = math.min(rrSwingLow - rrBufAtr * atr, close - minSlDistAtr * atr)

bool useRRengine = stopEngine == "R:R (swing low + ratio)"
float plannedStop = useRRengine ? rrStopCalc : structStop
bool  stopUsable  = not na(plannedStop) and plannedStop > 0 and plannedStop < close and
     (useRRengine or (not tooWideNow and not (tooTightNow and tooTight == "Skip Trade")))


// =====================================================================================================================
//  ENTRY
//  SPOT-ONLY ASSERTION: there is no strategy.entry(..., strategy.short) and no negative-qty strategy.order() in
//  this file. The sizing and stop maths below assume long-only.
// =====================================================================================================================
var float posEntry  = na
var float posStop   = na      // FROZEN at entry; never recomputed from structure afterwards
var float posR      = na
var float posTp1    = na
var float posTp2    = na
var float posTp3    = na
var float hiSince   = na
var bool  tp1Filled = false
var int   entryBar  = na
var bool  a_tp1 = false
var bool  a_tp2 = false
var bool  a_tp3 = false
var bool  a_stop= false

bool flat  = strategy.position_size == 0
bool atrOk = not na(atr) and atr > 0

bool rawLongSetup = confirmed and flat and long_en and filtersOk and primaryFired and stopUsable and atrOk
bool longSignal   = rawLongSetup and cooldownExitOk and choppyOk and entryBarOk and oneTradeGateOk and runLimitOk

float riskPerUnit = close - plannedStop
float qtyFixed    = close > 0 ? tradeAmt / close : na
float qtyRisk     = riskPerUnit > 0 ? (strategy.equity * riskPct / 100.0) / riskPerUnit : na
float entryQty    = sizingMode == "Fixed USDT" ? qtyFixed : qtyRisk

if longSignal and not na(entryQty) and entryQty > 0
    posEntry  := close
    posStop   := plannedStop
    posR      := posEntry - posStop
    posTp1    := useRRengine ? posEntry * (1 + rrTp1Pct / 100) : posEntry + stTp1R * posR
    posTp2    := useRRengine ? posEntry * (1 + rrTp2Pct / 100) : posEntry + stTp2R * posR
    posTp3    := useRRengine ? posEntry + posR * rrRatio       : posEntry + stTp3R * posR
    hiSince   := high
    tp1Filled := false
    entryBar  := bar_index
    indSigArmed := false
    a_tp1 := false, a_tp2 := false, a_tp3 := false, a_stop := false
    strategy.entry("Long", strategy.long, qty = entryQty)


// =====================================================================================================================
//  EXIT ENGINE
// =====================================================================================================================
bool inPos = strategy.position_size > 0

if inPos and not longSignal
    hiSince := math.max(nz(hiSince, high), high)
if inPos and not na(posTp1) and high >= posTp1
    tp1Filled := true

int barsInTrade = inPos and not na(entryBar) ? bar_index - entryBar : 0

// Unrealized R — gates the structure-break exit.
float longUnrealR = inPos and not na(posStop) and strategy.position_avg_price > posStop
     ? (close - strategy.position_avg_price) / (strategy.position_avg_price - posStop)
     : 0.0

// --- protective stop: frozen entry stop, lifted by break-even, then by the % trail. Monotonic. -----------------------
var float trailAnchor = na
var bool  trailArmed  = false
if not inPos
    trailAnchor := na
    trailArmed  := false

float effStop = posStop
if inPos and rrUseBE and not na(posR) and posR > 0 and longUnrealR >= rrBeAfterR
    effStop := math.max(nz(effStop, posStop), posEntry + rrBeOffR * posR)
if inPos and rrUseTrailSl
    if not trailArmed and (rrTrailActPct <= 0 ? close > posEntry : high >= posEntry * (1 + rrTrailActPct / 100))
        trailArmed := true
    if trailArmed
        float candTrail = close * (1 - rrTrailPct / 100)
        // MONOTONIC: the trail only ever ratchets up.
        trailAnchor := na(trailAnchor) ? candTrail : math.max(trailAnchor, candTrail)
        effStop     := math.max(nz(effStop, trailAnchor), trailAnchor)

// --- signal exits ----------------------------------------------------------------------------------------------------
bool exitG1Flip = exitOnG1Flip and inPos and not g1Bull
bool exitS4Flip = exitOnS4Flip and inPos and not s4Bull
bool exitTime   = maxBarsTrade > 0 and inPos and barsInTrade >= maxBarsTrade
bool exitAtSell = at_en and at_useExit and inPos and atEvtShort
bool exitHlBrk  = useHlBreakExit and inPos and barstate.isconfirmed and not na(hlBreakPivLow) and close < hlBreakPivLow
bool closeNow   = confirmed and (exitG1Flip or exitS4Flip or exitTime or exitAtSell or exitHlBrk)

if closeNow
    string why = exitHlBrk ? "HL break" : exitG1Flip ? "G1 flip" : exitS4Flip ? "S4 flip" :
         exitAtSell ? "AlphaTrend sell" : "time stop"
    strategy.close("Long", comment = why)

// --- brackets ---------------------------------------------------------------------------------------------------------
// Partial % basis conversion (structure engine). Pine's qty_percent is a % of the position REMAINING when the exit
// executes, not of the original size. With 40 / 30 / 30 of the ORIGINAL position:
//     leg 1 : 40% of 100% remaining                   -> qty_percent = 40
//     leg 2 : 30% of original = 30 / (100 - 40)       -> qty_percent = 30 / 60 * 100 = 50
//     leg 3 : the remainder                           -> qty_percent = 100
float legPct1 = stTp1Pct
float legPct2 = pctBasis == "Original Position" ? (stTp1Pct >= 100.0 ? 100.0 : math.min(100.0, stTp2Pct / (100.0 - stTp1Pct) * 100.0)) : stTp2Pct
float legPct3 = 100.0

if (inPos or longSignal) and not closeNow and not na(posStop)
    if useRRengine
        if rrUsePartialTp
            strategy.exit("RR1", from_entry = "Long", qty_percent = rrTp1Size, limit = posTp1, stop = effStop, comment_profit = "TP1", comment_loss = "SL")
            strategy.exit("RR2", from_entry = "Long", qty_percent = math.min(100.0, rrTp2Size / math.max(1.0, 100.0 - rrTp1Size) * 100.0), limit = posTp2, stop = effStop, comment_profit = "TP2", comment_loss = "SL")
            strategy.exit("RR3", from_entry = "Long", limit = posTp3, stop = effStop, comment_profit = "TP", comment_loss = "SL")
        else
            strategy.exit("RR X", from_entry = "Long", limit = posTp3, stop = effStop, comment_profit = "TP", comment_loss = "SL")
    else
        if stUsePartials
            strategy.exit("TP1", from_entry = "Long", qty_percent = legPct1, limit = posTp1, stop = effStop, comment_profit = "TP1", comment_loss = "SL")
            strategy.exit("TP2", from_entry = "Long", qty_percent = legPct2, limit = posTp2, stop = effStop, comment_profit = "TP2", comment_loss = "SL")
            strategy.exit("TP3", from_entry = "Long", qty_percent = legPct3, limit = posTp3, stop = effStop, comment_profit = "TP3", comment_loss = "SL")
        else
            strategy.exit("EXIT", from_entry = "Long", limit = posTp3, stop = effStop, comment_profit = "TP", comment_loss = "SL")

// --- reset when flat --------------------------------------------------------------------------------------------------
if flat and not longSignal
    posEntry  := na
    posStop   := na
    posR      := na
    posTp1    := na
    posTp2    := na
    posTp3    := na
    hiSince   := na
    tp1Filled := false


// =====================================================================================================================
//  DRAWING   (every block is toggle-gated, bar-window-gated, and has an eviction path)
// =====================================================================================================================
upPlot = plot(showST and loc_stTrend == 1 ? loc_stUp : na, "Supertrend Up",   style = plot.style_linebr, linewidth = 2, color = color.green)
dnPlot = plot(showST and loc_stTrend == 1 ? na : (showST ? loc_stDn : na), "Supertrend Down", style = plot.style_linebr, linewidth = 2, color = color.red)
mPlot  = plot(showST and showSTfill ? ohlc4 : na, "", style = plot.style_circles, linewidth = 1, display = display.none)
fill(mPlot, upPlot, title = "UpTrend Highlighter",   color = showSTfill and loc_stTrend ==  1 ? color.new(color.green, 85) : na)
fill(mPlot, dnPlot, title = "DownTrend Highlighter", color = showSTfill and loc_stTrend == -1 ? color.new(color.red,   85) : na)
plotshape(showST and showSTsig and loc_stBuy  ? loc_stUp : na, "ST Buy",  location = location.absolute, style = shape.labelup,   size = size.tiny, color = color.green, textcolor = color.white, text = "Buy")
plotshape(showST and showSTsig and loc_stSell ? loc_stDn : na, "ST Sell", location = location.absolute, style = shape.labeldown, size = size.tiny, color = color.red,   textcolor = color.white, text = "Sell")

color ppLineCol = loc_ppTrend == 1 and nz(loc_ppTrend[1]) == 1 ? color.lime : loc_ppTrend == -1 and nz(loc_ppTrend[1]) == -1 ? color.red : na
plot(showPPST ? loc_ppTsl : na, "PP SuperTrend", color = ppLineCol, linewidth = 2)

plot(showBB ? locBbBasis : na, "BB Basis", color = #2962FF)
bbP1 = plot(showBB ? locBbUpper : na, "BB Upper", color = #F23645)
bbP2 = plot(showBB ? locBbLower : na, "BB Lower", color = #089981)
fill(bbP1, bbP2, title = "BB Background", color = showBB ? color.rgb(33, 150, 243, 95) : na)

plot(plotAlphaTrend ? AlphaTrend : na, "AlphaTrend", color = color.new(#00E676, 0), linewidth = 2)
plot(plotAlphaTrend ? AlphaTrendLag : na, "AlphaTrend lag", color = color.new(#FF5252, 0), linewidth = 1)

// --- ChartPrime SR boxes ---------------------------------------------------------------------------------------------
var array<sr_zone> srZones = array.new<sr_zone>(0)
f_srEvict(array<sr_zone> arr, simple int cap) =>
    while arr.size() > cap
        sr_zone z = arr.shift()
        z.bx.delete()
    arr.size()

if showSR and inDrawWindow and barstate.isconfirmed
    if v_newSup
        color supCol = color.from_gradient(v_dvol, 0, math.max(v_volRef, 1), color(na), color.new(color.green, 30))
        box nb = box.new(bar_index - s3_lb, v_supTop, bar_index + 1, v_supBot,
             border_color = color.green, border_width = 1, bgcolor = supCol,
             text = "Vol: " + str.tostring(math.round(v_dvol, 2)), text_color = chart.fg_color, text_size = size.small)
        srZones.push(sr_zone.new(nb, true))
        f_srEvict(srZones, capSR)
    if v_newRes
        color resCol = color.from_gradient(v_dvol, math.min(v_volRefLo, -1), 0, color.new(color.red, 30), color(na))
        box nb = box.new(bar_index - s3_lb, v_resTop, bar_index + 1, v_resBot,
             border_color = color.red, border_width = 1, bgcolor = resCol,
             text = "Vol: " + str.tostring(math.round(v_dvol, 2)), text_color = chart.fg_color, text_size = size.small)
        srZones.push(sr_zone.new(nb, false))
        f_srEvict(srZones, capSR)
    if srZones.size() > 0
        for i = 0 to srZones.size() - 1
            sr_zone z = srZones.get(i)
            z.bx.set_right(bar_index + 1)
        for i = srZones.size() - 1 to 0
            sr_zone z = srZones.get(i)
            if z.isSup and (v_brkSup or v_holdSup)
                z.bx.set_bgcolor(v_brkSup ? color.new(color.red, 80) : color.new(color.green, 80))
                z.bx.set_border_color(v_brkSup ? color.red : color.green)
                z.bx.set_border_style(v_brkSup ? line.style_dashed : line.style_solid)
                break
        for i = srZones.size() - 1 to 0
            sr_zone z = srZones.get(i)
            if not z.isSup and (v_brkRes or v_holdRes)
                z.bx.set_bgcolor(v_brkRes ? color.new(color.green, 80) : color.new(color.red, 80))
                z.bx.set_border_color(v_brkRes ? color.new(color.green, 0) : color.new(color.red, 0))
                z.bx.set_border_style(v_brkRes ? line.style_dashed : line.style_solid)
                break

plotchar(showSR and v_holdRes, "Resistance Holds", "◆", color = #e92929, size = size.tiny, location = location.abovebar, offset = -1)
plotchar(showSR and v_holdSup, "Support Holds",    "◆", color = #20ca26, size = size.tiny, location = location.belowbar, offset = -1)

var array<label> srLabels = array.new<label>(0)
if showSR and inDrawWindow and barstate.isconfirmed and (v_brkSup or v_brkRes)
    if v_brkSup
        srLabels.push(label.new(bar_index[1], nz(v_supTop[1], low), text = "Break Sup", style = label.style_label_down,
             color = #7e1e1e, textcolor = chart.fg_color, size = size.small))
    else
        srLabels.push(label.new(bar_index[1], nz(v_resBot[1], high), text = "Break Res", style = label.style_label_up,
             color = #2b6d2d, textcolor = chart.fg_color, size = size.small))
    while srLabels.size() > capSR
        srLabels.shift().delete()

// --- LuxAlgo liquidity sweeps -----------------------------------------------------------------------------------------
var array<box>  lsBoxes = array.new<box>(0)
var array<line> lsLines = array.new<line>(0)
f_lsPush(array<box> bxa, array<line> lna, box b, line l1, line l2, simple int cap) =>
    bxa.push(b)
    lna.push(l1)
    lna.push(l2)
    while bxa.size() > cap
        bxa.shift().delete()
    while lna.size() > cap * 2
        lna.shift().delete()
    bxa.size()

if showLS and inDrawWindow and barstate.isconfirmed
    if v_lsBullNow
        box  b  = box.new(bar_index - 1, v_lsLvl, bar_index + 1, low, border_color = color(na), bgcolor = color.new(#089981, 75))
        line l1 = line.new(bar_index, v_lsLvl, bar_index, low, color = #089981, width = 3)
        line l2 = line.new(bar_index, high, bar_index + 3, high, color = #089981, style = line.style_dotted)
        f_lsPush(lsBoxes, lsLines, b, l1, l2, capLS)
    if v_lsBearNow
        box  b  = box.new(bar_index - 1, high, bar_index + 1, nz(v_lsBearHigh, high), border_color = color(na), bgcolor = color.new(#f23645, 75))
        line l1 = line.new(bar_index, high, bar_index, close, color = #f23645, width = 3)
        line l2 = line.new(bar_index, low, bar_index + 3, low, color = #f23645, style = line.style_dotted)
        f_lsPush(lsBoxes, lsLines, b, l1, l2, capLS)

// --- LuxAlgo buyside / sellside liquidity ------------------------------------------------------------------------------
var array<bsl_lvl> bslArr = array.new<bsl_lvl>(0)
f_bslEvict(array<bsl_lvl> arr, simple int cap) =>
    while arr.size() > cap
        bsl_lvl z = arr.shift()
        z.ln.delete()
        z.lne.delete()
        z.bxt.delete()
    arr.size()

if showBSL and inDrawWindow and barstate.isconfirmed
    if v_newBuyLiq or v_newSellLiq
        color c  = v_newBuyLiq ? color.new(#4caf50, 0) : color.new(#f23645, 0)
        string t = v_newBuyLiq ? "Buyside liquidity" : "Sellside liquidity"
        line  ln  = line.new(v_evBar, v_evPrc, bar_index - 1, v_evPrc, color = c)
        line  lne = line.new(bar_index - 1, v_evPrc, bar_index, v_evPrc, color = c, style = line.style_dotted)
        box   bxt = box.new(v_evBar, v_evPrc, bar_index + 10, v_evPrc, text = t, text_size = size.tiny,
             text_halign = text.align_left, text_valign = v_newBuyLiq ? text.align_bottom : text.align_top,
             text_color = color.new(c, 25), bgcolor = color(na), border_color = color(na))
        bslArr.push(bsl_lvl.new(ln, lne, bxt, v_evPrc, v_evTop, v_evBot, false))
        f_bslEvict(bslArr, capBSL)
    if bslArr.size() > 0
        for i = 0 to bslArr.size() - 1
            bsl_lvl z = bslArr.get(i)
            if not z.brk
                z.lne.set_x2(bar_index)
                z.bxt.set_right(bar_index + 10)
                if high > z.top or low < z.bot
                    z.brk := true

// --- Supply & demand zones (fixed objects, reused, so no eviction needed) ----------------------------------------------
var sd_area sdSupply = sd_area.new(
     box.new(na, na, na, na, border_color = color(na), bgcolor = color.new(#2157f3, 80)),
     line.new(na, na, na, na, color = #2157f3))
var sd_area sdDemand = sd_area.new(
     box.new(na, na, na, na, border_color = color(na), bgcolor = color.new(#ff5d00, 80)),
     line.new(na, na, na, na, color = #ff5d00))
var line sdEquiLine = line.new(na, na, na, na, color = color.gray, style = line.style_dashed)

if showSD and inDrawWindow and barstate.isconfirmed and not na(v_sdSupTop)
    int x1 = bar_index - s2_lb + 1
    sdSupply.bx.set_lefttop(x1, v_sdSupTop)
    sdSupply.bx.set_rightbottom(bar_index, v_sdSupBot)
    sdSupply.avg.set_xy1(x1, math.avg(v_sdSupTop, v_sdSupBot))
    sdSupply.avg.set_xy2(bar_index, math.avg(v_sdSupTop, v_sdSupBot))
    sdDemand.bx.set_lefttop(x1, v_sdDemTop)
    sdDemand.bx.set_rightbottom(bar_index, v_sdDemBot)
    sdDemand.avg.set_xy1(x1, math.avg(v_sdDemTop, v_sdDemBot))
    sdDemand.avg.set_xy2(bar_index, math.avg(v_sdDemTop, v_sdDemBot))
    sdEquiLine.set_xy1(x1, v_sdEqui)
    sdEquiLine.set_xy2(bar_index, v_sdEqui)

// --- LuxAlgo pivot points high / low (regular pivots + zigzag; ghost levels omitted) ------------------------------------
var array<label> ppLabels = array.new<label>(0)
var array<line>  ppLines  = array.new<line>(0)
var int   ppPrevX = na
var float ppPrevY = na
if showPPHL and inDrawWindow and barstate.isconfirmed
    if not na(v_ppPhRaw)
        ppLabels.push(label.new(bar_index - s7_len, v_ppPhRaw, "▼", textcolor = color.white, color = #ef5350,
             style = label.style_label_down, size = size.small, tooltip = str.tostring(v_ppPhRaw, "#.####")))
        if not na(ppPrevX)
            ppLines.push(line.new(ppPrevX, ppPrevY, bar_index - s7_len, v_ppPhRaw, color = #26a69a))
        ppPrevX := bar_index - s7_len
        ppPrevY := v_ppPhRaw
    if not na(v_ppPlRaw)
        ppLabels.push(label.new(bar_index - s7_len, v_ppPlRaw, "▲", textcolor = color.white, color = #26a69a,
             style = label.style_label_up, size = size.small, tooltip = str.tostring(v_ppPlRaw, "#.####")))
        if not na(ppPrevX)
            ppLines.push(line.new(ppPrevX, ppPrevY, bar_index - s7_len, v_ppPlRaw, color = #ef5350))
        ppPrevX := bar_index - s7_len
        ppPrevY := v_ppPlRaw
    while ppLabels.size() > capPP
        ppLabels.shift().delete()
    while ppLines.size() > capPP
        ppLines.shift().delete()

// --- live trade levels + entry label --------------------------------------------------------------------------------
plot(plotRR and inPos ? effStop : na, "Active Stop", color = color.new(color.red, 0), style = plot.style_stepline, linewidth = 2)
// plot() has no dashed style, so take-profit levels are drawn as circle plots.
plot(plotRR and inPos and (useRRengine ? rrUsePartialTp : stUsePartials) ? posTp1 : na, "TP1", color = color.new(color.green, 20), style = plot.style_circles)
plot(plotRR and inPos and (useRRengine ? rrUsePartialTp : stUsePartials) ? posTp2 : na, "TP2", color = color.new(color.green, 40), style = plot.style_circles)
plot(plotRR and inPos ? posTp3 : na, "TP3 / target", color = color.new(color.green, 60), style = plot.style_circles)

var array<label> entryLabels = array.new<label>(0)
if showEntryLbl and longSignal and inDrawWindow
    entryLabels.push(label.new(bar_index, low, "▲ " + str.tostring(nPassing) + "/" + str.tostring(nEnabled),
         style = label.style_label_up, color = color.new(color.teal, 20), textcolor = color.white, size = size.small,
         tooltip = "Entry " + str.tostring(close, format.mintick) + "  SL " + str.tostring(plannedStop, format.mintick)))
    while entryLabels.size() > capLbl
        entryLabels.shift().delete()

// --- data-window series -----------------------------------------------------------------------------------------------
plot(g2Rsi,   "G2 RSI",  display = display.data_window)
plot(g3Vfi,   "G3 VFI",  display = display.data_window)
plot(atr,     "ATR",     display = display.data_window)

// --- entry funnel counters ---------------------------------------------------------------------------------------------
// ta.cum() is hoisted out of the plot ternaries: running totals must accumulate on every bar, not only when shown.
float nGates   = ta.cum(gatesOk      ? 1 : 0)
float nStruct  = ta.cum(structOk     ? 1 : 0)
float nFilters = ta.cum(filtersOk    ? 1 : 0)
float nTrig    = ta.cum(primaryFired ? 1 : 0)
float nBoth    = ta.cum(filtersOk and primaryFired ? 1 : 0)
float nStopOk  = ta.cum(stopUsable   ? 1 : 0)
float nEntries = ta.cum(longSignal   ? 1 : 0)
plot(showFunnel ? nGates   : na, "n· gates ok",         display = display.data_window)
plot(showFunnel ? nStruct  : na, "n· structure ok",     display = display.data_window)
plot(showFunnel ? nFilters : na, "n· all filters",      display = display.data_window)
plot(showFunnel ? nTrig    : na, "n· trigger",          display = display.data_window)
plot(showFunnel ? nBoth    : na, "n· filters+trigger",  display = display.data_window)
plot(showFunnel ? nStopOk  : na, "n· stop usable",      display = display.data_window)
plot(showFunnel ? nEntries : na, "n· ENTRIES",          display = display.data_window)


// =====================================================================================================================
//  DEBUG TABLE
// =====================================================================================================================
f_mark(bool en, bool ok) => not en ? "–" : ok ? "✓" : "✗"
f_col(bool en, bool ok)  => not en ? color.gray : ok ? color.new(color.green, 0) : color.new(color.red, 0)

var table dbg = table.new(position.top_right, 4, 15, border_width = 1, frame_width = 1, frame_color = color.new(color.gray, 50))

// Pine forbids declaring a function inside a local scope, so the row writer lives at global scope and takes the
// table, text size and header colour as parameters.
f_row(table t, int r, string nm, string tf, string val, bool en, bool ok, string tsz, color hdr) =>
    t.cell(0, r, nm,  text_color = color.silver, bgcolor = hdr, text_size = tsz)
    t.cell(1, r, tf,  text_color = color.silver, text_size = tsz)
    t.cell(2, r, val, text_color = color.white,  text_size = tsz)
    t.cell(3, r, f_mark(en, ok), text_color = f_col(en, ok), text_size = tsz)
    r

if showTable and barstate.islast
    string tsz = tblSize == "Tiny" ? size.tiny : tblSize == "Normal" ? size.normal : size.small
    color  hdr = color.new(color.gray, 80)
    dbg.cell(0, 0, "Filter", text_color = color.white, bgcolor = color.new(color.blue, 40), text_size = tsz)
    dbg.cell(1, 0, "TF",     text_color = color.white, bgcolor = color.new(color.blue, 40), text_size = tsz)
    dbg.cell(2, 0, "Value",  text_color = color.white, bgcolor = color.new(color.blue, 40), text_size = tsz)
    dbg.cell(3, 0, "OK",     text_color = color.white, bgcolor = color.new(color.blue, 40), text_size = tsz)

    f_row(dbg, 1,  "G1 Supertrend", f_tfn(g1_tf), g1Bull ? "Bull" : "Bear", useG1, g1_ok, tsz, hdr)
    f_row(dbg, 2,  "G2 RSI",        f_tfn(g2_tf), str.tostring(g2Rsi, "#.#"), useG2, g2_ok, tsz, hdr)
    f_row(dbg, 3,  "G3 VFI",        f_tfn(g3_tf), g3Ready ? str.tostring(g3Vfi, "#.##") : "warmup", useG3, g3_ok, tsz, hdr)
    f_row(dbg, 4,  "G4 SR support", f_tfn(g4_tf), na(g4SupBot) ? "–" : str.tostring(g4SupBot, format.mintick), useG4, g4_ok, tsz, hdr)
    f_row(dbg, 5,  "S1 Liq sweep",  f_tfn(s1_tf), s1Age >= 100000 ? "none" : str.tostring(int(s1Age)) + " bars", useS1, s1_ok, tsz, hdr)
    f_row(dbg, 6,  "S2 Demand",     f_tfn(s2_tf), na(s2DemBot) ? "–" : str.tostring(s2DemBot, format.mintick), useS2, s2_ok, tsz, hdr)
    f_row(dbg, 7,  "S3 Support",    f_tfn(s3_tf), na(s3SupBot) ? "–" : str.tostring(s3SupBot, format.mintick), useS3, s3_ok, tsz, hdr)
    f_row(dbg, 8,  "S4 Supertrend", f_tfn(s4_tf), na(s4Line) ? "–" : str.tostring(s4Line, format.mintick), useS4, s4_ok, tsz, hdr)
    f_row(dbg, 9,  "S5 PP ST",      f_tfn(s5_tf), na(s5Tsl) ? "–" : str.tostring(s5Tsl, format.mintick), useS5, s5_ok, tsz, hdr)
    f_row(dbg, 10, "S6 Sellside",   f_tfn(s6_tf), na(s6SellLvl) ? "–" : str.tostring(s6SellLvl, format.mintick), useS6, s6_ok, tsz, hdr)
    f_row(dbg, 11, "S7 Pivot low",  f_tfn(s7_tf), na(s7PivLow) ? "–" : str.tostring(s7PivLow, format.mintick), useS7, s7_ok, tsz, hdr)
    f_row(dbg, 12, "Trigger",       f_tfn(trig_tf), at_en and at_primary ? "AlphaTrend" : trigMode, useTrigger or (at_en and at_primary), primaryFired, tsz, hdr)

    dbg.cell(0, 13, "PASSING", text_color = color.white, bgcolor = color.new(color.gray, 60), text_size = tsz)
    dbg.cell(1, 13, "", bgcolor = color.new(color.gray, 60), text_size = tsz)
    dbg.cell(2, 13, str.tostring(nPassing) + " / " + str.tostring(nEnabled) + " enabled",
         text_color = color.white, bgcolor = filtersOk ? color.new(color.green, 55) : color.new(color.red, 55), text_size = tsz)
    dbg.cell(3, 13, f_mark(true, filtersOk), text_color = f_col(true, filtersOk), bgcolor = color.new(color.gray, 60), text_size = tsz)

    string posTxt = "flat"
    if inPos
        posTxt := "LONG @" + str.tostring(posEntry, format.mintick) +
             "  SL " + str.tostring(effStop, format.mintick) +
             "  TP " + str.tostring(posTp3, format.mintick) +
             "  uR " + str.tostring(longUnrealR, "#.##")
    dbg.cell(0, 14, "Position", text_color = color.silver, bgcolor = hdr, text_size = tsz)
    dbg.cell(1, 14, useRRengine ? "R:R" : "Struct", text_color = color.silver, text_size = tsz)
    dbg.cell(2, 14, posTxt, text_color = color.white, text_size = tsz)
    dbg.cell(3, 14, stopUsable ? "SL ok" : "SL n/a", text_color = stopUsable ? color.green : color.red, text_size = tsz)


// =====================================================================================================================
//  ALERTS  (JSON payloads for webhook / bot consumption)
// =====================================================================================================================
f_json(string act, float px, float qty, float sl, float t1, float t2, float t3) =>
    '{"action":"' + act + '"' +
     ',"secret":"' + ALERT_SECRET + '"' +
     ',"symbol":"' + (use_custom_webhook ? alert_symbol : syminfo.ticker) + '"' +
     ',"exchange":"' + syminfo.prefix + '"' +
     ',"tf":"' + timeframe.period + '"' +
     ',"price":"' + str.tostring(px, format.mintick) + '"' +
     ',"qty":"' + (na(qty) ? "" : str.tostring(qty, "#.########")) + '"' +
     ',"quote_usdt":"' + str.tostring(alert_buy_usdt, "#.##") + '"' +
     ',"sl":"' + (na(sl) ? "" : str.tostring(sl, format.mintick)) + '"' +
     ',"tp1":"' + (na(t1) ? "" : str.tostring(t1, format.mintick)) + '"' +
     ',"tp2":"' + (na(t2) ? "" : str.tostring(t2, format.mintick)) + '"' +
     ',"tp3":"' + (na(t3) ? "" : str.tostring(t3, format.mintick)) + '"' +
     ',"time":"' + str.format_time(time_close, "yyyy-MM-dd'T'HH:mm:ssZ", "UTC") + '"}'

float noQty = na

if longSignal and not na(entryQty)
    alert(f_json("buy", close, entryQty, posStop, posTp1, posTp2, posTp3), alert.freq_once_per_bar_close)
if inPos and not a_tp1 and not na(posTp1) and high >= posTp1
    a_tp1 := true
    alert(f_json("tp1_fill", posTp1, noQty, effStop, posTp1, posTp2, posTp3), alert.freq_once_per_bar_close)
if inPos and not a_tp2 and not na(posTp2) and high >= posTp2
    a_tp2 := true
    alert(f_json("tp2_fill", posTp2, noQty, effStop, posTp1, posTp2, posTp3), alert.freq_once_per_bar_close)
if inPos and not a_tp3 and not na(posTp3) and high >= posTp3
    a_tp3 := true
    alert(f_json("tp3_fill", posTp3, noQty, effStop, posTp1, posTp2, posTp3), alert.freq_once_per_bar_close)
if inPos and not a_stop and not na(effStop) and low <= effStop
    a_stop := true
    bool wasTrailing = not na(trailAnchor) and trailAnchor >= nz(posStop, trailAnchor)
    alert(f_json(wasTrailing ? "trailing_stop_hit" : "stop_hit", effStop, noQty, effStop, posTp1, posTp2, posTp3), alert.freq_once_per_bar_close)
if closeNow
    alert(f_json("manual_exit", close, noQty, effStop, posTp1, posTp2, posTp3), alert.freq_once_per_bar_close)

Overview

Gates include Supertrend, RSI and Volume Flow Indicator, with structure filters such as support zones, supply and demand, liquidity sweeps and optional AlphaTrend. Two stop and target engines are offered: a swing-low reward-to-risk bracket or a structural stop. Sizing is fixed cash or risk percent. Higher-timeframe reads use closed bars by default.

Details

Published by

MuhammadMuhamid on GitHub

Draws

On the price chart

Markets

Crypto

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.

Settings

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

SettingTypeDefaultVariable
Signals on confirmed bar onlybooltrueuseBarConfirm
New entries: bar close only (3Commas)booltrueordersOnConfirmedBar
Min bars after exit before new entryint0cooldownBarsAfterExit
HTF reads: last CLOSED bar only (no repaint)booltruehtfClosed
Signals from Heikin Ashi candlesboolfalseuseHeikin
Pause entries after N consecutive lossesboolfalseuseChoppyFilter
Max consecutive losses before pauseint2maxConsecLoss
Pause duration (bars) after max losses hitint20choppyPauseBars
Signal deliverystringOffsignal_delivery
[Custom bot] secret → WEBHOOK_SECRETstringREPLACE_MEALERT_SECRET
[Custom bot] BUY quote USDTfloat800.0alert_buy_usdt
[Custom bot] SELL base qtyfloat0.001alert_sell_base
Show all 40 settings
SettingTypeDefaultVariable
[Custom bot] symbolstringZECUSDTalert_symbol
Stop entries after a profitable winning runboolfalseuseRunLimit
Winning trades in the runint5runLimTrades
Min combined profit % of the runfloat9.0runLimProfitPct
Pause new entries for N bars after cap hitint30runLimPauseBars
Sizing ModestringFixed USDTsizingMode
Trade Amount (USDT)float1000.0tradeAmt
Risk % of Equityfloat1.0riskPct
ATR length (risk)int14atrLenRisk
Stop / TP enginestringR:R (swing low + ratio)stopEngine
G1 · Enable — Supertrend must be bullishbooltrueuseG1
G1 timeframetimeframe60g1_tf
G1 ATR periodint10g1_atrLen
G1 ATR multiplierfloat3.0g1_mult
G1 change ATR calculation method?booltrueg1_chgAtr
G2 · Enable — RSI above thresholdbooltrueuseG2
G2 timeframetimeframe60g2_tf
G2 RSI lengthint14g2_len
G2 RSI thresholdfloat50.0g2_thr
G2 RSI max (blocks overbought chasing)float100.0g2_max
G3 · Enable — VFI above levelbooltrueuseG3
G3 timeframetimeframe60g3_tf
G3 VFI lengthint130g3_len
G3 VFI coeffloat0.2g3_coef
G3 VFI max volume cutofffloat2.5g3_vcoef
G3 VFI signal lengthint5g3_sig
G3 smooth VFIboolfalseg3_smooth
G3 VFI must be abovefloat0.0g3_level
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

FilledValueGap A006

by ruckard
13 stars

This strategy enters trades in the opposite direction of filled value gaps, operating under the assumption that price action will resume the primary trend after filling a recent gap. It plots directly on the main price chart and supports MA-based trend filters

Strategy v5 SMC / ICT / price actionTrend
Updated 1 year ago · None stated GitHub repository of FilledValueGap A006

This strategy identifies reversal opportunities based on liquidity sweeps and multi-gate confluence scoring. It evaluates setups using mandatory conditions such as swing sweeps, body and ATR filters, and dual EMA higher-timeframe trend alignment, alongside opt

Strategy v6 SMC / ICT / price actionTrend
Updated 3 days ago · MIT GitHub repository of Reversal Engine Score [WavesUnchained] Strategy

XXX

by MeridianAlgo
9 stars

This multi-timeframe scalping strategy plots moving averages alongside Smart Money Concepts elements on the price chart. It calculates trend signals using configurable moving average types like ALMA, Hull MA, or TEMA across alternate timeframes. It also plots

Strategy v5 TrendMoving averages Repaints
Updated 4 months ago · MIT GitHub repository of XXX

FVG Strategy

by muammeryldrm42
7 stars

Calculates three-bar Fair Value Gaps (FVG) and filters them using Average True Range. When price mitigates the FVG midpoint, the strategy enters a position in the direction of the gap, provided an optional EMA trend filter is satisfied. Exits are defined by an

Strategy v5 SMC / ICT / price actionSupport & resistance
Updated 3 months ago · MIT GitHub repository of FVG Strategy

NQ 9:45-10:15 ICT Strategy

by eddiebelaval
7 stars

This ICT-based strategy trades during the 9:45 AM to 10:15 AM New York session window. It calculates a daily bias using a 20-period daily EMA and tracks previous day highs and lows to identify liquidity sweeps. The script refines order block entry levels and m

Strategy v6 SMC / ICT / price actionSessions & time Repaints
Updated 3 months ago · None stated GitHub repository of NQ 9:45-10:15 ICT Strategy

More SMC / ICT scripts

Related on ForexR

Trade Your Charts With a Regulated Broker

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

Compare brokers