Strategy · Pine Script v6 · On the chart

SR+Trend v12

by MuhammadMuhamid

A long-only crypto spot strategy whose support and resistance section can be switched on or off and set as the main entry trigger or as a secondary filter.

View on GitHub

GitHub stars1on the repository
Pine versionv6current
Lines of code1,711
Settings40inputs
LicenceNone statedfile: MPL-2.0
Last update12 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,711 lines of Pine Script v6 from SR_Uptrend_Strategy.pine, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

SR_Uptrend_Strategy.pine
// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// SR+Trend v9 — MTF pivot S/R + MA trend (MTF) + SuperTrend + LinReg + VWMA + Volume + HH/HL structure
//
// WHAT CHANGED vs v8:
//   ADDED:   Session Time Filter (Logic 1) — restrict entries to UTC hours; auto-close at session end.
//   ADDED:   Entry Bar Body Filter (Logic 2) — reject bearish entry bars exceeding ATR threshold.
//   ADDED:   Vol Exhaust Re-Entry Block (Logic 4) — prevent re-entry near exit zone for N bars.
//   ADDED:   Adaptive TP (Logic 5) — higher R in strong HH-confirmed trend, lower in neutral market.
//   ADDED:   ATR Spike Filter (Logic 6) — block entries during abnormal volatility spikes.
//
// WHAT CHANGED vs v5:
//   REMOVED: All MACD, RSI, ADX, and IMBA (Fair Value Gap / Fib-channel) code.
//   ADDED:   HTF Regime EMA filter (clean ADX replacement — price above rising 4h EMA).
//   ADDED:   Volume Exhaustion exit (new N-bar high on drying volume = distribution top).
//   ADDED:   HL Structure Break exit (confirmed close below last pivot low = early trend invalidation).
//   ADDED:   Break-Even Stop (move SL to entry after +0.75R — kills "gave-back-all-gains" losses).
//   ADDED:   Consecutive-Loss Pause / Choppy-Market Circuit Breaker (blocks re-entry during ranging).
//
// --- 3Commas Signal bot (TradingView Strategy) ---
// 1) 3Commas → Signal bot → Alert type "TradingView Strategy" → copy webhook + JSON "secret" + "bot_uuid".
// 2) Webhook URL (Signal bots only): https://api.3commas.io/signal_bots/webhooks
// 3) TradingView alert: prefer "Order fills only" (not "… and alert()") when Signal delivery = 3Commas.
// 4) Properties → "Recalculate after order is filled": ON vs OFF uses a different backtest timeline.
//    Keep the same setting you used while tuning.
// 5) Alert "Message": paste JSON from 3commas_alert_message_template.json (replace secret + bot_uuid only).
// 6) Chart exchange + symbol must match your Signal bot pair (e.g. BINANCE:APTUSDT).
//@version=6
strategy("SR+Trendv12", shorttitle = "SRTrendv12", overlay = true,
     max_labels_count = 500, max_lines_count = 300,
     initial_capital = 1000, default_qty_type = strategy.cash, default_qty_value = 930,
     commission_type = strategy.commission.percent, commission_value = 0.05,
     slippage = 2, pyramiding = 0,
     calc_on_every_tick = false, calc_on_order_fills = false)

// ── Groups ────────────────────────────────────────────────────────────────────
string G_GEN       = "1 General"
string G_SR        = "2 Support / Resistance (MTF pivots)"
string G_TR_MA     = "3 Trend — Moving averages (MTF)"
string G_TR_ST     = "4 Trend — SuperTrend"
string G_TR_LR     = "5 Trend — Linear regression"
string G_TR_VW     = "6a Trend — VWMA (optional)"
string G_TR_LOCAL  = "6d Trend — Local EMA stack (micro)"
string G_TR_VOL    = "6e Volume filter"
string G_TR_HH     = "6f Higher-high structure"
string G_ENT_LONG  = "7 Entries — Long (support)"
string G_ENT_SHRT  = "8 Entries — Short (resistance)"
string G_EXIT      = "9 Risk — SL / TP / Trailing / Exits"
string G_RR        = "9b R:R — swing-low stop + ratio TP"
string G_VIS       = "10 Visualization"
string G_RSI       = "14 RSI (embedded)"
// Partial-TP controls live inside the Risk section (group 9) so TP1/TP2/TP3 + trailing
// sit alongside SL / break-even. Same string as G_EXIT → merged into one settings group.
string G_PTP       = "9 Risk — SL / TP / Trailing / Exits"
string G_RF        = "18 Indicator — Range Filter"
string G_QT        = "19 Indicator — Q-Trend"
string G_AT        = "20 Indicator — AlphaTrend"
string G_HAC       = "21 Indicator — HACOLT (Heiken-Ashi)"

// ── 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 + many 3Commas 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 = 50, group = G_GEN)

// Choppy-market circuit breaker — pauses entries after back-to-back losses.
// Solves the NEAR May 20-21 pattern: 4 consecutive losses in 36h during a sideways range
// where all MA filters remained bullish but every support touch immediately reversed.
useChoppyFilter = input.bool(true, "Pause entries after N consecutive losses", group = G_GEN,
     tooltip = "After maxConsecLoss losing trades in a row, block all new entries for choppyPauseBars bars. Detects distribution / choppy phases that look bullish on the macro but fail at the micro level.")
maxConsecLoss   = input.int(2, "Max consecutive losses before pause", minval = 1, maxval = 10, group = G_GEN,
     tooltip = "2 is the recommended default. Raise to 3 on high win-rate coins where two losses in a row still represent normal variance.")
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. Set to 48 for a 4-hour pause. The market usually resolves its range or breaks out within this window.")

signal_delivery = input.string("3Commas Signal Bot", "Signal delivery",
     options = ["3Commas Signal Bot", "Custom webhook bot", "Off"], group = G_GEN,
     tooltip = "3Commas: add a TradingView alert manually with 3Commas JSON + webhook (see header). Custom: FastAPI bot 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("APTUSDT", "[Custom bot] symbol", group = G_GEN, display = display.none)
use_custom_webhook     = signal_delivery == "Custom webhook bot"

// ── SR — pivot-based MTF ──────────────────────────────────────────────────────
srEnable         = input.bool(false, "Enable Support / Resistance section", group = G_SR,
     tooltip = "Master on/off for the S/R pivot module. OFF = S/R neither triggers nor filters entries.")
srPrimary        = input.bool(false, "S/R is PRIMARY (entry trigger)", group = G_SR,
     tooltip = "ON  = a fresh S/R support retest TRIGGERS an entry (primary signal).\nOFF = S/R only acts as a confirming FILTER (price must be in a valid retest state) while some other primary indicator triggers the entry.")
touchBandMode    = input.string("ATR", "Touch zone mode", options = ["ATR", "Ticks"], group = G_SR)
touchAtrMult     = input.float(0.85, "Touch zone × ATR", step = 0.05, minval = 0.05, group = G_SR, inline = "tz")
touchTicks       = input.int(5, "or ticks", minval = 1, group = G_SR, inline = "tz")
srStrengthMin    = input.int(1, "Minimum pivot touches", minval = 1, maxval = 10, group = G_SR)
strengthTf       = input.timeframe("15", "Strength: pivot-touch TF", group = G_SR)
strengthPivotLen = input.int(14, "Strength TF pivot length", minval = 3, maxval = 50, group = G_SR)
tf5_en   = input.bool(true, "Use 5m S/R",  group = G_SR, inline = "tf5")
pivLen5  = input.int(13, "",  minval = 3, maxval = 50, group = G_SR, inline = "tf5")
tf15_en  = input.bool(true, "Use 15m S/R", group = G_SR, inline = "tf15")
pivLen15 = input.int(9,  "",  minval = 3, maxval = 50, group = G_SR, inline = "tf15")
tf60_en  = input.bool(true, "Use 1h S/R",  group = G_SR, inline = "tf60")
pivLen60 = input.int(9,  "",  minval = 3, maxval = 50, group = G_SR, inline = "tf60")
tf240_en  = input.bool(true, "Use 4h S/R", group = G_SR, inline = "tf240")
pivLen240 = input.int(5, "",  minval = 3, maxval = 50, group = G_SR, inline = "tf240")
useRetestWindow   = input.bool(true, "Use: bars after support touch to confirm entry", group = G_SR,
     tooltip = "ON = a long is only valid within N bars after a support touch (retest window). OFF = the retest-window requirement is removed.")
retestConfirmBars = input.int(8, "Bars after support touch to confirm entry", minval = 1, maxval = 30, group = G_SR)
usePriorAbove     = input.bool(true, "Use: pullback — prior bars close above support", group = G_SR,
     tooltip = "ON = require the prior N bars' closes to have been above the support level (genuine pullback from above). OFF = skip this pullback requirement.")
priorAboveLb      = input.int(4, "Pullback: prior bars close above support",  minval = 1, maxval = 20, group = G_SR)
blockBreakReclaim = input.bool(true, "Block pierce-and-reclaim entries", group = G_SR)
reclaimPierceAtr  = input.float(0.05, "Pierce below support (× ATR) = reclaim block", step = 0.05, minval = 0, group = G_SR)
useResFlipSup     = input.bool(false, "Broken MTF resistance → support (retest longs)", group = G_SR)
flipConfirmOnClose  = input.bool(false, "Flip on confirmed bar only", group = G_SR)
flipBreakBufAtr     = input.float(0.05, "Flip when close above res (+ × ATR)", step = 0.05, minval = 0, group = G_SR)
flipInvalidBufAtr   = input.float(0.2,  "Unflip when close below res (− × ATR)", step = 0.05, minval = 0.01, group = G_SR)

// ── MA MTF trend ──────────────────────────────────────────────────────────────
useMaTrend     = input.bool(false, "Require MA uptrend / downtrend", group = G_TR_MA)
maPrimary      = input.bool(false, "MA trend is PRIMARY (entry trigger)", group = G_TR_MA,
     tooltip = "ON  = MA alignment (close above all enabled rising MAs) TRIGGERS entries (primary signal).\nOFF = MA trend only acts as a confirming FILTER while another primary indicator triggers the entry.")
maBlockWhenNa  = input.bool(true, "MA: block entries while MTF MA is na", group = G_TR_MA)
plotMaLines    = input.bool(false, "Plot MTF MA lines (TF1–TF4)", group = G_TR_MA)
requireMaSlope = input.bool(false, "Require enabled MAs to be rising (slope)", group = G_TR_MA)
maSlopeLb      = input.int(5, "MA slope lookback (chart bars)", minval = 1, maxval = 50, group = G_TR_MA)
// Each MA has its own type (SMA/EMA/WMA/RMA/VWMA/HMA) and source (open/high/low/close).
ma1_en   = input.bool(true,  "TF1", group = G_TR_MA, inline = "m1")
ma1_tf   = input.timeframe("1",   "", group = G_TR_MA, inline = "m1")
ma1_len  = input.int(200, "", group = G_TR_MA, inline = "m1")
ma1_type = input.string("SMA", "", options = ["SMA", "EMA", "WMA", "RMA", "VWMA", "HMA"], group = G_TR_MA, inline = "m1")
ma1_src  = input.string("Close", "", options = ["Open", "High", "Low", "Close"], group = G_TR_MA, inline = "m1")
ma2_en   = input.bool(false, "TF2", group = G_TR_MA, inline = "m2")
ma2_tf   = input.timeframe("240", "", group = G_TR_MA, inline = "m2")
ma2_len  = input.int(100, "", group = G_TR_MA, inline = "m2")
ma2_type = input.string("SMA", "", options = ["SMA", "EMA", "WMA", "RMA", "VWMA", "HMA"], group = G_TR_MA, inline = "m2")
ma2_src  = input.string("Close", "", options = ["Open", "High", "Low", "Close"], group = G_TR_MA, inline = "m2")
ma3_en   = input.bool(true,  "TF3", group = G_TR_MA, inline = "m3")
ma3_tf   = input.timeframe("60",  "", group = G_TR_MA, inline = "m3")
ma3_len  = input.int(400, "", group = G_TR_MA, inline = "m3")
ma3_type = input.string("SMA", "", options = ["SMA", "EMA", "WMA", "RMA", "VWMA", "HMA"], group = G_TR_MA, inline = "m3")
ma3_src  = input.string("Close", "", options = ["Open", "High", "Low", "Close"], group = G_TR_MA, inline = "m3")
ma4_en   = input.bool(false, "TF4", group = G_TR_MA, inline = "m4")
ma4_tf   = input.timeframe("15",  "", group = G_TR_MA, inline = "m4")
ma4_len  = input.int(50, "", group = G_TR_MA, inline = "m4")
ma4_type = input.string("SMA", "", options = ["SMA", "EMA", "WMA", "RMA", "VWMA", "HMA"], group = G_TR_MA, inline = "m4")
ma4_src  = input.string("Close", "", options = ["Open", "High", "Low", "Close"], group = G_TR_MA, inline = "m4")

// ── SuperTrend ────────────────────────────────────────────────────────────────
useSuperTrend = input.bool(false, "Require SuperTrend alignment", group = G_TR_ST)
stTf          = input.timeframe("5", "SuperTrend timeframe", group = G_TR_ST)
stAtrLen      = input.int(15, "ATR period", minval = 1, group = G_TR_ST)
stMult        = input.float(2.5, "Multiplier", step = 0.1, minval = 0.1, group = G_TR_ST)
stSrc         = input.source(hl2, "Source (same TF as chart only)", group = G_TR_ST)

// ── LinReg ────────────────────────────────────────────────────────────────────
useLinReg  = input.bool(false, "Use LinReg candle filter", group = G_TR_LR)
lrTf       = input.timeframe("5", "LinReg timeframe", group = G_TR_LR)
lrLen      = input.int(12, "LinReg length", minval = 2, group = G_TR_LR)
sigLen     = input.int(11, "Signal line length (chart TF)", minval = 1, group = G_TR_LR)
sigUseSma  = input.bool(false, "Signal = SMA (off = EMA)", group = G_TR_LR)
plotLinReg = input.bool(false, "Plot LinReg close line", group = G_TR_LR)

// ── VWMA 1 ────────────────────────────────────────────────────────────────────
useVwmaTrend = input.bool(false, "Require close above VWMA (HTF)", group = G_TR_VW)
vwmaTf       = input.timeframe("240", "VWMA timeframe", group = G_TR_VW)
vwmaLen      = input.int(200, "VWMA length", minval = 1, group = G_TR_VW)
plotVwmaLine = input.bool(false, "Plot VWMA line", group = G_TR_VW)

// ── Local EMA stack (micro) ───────────────────────────────────────────────────
useLocalTrend    = input.bool(false, "Require local EMA trend alignment", group = G_TR_LOCAL,
     tooltip = "Fast EMA > slow EMA on the chosen TF confirms local directional bias. Use 5m to block entries when the 5m structure is down inside an HTF uptrend.")
localTrendTf     = input.timeframe("5", "Local EMA timeframe", group = G_TR_LOCAL)
localEmaFast     = input.int(21, "Fast EMA period", minval = 2, maxval = 200, group = G_TR_LOCAL)
localEmaSlow     = input.int(34, "Slow EMA period",  minval = 2, maxval = 500, group = G_TR_LOCAL)
requireAboveFast = input.bool(true, "Also require close above fast EMA (long)", group = G_TR_LOCAL,
     tooltip = "For longs: close must also be above the fast EMA. Adds momentum confirmation.")
plotLocalEma     = input.bool(false, "Plot local EMA lines", group = G_TR_LOCAL)

// ── Volume filter ─────────────────────────────────────────────────────────────
useVolumeFilter = input.bool(false, "Require above-average volume on entry", group = G_TR_VOL)
volTf           = input.timeframe("", "Volume timeframe (blank = chart)", group = G_TR_VOL,
     tooltip = "Timeframe used to evaluate volume vs its SMA for the entry filter. Blank = chart timeframe.")
volMaLen        = input.int(20, "Volume SMA length", minval = 5, group = G_TR_VOL)
volMultMin      = input.float(1.2, "Min volume (× SMA)", step = 0.1, minval = 0.1, group = G_TR_VOL,
     tooltip = "1.0 = at-least-average volume; 1.5 = 50% above average.")
plotVolSma      = input.bool(false, "Plot volume SMA line", group = G_TR_VOL)

// ── Higher-high structure filter ──────────────────────────────────────────────
useHhStructure = input.bool(false, "Require higher-high structure (HTF longs)", group = G_TR_HH,
     tooltip = "Last confirmed pivot high on the chosen TF must be above the prior pivot high. Validates real HTF uptrend structure.")
hhTf           = input.timeframe("15", "Higher-high timeframe", group = G_TR_HH)
hhPivotLen     = input.int(9, "Pivot length (HH TF)", minval = 3, maxval = 30, group = G_TR_HH)
plotHhStruct   = input.bool(false, "Plot higher-high structure (last/prior pivot highs)", group = G_TR_HH,
     tooltip = "Plots the most recent and prior confirmed pivot highs on the HH timeframe so you can see the structure the filter evaluates. Works even when the filter itself is off.")

// ── Long entries ──────────────────────────────────────────────────────────────
long_en          = input.bool(true, "Enable longs at support retest", group = G_ENT_LONG)
requireBounce    = input.bool(false, "Legacy: same-bar close above touched level", group = G_ENT_LONG)
lrLongRule       = input.string("Green candle", "LinReg long rule", options = ["Green candle", "Close > signal", "Both"], group = G_ENT_LONG)
useContLong      = input.bool(false, "Also enable continuation longs", group = G_ENT_LONG)
contBreakLb      = input.int(10, "Continuation: prior high lookback", minval = 2, maxval = 100, group = G_ENT_LONG)
contRequireGreen = input.bool(false, "Continuation: require green candle", group = G_ENT_LONG)
contRelaxStrength = input.bool(false, "Continuation: skip pivot-touch strength filter", group = G_ENT_LONG)
contExtraTpR      = input.float(1.0, "Continuation: extra TP when mode = R multiple", minval = 0, step = 0.25, group = G_ENT_LONG)

// Logic 2 — Entry Bar Body Filter
// Rejects bearish entry candles whose body exceeds a fraction of ATR.
// A bearish close on the entry bar = momentum already shifted against you; skip it.
useBodyFilter    = input.bool(false, "Body filter: reject bearish entry bar (Logic 2)", group = G_ENT_LONG,
     tooltip = "Skips entries where the trigger bar is bearish AND its body (open−close) exceeds entryBodyAtrMult × ATR. A big bearish close on the setup bar is a momentum warning — skip it and wait for the next bar.")
entryBodyAtrMult = input.float(0.3, "Body filter: max bearish body size (× ATR)", step = 0.05, minval = 0.05, group = G_ENT_LONG,
     tooltip = "0.3 = reject only if the bearish body is larger than 30% of ATR. Increase to be more lenient (0.5 allows a medium bearish bar). Decrease to be stricter.")

// ── Short entries ─────────────────────────────────────────────────────────────
short_en     = input.bool(false, "Enable shorts at resistance", group = G_ENT_SHRT)
lrShortRule  = input.string("Red candle", "LinReg short rule", options = ["Red candle", "Close < signal", "Both"], group = G_ENT_SHRT)

// ── Exits ─────────────────────────────────────────────────────────────────────
atrLenExit = input.int(7, "ATR length (risk)", minval = 1, group = G_EXIT)

useStopLoss = input.bool(true, "Use stop loss", group = G_EXIT,
     tooltip = "ON = place a protective hard stop (mode below). OFF = no hard stop is placed (TP / trailing / break-even and the other exits still operate). R-multiple targets still use the computed stop distance for sizing.")
slMode    = input.string("Below structure & ATR", "Stop loss mode",
     options = ["ATR multiple", "Percent", "Below structure & ATR", "Swing low lookback"], group = G_EXIT)
slAtrMult = input.float(0.6, "SL × ATR (from entry)", step = 0.1, minval = 0.1, group = G_EXIT)
slPct     = input.float(1.4, "SL percent (if Percent mode)", step = 0.05, minval = 0.05, group = G_EXIT)
slSwingLb = input.int(25, "Swing low lookback bars", minval = 2, group = G_EXIT)
structBuff = input.float(1.5, "Structure buffer × ATR under level", step = 0.05, group = G_EXIT)
minSlDistAtr = input.float(0.25, "Floor: min SL distance from entry (× ATR)", step = 0.05, minval = 0.05, group = G_EXIT)
plotSlLevel  = input.bool(false, "Plot stop-loss level (live, whichever SL mode)", group = G_EXIT,
     tooltip = "Plots the actual protective stop the strategy is using while in a position — reflects the chosen SL mode plus any break-even / trailing ratchet.")
plotSwingLow = input.bool(false, "Plot swing low (SL reference)", group = G_EXIT,
     tooltip = "Plots ta.lowest(low, swing low lookback) — the swing-low level the Swing-low SL mode references, so you can see where a structure-based stop would sit.")

// ── R:R — swing-low stop + ratio TP ───────────────────────────────────────────
// Dedicated risk:reward mode for longs: SL placed below the previous swing low,
// TP at an adjustable reward:risk multiple. When ON, it overrides the SL/TP for
// long entries and uses the single-target bracket (partial-TP engine is bypassed).
useRR      = input.bool(false, "Use R:R mode (swing-low SL + ratio TP) — longs", group = G_RR,
     tooltip = "ON = long SL is set just below the previous swing low and TP is taken at the reward:risk ratio below. Overrides the group-9 SL/TP and the partial-TP engine for longs. Shorts are unaffected.")
rrSwingLb  = input.int(10, "Swing low lookback (bars)", minval = 2, maxval = 200, group = G_RR,
     tooltip = "Number of bars used to find the previous swing low. The stop is placed below this low.")
rrBufAtr   = input.float(0.1, "SL buffer below swing low (× ATR)", step = 0.05, minval = 0.0, group = G_RR,
     tooltip = "Extra distance below the swing low, in ATR units, so the stop sits clear of the exact wick. 0 = exactly at the swing low.")
rrRatio    = input.float(2.0, "Reward : Risk ratio (TP)", step = 0.25, minval = 0.25, group = G_RR,
     tooltip = "Final take-profit distance as a multiple of risk. 2.0 = 1:2. This is the runner's target when partial TPs are on, or the single target when they are off. Adjustable.")
plotRR     = input.bool(false, "Plot R:R levels (live SL + TP)", group = G_RR,
     tooltip = "Plots the active long's stop (incl. trailing) and the reward:risk target. Also shows the PROJECTED SL/TP while flat so you can see where they would sit on the next entry.")

// R:R — multiple partial take-profits (percentage-based, toggleable)
rrUsePartialTp = input.bool(false, "R:R: take partial TPs (%) — TP1 / TP2 + runner", group = G_RR,
     tooltip = "ON = scale out at two percentage levels (TP1, TP2) and let the remainder run to the reward:risk target. OFF = single exit at the reward:risk target.")
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")

// R:R — trailing stop loss (percentage-based, toggleable)
rrUseTrailSl = input.bool(false, "R:R: trailing stop (%)", group = G_RR,
     tooltip = "ON = a percentage trailing stop ratchets up under price (never down). Combines with the swing-low stop and break-even — the highest of them is used.")
rrTrailPct   = input.float(3.0, "Trailing stop distance (%)", step = 0.1, minval = 0.1, group = G_RR,
     tooltip = "Stop trails this % below the close. 3 = stop sits 3% under price and only moves up.")
rrTrailActPct = input.float(0.0, "Trailing: activate after +% in profit (0 = once green)", step = 0.1, minval = 0.0, group = G_RR,
     tooltip = "Trailing stop only arms after price is this % above entry. 0 = arm as soon as the trade is in profit.")

// One-trade-per-indicator-signal gate.
useOneTradePerSignal = input.bool(false, "One trade per RF / Q-Trend / AlphaTrend buy signal", group = G_ENT_LONG,
     tooltip = "When ON, after a long is taken no new long is allowed until a FRESH buy trigger fires on Range Filter, Q-Trend, or AlphaTrend (whichever of those are enabled). Each indicator buy signal allows exactly one trade.")
minRForSoftExit = input.float(0.0, "Soft exit gate: min unrealized R (0 = off)", step = 0.25, minval = 0, group = G_EXIT,
     tooltip = "Regime and micro exits are suppressed until unrealized profit reaches this R multiple.")

// Break-even stop — moves hard SL to entry price after a defined R gain.
// Eliminates the "turned green then hit original stop" scenario. After the trade
// reaches breakEvenTriggerR in unrealized profit, losing is no longer possible.
useBreakEven      = input.bool(true, "Move SL to break-even after +R (long)", group = G_EXIT,
     tooltip = "Once unrealized profit reaches breakEvenTriggerR × initial risk, the hard stop moves to entry price. Converts a potential loss into a scratch exit on reversal.")
breakEvenTriggerR = input.float(0.75, "Break-even trigger: unrealized R", step = 0.25, minval = 0.25, group = G_EXIT,
     tooltip = "0.75 means: once the trade is up 75% of initial risk, SL moves to entry. Balance with trailTriggerR — break-even fires first, then trail takes over.")
breakEvenOffsetPct = input.float(0.0, "Break-even offset above entry (%) — covers fees", step = 0.05, minval = 0.0, group = G_EXIT,
     tooltip = "Places the break-even stop this % ABOVE the entry price instead of exactly at entry, so a scratch exit still covers commission/slippage. E.g. 0.2 = stop 0.2% above entry. 0 = exactly at entry (legacy behaviour).")

tpMode    = input.string("Risk multiple (R)", "Take profit mode",
     options = ["Risk multiple (R)", "ATR multiple", "Percent"], group = G_EXIT)
tpR       = input.float(2.0, "TP as R multiple", step = 0.25, minval = 0.25, group = G_EXIT)
tpAtrMult = input.float(12.0, "TP × ATR from entry", step = 0.25, minval = 0.25, group = G_EXIT)
tpPct     = input.float(1.5, "TP percent", step = 0.05, minval = 0.05, group = G_EXIT)

// Logic 5 — Adaptive TP (adjusts R target by trend strength)
// In a strong HH-confirmed uptrend: use a larger R target + later trail trigger.
// In neutral / weak trend: use a tighter R target + earlier trail trigger to bank profit faster.
useAdaptiveTp        = input.bool(true, "Adaptive TP: adjust R target by trend strength (Logic 5)", group = G_EXIT,
     tooltip = "When ON, the TP R-multiple and trail trigger R adapt to whether the HH structure filter confirms a strong uptrend. Strong = larger target; neutral = tighter target to bank profits sooner. Only applies when TP mode = Risk multiple (R).")
adaptiveTpStrong     = input.float(2.5, "Adaptive TP: R target in strong trend (HH confirmed)", step = 0.25, minval = 0.5, group = G_EXIT,
     tooltip = "Used when hhOkLong is true (higher-high structure confirmed). Larger R captures extended trends.")
adaptiveTpNeutral    = input.float(2.0, "Adaptive TP: R target in neutral / weak trend", step = 0.25, minval = 0.5, group = G_EXIT,
     tooltip = "Used when HH structure is NOT confirmed. Smaller R banks profits faster in choppier conditions.")
adaptiveTrailStrong  = input.float(1.75, "Adaptive trail trigger R: strong trend", step = 0.25, minval = 0.25, group = G_EXIT,
     tooltip = "Trail arms later (higher R) in a strong trend so the ratchet gives the trade more room to run.")
adaptiveTrailNeutral = input.float(1.25, "Adaptive trail trigger R: neutral trend", step = 0.25, minval = 0.25, group = G_EXIT,
     tooltip = "Trail arms earlier (lower R) in neutral trend to lock in profits quickly before reversal.")

useTrail      = input.bool(true, "Trailing stop after TP trigger", group = G_EXIT)
trailTriggerR = input.float(0.5, "Activate trail after +R unrealized", step = 0.25, minval = 0.25, group = G_EXIT)
trailAtrMult  = input.float(1.8, "Trail distance × ATR", step = 0.1, minval = 0.1, group = G_EXIT)
trailStyle    = input.string("ATR from close (ratchet)", "Trailing style",
     options = ["ATR from close (ratchet)", "Chandelier (swing ± ATR)"], group = G_EXIT,
     tooltip = "Ratchet: stop follows close − ATR×mult (ratchets up, never down). Chandelier: stop = highest high since trail armed − ATR×mult (fewer premature exits in strong trends).")

// HTF resistance break → runner trail
useHtfBreakTrail        = input.bool(true, "HTF res break: cancel TP limit + trail runner", group = G_EXIT)
htfResTf                = input.timeframe("60", "HTF resistance timeframe", group = G_EXIT)
htfResPivotLen          = input.int(9, "HTF pivot length (resistance)", minval = 3, maxval = 50, group = G_EXIT)
htfBreakConfirmMode     = input.string("HTF candle close", "Resistance break confirmation",
     options = ["HTF candle close", "Chart close (aligned level)"], group = G_EXIT)
htfBreakBufAtr          = input.float(0.25, "Break buffer × ATR above resistance", step = 0.05, minval = 0, group = G_EXIT)
htfTrailImmediate       = input.bool(true, "After break: arm trailing immediately", group = G_EXIT)
htfTrailTriggerR        = input.float(0.25, "After break: trail arms after +R (if not immediate)", step = 0.25, minval = 0, group = G_EXIT)
htfTrailAtrMult         = input.float(1.0, "After break: trail distance × ATR", step = 0.1, minval = 0.1, group = G_EXIT)
htfResetTrailAnchorOnBreak = input.bool(true, "After break: reset trail anchor", group = G_EXIT)

// Exit MA (4h SMA cross)
useExitLongMa  = input.bool(true, "Exit long: MTF close crosses below MA", group = G_EXIT)
useExitShortMa = input.bool(false, "Exit short: MTF close crosses above MA", group = G_EXIT)
exitMaTf       = input.timeframe("240", "Exit MA timeframe", group = G_EXIT)
exitMaLen      = input.int(100, "Exit MA length", minval = 1, group = G_EXIT)
exitMaType     = input.string("SMA", "Exit MA type", options = ["SMA", "EMA", "WMA", "RMA", "VWMA", "HMA"], group = G_EXIT)
exitMaSrc      = input.string("Close", "Exit MA source", options = ["Open", "High", "Low", "Close"], group = G_EXIT)
plotExitMa     = input.bool(false, "Plot exit MA (aligned to chart)", group = G_EXIT)

// Micro exits
useExitBelowSt     = input.bool(true, "Exit long: close below SuperTrend line", group = G_EXIT)
useExitBelowMa1    = input.bool(true, "Exit long: close below TF1 MA", group = G_EXIT)
useExitBelowLinReg = input.bool(true, "Exit long: close below LinReg close", group = G_EXIT)

// Volume Exhaustion Exit ──────────────────────────────────────────────────────
// Fires when price prints a new N-bar high while volume collapses. This is the
// classic distribution / topping signature. The trailing stop catches it only
// after a 2-4% giveback; this exit fires at the close of the exhaustion bar itself.
useVolExhaustExit = input.bool(true, "Exit long: volume exhaustion at new high", group = G_EXIT,
     tooltip = "Closes the long when the bar is a new N-bar high AND volume is below the exhaustion threshold. Catches distribution tops before the trailing stop gives back 2-4%.")
volExhaustMult    = input.float(0.6, "Volume exhaustion: max vol (× SMA)", step = 0.05, minval = 0.05, group = G_EXIT,
     tooltip = "Volume must drop to this fraction of its SMA to trigger. 0.6 = 40% below average. Lower = more selective. Start at 0.6 and tighten if too many early exits.")
volExhaustLb      = input.int(12, "Volume exhaustion: new-high lookback (bars)", minval = 2, maxval = 100, group = G_EXIT,
     tooltip = "Price must be printing the highest high over this many bars. Prevents premature exits on normal momentum bars mid-trend. 10-15 bars is typical on 5m.")
volExhaustMinR    = input.float(1.0, "Volume exhaustion: min unrealized R to activate", step = 0.25, minval = 0, group = G_EXIT,
     tooltip = "Suppress exhaustion exit until trade has this much unrealized profit. Prevents firing on early consolidation candles just after entry. 1.0R recommended.")

// HL Structure Break Exit ─────────────────────────────────────────────────────
// A confirmed close below the most recent pivot low on hlBreakTf is the first
// lower-low — structural invalidation of the local uptrend. Fires meaningfully
// earlier than waiting for the 4h MA cross or SuperTrend flip to catch up.
useHlBreakExit = input.bool(true, "Exit long: confirmed break of last pivot low (HL break)", group = G_EXIT,
     tooltip = "Closes the long when price closes below the most recent pivot low on hlBreakTf. First lower-low = uptrend structure gone. Fires faster than trailing stop or MA cross.")
hlBreakTf      = input.timeframe("15", "HL break pivot TF", group = G_EXIT,
     tooltip = "15m pivot low recommended when running on 5m charts. The 15m pivot low represents a meaningful local higher-low; breaking it is an early invalidation signal.")
hlBreakPivLen  = input.int(9, "HL break pivot length", minval = 3, maxval = 30, group = G_EXIT)
hlBreakMinR    = input.float(0.0, "HL break: min unrealized R to activate (0 = always)", step = 0.25, minval = 0, group = G_EXIT,
     tooltip = "Set > 0 to avoid exiting on the normal post-entry consolidation dip. 0 means always active. Recommended: 0 (let structure break always exit).")

// ── RSI (embedded) ────────────────────────────────────────────────────────────
// Wilder RSI ported from the provided RSI source (RMA-based up/down smoothing).
// Computed in the background and available to strategy conditions. Optional filter:
// require RSI within [rsiLongMin, rsiLongMax] on entry — keeps you out of exhausted
// (overbought) or falling-knife (deeply oversold) conditions when enabled.
useRsiFilter     = input.bool(false, "RSI filter: gate long entries by RSI band", group = G_RSI,
     tooltip = "When ON, a long entry requires RSI between the min and max below. Default band 45–70 favours momentum without buying into overbought blow-offs.")
rsiLengthInput   = input.int(14, "RSI Length", minval = 1, group = G_RSI)
rsiTf            = input.timeframe("", "RSI timeframe (blank = chart)", group = G_RSI,
     tooltip = "Timeframe the RSI is computed on. Blank = chart timeframe.")
rsiSourceInput   = input.source(close, "RSI Source", group = G_RSI)
rsiLongMin       = input.float(45.0, "Long entry: min RSI", minval = 0, maxval = 100, step = 1, group = G_RSI)
rsiLongMax       = input.float(70.0, "Long entry: max RSI", minval = 0, maxval = 100, step = 1, group = G_RSI)
useRsiExit       = input.bool(false, "Exit long when RSI crosses below level", group = G_RSI,
     tooltip = "Optional soft exit: closes the long on a confirmed RSI crossunder of rsiExitLevel. Momentum-rollover exit, independent of price structure.")
rsiExitLevel     = input.float(50.0, "RSI exit level (crossunder)", minval = 0, maxval = 100, step = 1, group = G_RSI)
plotRsiState     = input.bool(false, "Plot RSI state marker (background availability check)", group = G_RSI)

// ── Partial Take Profit (multi-stage) ─────────────────────────────────────────
// Replaces the single-target TP for longs with a 3-tier partial profit engine.
// Each tier closes a user-defined % of the ORIGINAL position at a user-defined %
// price move above entry. After TP1 fills, a trailing stop arms on the remaining
// contracts (source = fixed %, Chandelier Exit line, or ALMA SuperTrend line).
//
//   MATH (longs):
//     tp1Price = entry × (1 + tp1Pct/100)   closes  entryQty × tp1Size/100  contracts
//     tp2Price = entry × (1 + tp2Pct/100)   closes  entryQty × tp2Size/100  contracts
//     tp3Price = entry × (1 + tp3Pct/100)   closes  the remaining contracts
//   Position size closed across all tiers should sum to ≤ 100%; TP3 sweeps whatever
//   remains so the runner is never left orphaned. Sizes are taken against the ORIGINAL
//   filled quantity (captured at entry) — NOT the shrinking remainder — so the % you
//   type is the % of the trade you actually realise at each level.
usePartialTp     = input.bool(true, "Enable multi-stage partial TP (longs)", group = G_PTP,
     tooltip = "When ON, the long exit uses the 3-tier partial-TP engine below instead of a single TP target. When OFF, the legacy single-TP R/ATR/% bracket is used.")
tp1Pct           = input.float(2.0,  "TP1 — price move %", step = 0.1, minval = 0.1, group = G_PTP, inline = "ptp1")
tp1Size          = input.float(25.0, "close % of pos", step = 5, minval = 1, maxval = 100, group = G_PTP, inline = "ptp1")
tp2Pct           = input.float(5.0,  "TP2 — price move %", step = 0.1, minval = 0.1, group = G_PTP, inline = "ptp2")
tp2Size          = input.float(35.0, "close % of pos", step = 5, minval = 1, maxval = 100, group = G_PTP, inline = "ptp2")
tp3Pct           = input.float(10.0, "TP3 — price move %", step = 0.1, minval = 0.1, group = G_PTP, inline = "ptp3")
tp3Size          = input.float(40.0, "close % of pos", step = 5, minval = 1, maxval = 100, group = G_PTP, inline = "ptp3")
ptpTrailAfterTp1 = input.bool(true, "Arm trailing stop on remainder after TP1", group = G_PTP,
     tooltip = "Once TP1 fills, the remaining position is protected by a trailing stop using the source below. This is in addition to the hard SL / break-even floor.")
ptpTrailPct      = input.float(3.0, "Trail distance % (after TP1)", step = 0.1, minval = 0.1, group = G_PTP,
     tooltip = "Post-TP1 trailing stop = close × (1 − trail%/100), ratcheting up.")

// ── Visualization ─────────────────────────────────────────────────────────────
showSrPlots  = input.bool(false, "Plot MTF support/resistance lines", group = G_VIS)
showMarkers  = input.bool(true, "Plot Buy/Sell markers", group = G_VIS)
showHtfBreak = input.bool(true, "Marker when HTF break runner arms", group = G_VIS)
showFlipSup  = input.bool(true, "Plot levels acting as flipped support", group = G_VIS)
showStBand   = input.bool(false, "Plot SuperTrend band", group = G_VIS)
plotAtr      = input.bool(false, "Plot ATR (risk ATR) value", group = G_VIS)
showExitPnL  = input.bool(true, "Label P/L % when a trade closes", group = G_VIS)
showCumPnL   = input.bool(true, "Show cumulative net P/L % (table, last bar)", group = G_VIS)

// ══════════════════════════════════════════════════════════════════════════════
// NEW INDICATORS (4) — each: Off / Primary (trigger) / Filter (confirm), plot toggle.
// All compute on the chart timeframe and default OFF. Ported inline (no imports).
// ══════════════════════════════════════════════════════════════════════════════

// ── Indicator 18 — Range Filter (DonovanWall / guikroth) ──────────────────────
rf_en       = input.bool(false, "Enable Range Filter", group = G_RF)
rf_primary  = input.bool(false, "Range Filter is PRIMARY (entry trigger)", group = G_RF,
     tooltip = "ON = Range Filter Buy/Sell breakout TRIGGERS entries. OFF = used only as a confirming FILTER (trend direction must agree).")
rf_src      = input.source(close, "Source", group = G_RF)
rf_per      = input.int(100, "Sampling Period", minval = 1, group = G_RF)
rf_mult     = input.float(3.0, "Range Multiplier", minval = 0.1, step = 0.1, group = G_RF)
plotRangeFilter = input.bool(false, "Plot Range Filter (line + bands)", group = G_RF)

// ── Indicator 19 — Q-Trend (tarasenko_) ───────────────────────────────────────
qt_en       = input.bool(false, "Enable Q-Trend", group = G_QT)
qt_primary  = input.bool(false, "Q-Trend is PRIMARY (entry trigger)", group = G_QT,
     tooltip = "ON = Q-Trend Buy/Sell TRIGGERS entries. OFF = used only as a confirming FILTER (last signal must agree).")
qt_src      = input.source(close, "Source", group = G_QT)
qt_p        = input.int(200, "Trend period", minval = 1, group = G_QT)
qt_atrP     = input.int(14, "ATR Period", minval = 1, group = G_QT)
qt_mult     = input.float(1.0, "ATR Multiplier", minval = 0.1, step = 0.1, group = G_QT)
plotQTrend  = input.bool(false, "Plot Q-Trend trend line", group = G_QT)

// ── Indicator 20 — AlphaTrend (KivancOzbilgic) ────────────────────────────────
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 = AlphaTrend Buy/Sell crossover TRIGGERS entries. OFF = used only as a confirming FILTER (AlphaTrend must be rising).")
at_coeff    = input.float(1.0, "Multiplier", step = 0.1, group = G_AT)
at_ap       = input.int(14, "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)
plotAlphaTrend = input.bool(false, "Plot AlphaTrend lines", group = G_AT)

// ── Indicator 21 — HACOLT (Vervoort Heiken-Ashi, LazyBear) ────────────────────
hac_en      = input.bool(false, "Enable HACOLT", group = G_HAC)
hac_primary = input.bool(false, "HACOLT is PRIMARY (entry trigger)", group = G_HAC,
     tooltip = "ON = HACOLT flip to long/short TRIGGERS entries. OFF = used only as a confirming FILTER (HACOLT state must agree).")
hac_length  = input.int(55, "TEMA Period", minval = 1, group = G_HAC)
hac_emaLen  = input.int(60, "EMA Period", minval = 1, group = G_HAC)
hac_csf     = input.float(1.1, "Candle size factor", step = 0.1, group = G_HAC)
plotHacolt  = input.bool(false, "Plot HACOLT state (background tint)", group = G_HAC)

// ── Helper functions ──────────────────────────────────────────────────────────
f_ma(string t, series float src, int length) =>
    float maS = ta.sma(src, length)
    float maE = ta.ema(src, length)
    float maW = ta.wma(src, length)
    float maR = ta.rma(src, length)
    float maV = ta.vwma(src, length)
    // HMA = WMA( 2·WMA(src, len/2) − WMA(src, len), floor(sqrt(len)) ). Ported from the
    // provided Hull Moving Average source; embedded inline (no external dependency).
    float maH = ta.wma(2 * ta.wma(src, int(math.max(1, length / 2))) - ta.wma(src, length), int(math.max(1, math.floor(math.sqrt(length)))))
    t == "SMA" ? maS : t == "EMA" ? maE : t == "WMA" ? maW : t == "VWMA" ? maV : t == "HMA" ? maH : maR

// Picks an OHLC source by name — evaluated inside request.security so it resolves
// to the target timeframe's open/high/low/close.
f_pick_src(string s) =>
    s == "Open" ? open : s == "High" ? high : s == "Low" ? low : close

// MTF MA — explicit VWMA expansion so `volume` is visible to Pine v6's request.security analyser.
// `typeSel` chooses the MA type and `srcSel` the OHLC source — both per MA.
f_ma_mtf(string tf, float lenF, bool en, string srcSel, string typeSel) =>
    int   _L   = int(lenF)
    float mSec = request.security(syminfo.tickerid, tf,
         typeSel == "EMA"  ? ta.ema(f_pick_src(srcSel), _L) :
         typeSel == "WMA"  ? ta.wma(f_pick_src(srcSel), _L) :
         typeSel == "RMA"  ? ta.rma(f_pick_src(srcSel), _L) :
         typeSel == "VWMA" ? ta.sma(f_pick_src(srcSel) * volume, _L) / ta.sma(volume, _L) :
         typeSel == "HMA"  ? ta.wma(2 * ta.wma(f_pick_src(srcSel), int(math.max(1, _L / 2))) - ta.wma(f_pick_src(srcSel), _L), int(math.max(1, math.floor(math.sqrt(_L))))) :
                             ta.sma(f_pick_src(srcSel), _L),
         barmerge.gaps_off, barmerge.lookahead_off)
    en and lenF > 0 ? mSec : na

// MTF VWMA — explicit formula keeps `volume` visible to Pine v6 dependency analyser.
f_vwma_mtf(string tf, int lenV, bool en) =>
    [cv_ma, v_ma] = request.security(syminfo.tickerid, tf,
         [ta.sma(close * volume, lenV), ta.sma(volume, lenV)],
         barmerge.gaps_off, barmerge.lookahead_off)
    float result = (not na(v_ma) and v_ma > 0) ? cv_ma / v_ma : na
    en and lenV > 0 ? result : na

f_exit_ma_long_cross() =>
    float m  = f_ma(exitMaType, f_pick_src(exitMaSrc), exitMaLen)
    bool  cu = ta.crossunder(close, m)
    barstate.isconfirmed and cu

f_exit_ma_short_cross() =>
    float m  = f_ma(exitMaType, f_pick_src(exitMaSrc), exitMaLen)
    bool  co = ta.crossover(close, m)
    barstate.isconfirmed and co

f_last_pivot_low(string tf, int len_) =>
    request.security(syminfo.tickerid, tf,
         ta.valuewhen(not na(ta.pivotlow(len_, len_)), ta.pivotlow(len_, len_), 0),
         barmerge.gaps_off, barmerge.lookahead_off)

f_last_pivot_high(string tf, int len_) =>
    request.security(syminfo.tickerid, tf,
         ta.valuewhen(not na(ta.pivothigh(len_, len_)), ta.pivothigh(len_, len_), 0),
         barmerge.gaps_off, barmerge.lookahead_off)

f_hhph_last(int len_) =>
    ta.valuewhen(not na(ta.pivothigh(len_, len_)), ta.pivothigh(len_, len_), 0)

f_hhph_prior(int len_) =>
    ta.valuewhen(not na(ta.pivothigh(len_, len_)), ta.pivothigh(len_, len_), 1)

// SuperTrend bands evaluated on the TF where this runs (for request.security).
f_super_trend_htf() =>
    atr_ = ta.atr(stAtrLen)
    src  = hl2
    up   = src - stMult * atr_
    up1  = nz(up[1], up)
    up  := close[1] > up1 ? math.max(up, up1) : up
    dn   = src + stMult * atr_
    dn1  = nz(dn[1], dn)
    dn  := close[1] < dn1 ? math.min(dn, dn1) : dn
    tr   = 1
    tr  := nz(tr[1], 1)
    tr  := tr == -1 and close > dn1 ? 1 : tr == 1 and close < up1 ? -1 : tr
    [up, dn, tr]

f_linreg_ohlc(int lenLR) =>
    [ta.linreg(open, lenLR, 0), ta.linreg(high, lenLR, 0), ta.linreg(low, lenLR, 0), ta.linreg(close, lenLR, 0)]

f_strength_pl(int len_) =>
    ta.valuewhen(not na(ta.pivotlow(len_, len_)), ta.pivotlow(len_, len_), 0)

f_strength_ph(int len_) =>
    ta.valuewhen(not na(ta.pivothigh(len_, len_)), ta.pivothigh(len_, len_), 0)

// Confirmed close above latest pivot high on HTF (for HTF break detection).
f_htf_close_above_resistance(int pivLen, float bufAtr) =>
    float lv   = ta.valuewhen(not na(ta.pivothigh(pivLen, pivLen)), ta.pivothigh(pivLen, pivLen), 0)
    float atrh = ta.atr(atrLenExit)
    not na(lv) and barstate.isconfirmed and close > lv + bufAtr * atrh

// ── Core series ───────────────────────────────────────────────────────────────
atr      = ta.atr(atrLenExit)
tickSize = syminfo.mintick

float htfExitResLvl     = useHtfBreakTrail ? f_last_pivot_high(htfResTf, htfResPivotLen) : na
bool  htfResBreakHtfBar = request.security(syminfo.tickerid, htfResTf, f_htf_close_above_resistance(htfResPivotLen, htfBreakBufAtr), barmerge.gaps_off, barmerge.lookahead_off)
bool  rawBreakHtfBar    = useHtfBreakTrail and htfBreakConfirmMode == "HTF candle close" and htfResBreakHtfBar
bool  rawBreakChartBar  = useHtfBreakTrail and htfBreakConfirmMode == "Chart close (aligned level)" and not na(htfExitResLvl) and barstate.isconfirmed and close > htfExitResLvl + htfBreakBufAtr * atr
bool  rawHtfResBreakNow = rawBreakHtfBar or rawBreakChartBar

float band = touchBandMode == "ATR" ? atr * touchAtrMult : touchTicks * tickSize

float sup5   = tf5_en   ? f_last_pivot_low("5",   pivLen5)   : na
float sup15  = tf15_en  ? f_last_pivot_low("15",  pivLen15)  : na
float sup60  = tf60_en  ? f_last_pivot_low("60",  pivLen60)  : na
float sup240 = tf240_en ? f_last_pivot_low("240", pivLen240) : na

float res5   = tf5_en   ? f_last_pivot_high("5",   pivLen5)   : na
float res15  = tf15_en  ? f_last_pivot_high("15",  pivLen15)  : na
float res60  = tf60_en  ? f_last_pivot_high("60",  pivLen60)  : na
float res240 = tf240_en ? f_last_pivot_high("240", pivLen240) : na

touchedSupport(lvl) =>
    not na(lvl) and low <= lvl + band

touchedResistance(lvl) =>
    not na(lvl) and high >= lvl - band

breakReclaimBar(lvl) =>
    not na(lvl) and low < lvl - reclaimPierceAtr * atr and close > lvl

retestSupportBar(lvl) =>
    if na(lvl)
        false
    else
        bool inZone    = low <= lvl + band
        // Pullback toggle: when usePriorAbove is OFF, the "prior bars closed above support" requirement is skipped.
        bool _priorAbove = ta.lowest(close[1], priorAboveLb) > lvl
        bool fromAbove = not usePriorAbove or _priorAbove
        bool reclaimOk = not blockBreakReclaim or not breakReclaimBar(lvl)
        inZone and fromAbove and reclaimOk

bounceLong(lvl) =>
    not requireBounce or (not na(lvl) and close > lvl)

bounceShort(lvl) =>
    not requireBounce or (not na(lvl) and close < lvl)

// ── Resistance → support flip ─────────────────────────────────────────────────
var bool flipR5   = false
var bool flipR15  = false
var bool flipR60  = false
var bool flipR240 = false

if useResFlipSup
    float brkFlip  = flipBreakBufAtr * atr
    float invFlip  = flipInvalidBufAtr * atr
    bool  okFlipBar = not flipConfirmOnClose or barstate.isconfirmed

    if barstate.isconfirmed
        if tf5_en   and not na(res5)   and not na(res5[1])   and res5   != res5[1]
            flipR5   := false
        if tf15_en  and not na(res15)  and not na(res15[1])  and res15  != res15[1]
            flipR15  := false
        if tf60_en  and not na(res60)  and not na(res60[1])  and res60  != res60[1]
            flipR60  := false
        if tf240_en and not na(res240) and not na(res240[1]) and res240 != res240[1]
            flipR240 := false

    if okFlipBar and tf5_en and not na(res5)
        if close > res5 + brkFlip
            flipR5 := true
        if flipR5 and close < res5 - invFlip
            flipR5 := false
    if okFlipBar and tf15_en and not na(res15)
        if close > res15 + brkFlip
            flipR15 := true
        if flipR15 and close < res15 - invFlip
            flipR15 := false
    if okFlipBar and tf60_en and not na(res60)
        if close > res60 + brkFlip
            flipR60 := true
        if flipR60 and close < res60 - invFlip
            flipR60 := false
    if okFlipBar and tf240_en and not na(res240)
        if close > res240 + brkFlip
            flipR240 := true
        if flipR240 and close < res240 - invFlip
            flipR240 := false

touchFlipRetest(bool tfEn, bool flipped, float resLvl) =>
    // Evaluate retestSupportBar() unconditionally so its historical series always
    // advances (the && short-circuit would otherwise skip it → CW10002 warning).
    bool _rstFlip = retestSupportBar(resLvl)
    useResFlipSup and tfEn and flipped and _rstFlip

// Pre-compute unconditionally so historical series in retestSupportBar() always execute.
bool _rst5   = retestSupportBar(sup5)
bool _rst15  = retestSupportBar(sup15)
bool _rst60  = retestSupportBar(sup60)
bool _rst240 = retestSupportBar(sup240)
bool retest5   = tf5_en   and _rst5
bool retest15  = tf15_en  and _rst15
bool retest60  = tf60_en  and _rst60
bool retest240 = tf240_en and _rst240
bool retestFlip5  = touchFlipRetest(tf5_en,   flipR5,   res5)
bool retestFlip15 = touchFlipRetest(tf15_en,  flipR15,  res15)
bool retestFlip60 = touchFlipRetest(tf60_en,  flipR60,  res60)
bool retestFlip240 = touchFlipRetest(tf240_en, flipR240, res240)
bool retestLongNow = retest5 or retest15 or retest60 or retest240 or retestFlip5 or retestFlip15 or retestFlip60 or retestFlip240

// Use pre-computed retestFlip* vars instead of calling touchFlipRetest() again.
touchLong  = (tf5_en and touchedSupport(sup5)) or touchedSupport(sup15) or touchedSupport(sup60) or touchedSupport(sup240) or retestFlip5 or retestFlip15 or retestFlip60 or retestFlip240
touchShort = (tf5_en and touchedResistance(res5) and not (useResFlipSup and flipR5)) or (tf15_en and touchedResistance(res15) and not (useResFlipSup and flipR15)) or (tf60_en and touchedResistance(res60) and not (useResFlipSup and flipR60)) or (tf240_en and touchedResistance(res240) and not (useResFlipSup and flipR240))

float nearestTouchSup = na
if touchedSupport(sup240) or retestFlip240
    nearestTouchSup := touchedSupport(sup240) ? sup240 : res240
else if touchedSupport(sup60) or retestFlip60
    nearestTouchSup := touchedSupport(sup60) ? sup60 : res60
else if touchedSupport(sup15) or retestFlip15
    nearestTouchSup := touchedSupport(sup15) ? sup15 : res15
else if tf5_en and (touchedSupport(sup5) or retestFlip5)
    nearestTouchSup := touchedSupport(sup5) ? sup5 : res5

var int   lastRetestBar = na
var float lastRetestLvl = na
if retestLongNow
    lastRetestBar := bar_index
    lastRetestLvl := retest240 ? sup240 : retest60 ? sup60 : retest15 ? sup15 : retest5 ? sup5 : retestFlip240 ? res240 : retestFlip60 ? res60 : retestFlip15 ? res15 : retestFlip5 ? res5 : na

int  barsSinceRetest  = na(lastRetestBar) ? 999999 : bar_index - lastRetestBar
// Retest-window toggle: when useRetestWindow is OFF, the N-bars-after-touch constraint is removed.
bool recentRetestWindow = not useRetestWindow or (not na(lastRetestBar) and barsSinceRetest >= 0 and barsSinceRetest <= retestConfirmBars)
bool retestLvlOk      = not requireBounce or na(lastRetestLvl) or close > lastRetestLvl
bool retestEntryOk    = recentRetestWindow and retestLvlOk and not (blockBreakReclaim and not na(lastRetestLvl) and breakReclaimBar(lastRetestLvl))

float nearestTouchRes = na
if tf240_en and touchedResistance(res240) and not (useResFlipSup and flipR240)
    nearestTouchRes := res240
else if tf60_en and touchedResistance(res60) and not (useResFlipSup and flipR60)
    nearestTouchRes := res60
else if tf15_en and touchedResistance(res15) and not (useResFlipSup and flipR15)
    nearestTouchRes := res15
else if tf5_en and touchedResistance(res5) and not (useResFlipSup and flipR5)
    nearestTouchRes := res5

// ── Pivot-strength filter ─────────────────────────────────────────────────────
float lastPL_str = request.security(syminfo.tickerid, strengthTf, f_strength_pl(strengthPivotLen), barmerge.gaps_off, barmerge.lookahead_off)
float lastPH_str = request.security(syminfo.tickerid, strengthTf, f_strength_ph(strengthPivotLen), barmerge.gaps_off, barmerge.lookahead_off)

tapsNearLong(lvl) =>
    not na(lvl) and not na(lastPL_str) and math.abs(lastPL_str - lvl) <= band * 3 ? 1 : 0

tapsNearShort(lvl) =>
    not na(lvl) and not na(lastPH_str) and math.abs(lastPH_str - lvl) <= band * 3 ? 1 : 0

int strengthSumLong  = (tf5_en ? tapsNearLong(sup5) : 0) + (tf15_en ? tapsNearLong(sup15) : 0) + (tf60_en ? tapsNearLong(sup60) : 0) + (tf240_en ? tapsNearLong(sup240) : 0)
int strengthFlipLong = (tf5_en and flipR5 ? tapsNearLong(res5) : 0) + (tf15_en and flipR15 ? tapsNearLong(res15) : 0) + (tf60_en and flipR60 ? tapsNearLong(res60) : 0) + (tf240_en and flipR240 ? tapsNearLong(res240) : 0)
int strengthSumShort = (tf5_en and not flipR5 ? tapsNearShort(res5) : 0) + (tf15_en and not flipR15 ? tapsNearShort(res15) : 0) + (tf60_en and not flipR60 ? tapsNearShort(res60) : 0) + (tf240_en and not flipR240 ? tapsNearShort(res240) : 0)
bool strengthOkLong  = srStrengthMin <= 1 or strengthSumLong + strengthFlipLong >= srStrengthMin
bool strengthOkShort = srStrengthMin <= 1 or strengthSumShort >= srStrengthMin

float priorHighCont = ta.highest(high[1], contBreakLb)
bool  deepSupTouch  = touchedSupport(sup15) or touchedSupport(sup60) or touchedSupport(sup240)

// ── MA trend ──────────────────────────────────────────────────────────────────
float ma1      = f_ma_mtf(ma1_tf, ma1_len, ma1_en, ma1_src, ma1_type)
float ma2      = f_ma_mtf(ma2_tf, ma2_len, ma2_en, ma2_src, ma2_type)
float ma3      = f_ma_mtf(ma3_tf, ma3_len, ma3_en, ma3_src, ma3_type)
float ma4      = f_ma_mtf(ma4_tf, ma4_len, ma4_en, ma4_src, ma4_type)
float vwTrend  = f_vwma_mtf(vwmaTf,  vwmaLen,  useVwmaTrend)

maPassLong(bool en, float m) =>
    not en ? true :
     maBlockWhenNa and na(m) ? false :
     not maBlockWhenNa and na(m) ? true :
     close > m

maPassShort(bool en, float m) =>
    not en ? true :
     maBlockWhenNa and na(m) ? false :
     not maBlockWhenNa and na(m) ? true :
     close < m

bool maUp = maPassLong(ma1_en, ma1) and maPassLong(ma2_en, ma2) and maPassLong(ma3_en, ma3) and maPassLong(ma4_en, ma4)
bool maDn = maPassShort(ma1_en, ma1) and maPassShort(ma2_en, ma2) and maPassShort(ma3_en, ma3) and maPassShort(ma4_en, ma4)

bool vwOkLong   = maPassLong(useVwmaTrend,  vwTrend)
bool vwOkShort  = maPassShort(useVwmaTrend, vwTrend)

float exitMaAligned     = (useExitLongMa or useExitShortMa or plotExitMa) ? request.security(syminfo.tickerid, exitMaTf, f_ma(exitMaType, f_pick_src(exitMaSrc), exitMaLen), barmerge.gaps_off, barmerge.lookahead_off) : na
// Always compute cross signals unconditionally so their internal historical series never skip a bar.
bool _exitMaLongRaw   = request.security(syminfo.tickerid, exitMaTf, f_exit_ma_long_cross(),  barmerge.gaps_off, barmerge.lookahead_off)
bool _exitMaShortRaw  = request.security(syminfo.tickerid, exitMaTf, f_exit_ma_short_cross(), barmerge.gaps_off, barmerge.lookahead_off)
bool exitLongMaTrigger  = useExitLongMa  and _exitMaLongRaw
bool exitShortMaTrigger = useExitShortMa and _exitMaShortRaw

// ── SuperTrend ────────────────────────────────────────────────────────────────
bool stIsChartTf = timeframe.in_seconds(timeframe.period) == timeframe.in_seconds(stTf)

float stAtr_c  = ta.atr(stAtrLen)
float stUp_c   = stSrc - stMult * stAtr_c
float stUp1_c  = nz(stUp_c[1], stUp_c)
stUp_c        := close[1] > stUp1_c ? math.max(stUp_c, stUp1_c) : stUp_c
float stDn_c   = stSrc + stMult * stAtr_c
float stDn1_c  = nz(stDn_c[1], stDn_c)
stDn_c        := close[1] < stDn1_c ? math.min(stDn_c, stDn1_c) : stDn_c
var int stTrend_c = 1
stTrend_c := nz(stTrend_c[1], 1)
stTrend_c := stTrend_c == -1 and close > stDn1_c ? 1 : stTrend_c == 1 and close < stUp1_c ? -1 : stTrend_c

[stUp_h, stDn_h, stTrend_h] = request.security(syminfo.tickerid, stTf, f_super_trend_htf(), barmerge.gaps_off, barmerge.lookahead_off)

float stUp      = stIsChartTf ? stUp_c   : stUp_h
float stDn      = stIsChartTf ? stDn_c   : stDn_h
int   stTrendVal = stIsChartTf ? stTrend_c : int(stTrend_h)
bool  stBull    = stTrendVal == 1
bool  stBear    = stTrendVal == -1

// ── LinReg ────────────────────────────────────────────────────────────────────
[lrO, lrH, lrL, lrC] = request.security(syminfo.tickerid, lrTf, f_linreg_ohlc(lrLen), barmerge.gaps_off, barmerge.lookahead_off)
float bopen  = useLinReg ? lrO : open
float bhigh  = useLinReg ? lrH : high
float blow   = useLinReg ? lrL : low
float bclose = useLinReg ? lrC : close

float sigSma = ta.sma(bclose, sigLen)
float sigEma = ta.ema(bclose, sigLen)
float sig    = sigUseSma ? sigSma : sigEma

bool lrGreen = bopen < bclose
bool lrRed   = bopen > bclose

bool lrLongOk =
     not useLinReg ? true :
     lrLongRule == "Green candle"   ? lrGreen :
     lrLongRule == "Close > signal" ? bclose > sig :
     lrGreen and bclose > sig

bool lrShortOk =
     not useLinReg ? true :
     lrShortRule == "Red candle"    ? lrRed :
     lrShortRule == "Close < signal" ? bclose < sig :
     lrRed and bclose < sig

// ── MA slope ──────────────────────────────────────────────────────────────────
bool ma1SlopeOk    = not requireMaSlope or not ma1_en or na(ma1) or na(ma1[maSlopeLb]) or ma1 > ma1[maSlopeLb]
bool ma2SlopeOk    = not requireMaSlope or not ma2_en or na(ma2) or na(ma2[maSlopeLb]) or ma2 > ma2[maSlopeLb]
bool ma3SlopeOk    = not requireMaSlope or not ma3_en or na(ma3) or na(ma3[maSlopeLb]) or ma3 > ma3[maSlopeLb]
bool ma1SlopeDnOk  = not requireMaSlope or not ma1_en or na(ma1) or na(ma1[maSlopeLb]) or ma1 < ma1[maSlopeLb]
bool ma2SlopeDnOk  = not requireMaSlope or not ma2_en or na(ma2) or na(ma2[maSlopeLb]) or ma2 < ma2[maSlopeLb]
bool ma3SlopeDnOk  = not requireMaSlope or not ma3_en or na(ma3) or na(ma3[maSlopeLb]) or ma3 < ma3[maSlopeLb]
bool ma4SlopeOk    = not requireMaSlope or not ma4_en or na(ma4) or na(ma4[maSlopeLb]) or ma4 > ma4[maSlopeLb]
bool ma4SlopeDnOk  = not requireMaSlope or not ma4_en or na(ma4) or na(ma4[maSlopeLb]) or ma4 < ma4[maSlopeLb]
bool maSlopeOkLong  = ma1SlopeOk  and ma2SlopeOk  and ma3SlopeOk  and ma4SlopeOk
bool maSlopeOkShort = ma1SlopeDnOk and ma2SlopeDnOk and ma3SlopeDnOk and ma4SlopeDnOk

// ── Local EMA stack ───────────────────────────────────────────────────────────
float localFastEma = useLocalTrend ? request.security(syminfo.tickerid, localTrendTf, ta.ema(close, localEmaFast), barmerge.gaps_off, barmerge.lookahead_off) : na
float localSlowEma = useLocalTrend ? request.security(syminfo.tickerid, localTrendTf, ta.ema(close, localEmaSlow), barmerge.gaps_off, barmerge.lookahead_off) : na
bool localTrendOkLong  = not useLocalTrend or (not na(localFastEma) and not na(localSlowEma) and localFastEma > localSlowEma and (not requireAboveFast or close > localFastEma))
bool localTrendOkShort = not useLocalTrend or (not na(localFastEma) and not na(localSlowEma) and localFastEma < localSlowEma and (not requireAboveFast or close < localFastEma))

// ── Volume filter (timeframe-selectable) ──────────────────────────────────────
// Chart-TF volume SMA — used by the Volume Exhaustion exit and the plot.
float volMaSeries = ta.sma(volume, volMaLen)
// Entry-filter volume + SMA evaluated on the chosen volTf (blank = chart TF).
[volEntryCur, volEntrySma] = request.security(syminfo.tickerid, volTf == "" ? timeframe.period : volTf,
     [volume, ta.sma(volume, volMaLen)], barmerge.gaps_off, barmerge.lookahead_off)
bool  volOkLong   = not useVolumeFilter or (not na(volEntrySma) and volEntrySma > 0 and volEntryCur >= volMultMin * volEntrySma)
bool  volOkShort  = volOkLong

// ── Higher-high structure ─────────────────────────────────────────────────────
float hhLastPH  = (useHhStructure or plotHhStruct) ? request.security(syminfo.tickerid, hhTf, f_hhph_last(hhPivotLen),  barmerge.gaps_off, barmerge.lookahead_off) : na
float hhPriorPH = (useHhStructure or plotHhStruct) ? request.security(syminfo.tickerid, hhTf, f_hhph_prior(hhPivotLen), barmerge.gaps_off, barmerge.lookahead_off) : na
bool  hhOkLong  = not useHhStructure or (not na(hhLastPH) and not na(hhPriorPH) and hhLastPH > hhPriorPH)

// ══════════════════════════════════════════════════════════════════════════════
// EMBEDDED INDICATORS — RSI (MTF) + 4 NEW INDICATORS (Range Filter, Q-Trend,
// AlphaTrend, HACOLT). All ported inline from the provided sources; no imports.
// ══════════════════════════════════════════════════════════════════════════════

// ── RSI (Wilder) — timeframe-selectable ───────────────────────────────────────
float rsiVal     = request.security(syminfo.tickerid, rsiTf == "" ? timeframe.period : rsiTf,
     ta.rsi(rsiSourceInput, rsiLengthInput), barmerge.gaps_off, barmerge.lookahead_off)
bool  rsiOkLong  = not useRsiFilter or (not na(rsiVal) and rsiVal >= rsiLongMin and rsiVal <= rsiLongMax)
// Compute the crossunder unconditionally (every bar) so its internal historical state
// is always maintained, then gate it — avoids CW10002 inconsistent-calculation warning.
bool  _rsiXunder = ta.crossunder(rsiVal, rsiExitLevel)
bool  rsiExitNow = useRsiExit and barstate.isconfirmed and _rsiXunder

// ── NEW Indicator 18 — Range Filter ───────────────────────────────────────────
f_smoothrng(float x, int t, float m) =>
    float avrng = ta.ema(math.abs(x - x[1]), t)
    ta.ema(avrng, t * 2 - 1) * m
f_rngfilt(float x, float r) =>
    var float rf = na
    rf := na(rf[1]) ? x : x > rf[1] ? (x - r < rf[1] ? rf[1] : x - r) : (x + r > rf[1] ? rf[1] : x + r)
    rf
float rfSm   = f_smoothrng(rf_src, rf_per, rf_mult)
float rfFilt = f_rngfilt(rf_src, rfSm)
var float rfUpward = 0.0
rfUpward := rfFilt > rfFilt[1] ? nz(rfUpward[1]) + 1 : rfFilt < rfFilt[1] ? 0.0 : nz(rfUpward[1])
var float rfDownward = 0.0
rfDownward := rfFilt < rfFilt[1] ? nz(rfDownward[1]) + 1 : rfFilt > rfFilt[1] ? 0.0 : nz(rfDownward[1])
float rfHband = rfFilt + rfSm
float rfLband = rfFilt - rfSm
bool rfLongRaw  = (rf_src > rfFilt and rf_src > rf_src[1] and rfUpward > 0) or (rf_src > rfFilt and rf_src < rf_src[1] and rfUpward > 0)
bool rfShortRaw = (rf_src < rfFilt and rf_src < rf_src[1] and rfDownward > 0) or (rf_src < rfFilt and rf_src > rf_src[1] and rfDownward > 0)
var int rfCondIni = 0
rfCondIni := rfLongRaw ? 1 : rfShortRaw ? -1 : nz(rfCondIni[1])
bool rfBuySignal  = rfLongRaw  and rfCondIni[1] == -1
bool rfSellSignal = rfShortRaw and rfCondIni[1] ==  1
bool rfEvtLong   = rfBuySignal               // primary trigger (long)
bool rfStateLong = rfUpward > 0              // filter state (bullish)
bool rfEvtShort  = rfSellSignal
bool rfStateShort = rfDownward > 0

// ── NEW Indicator 19 — Q-Trend ────────────────────────────────────────────────
float qtH   = ta.highest(qt_src, qt_p)
float qtL   = ta.lowest(qt_src, qt_p)
float qtAtr = ta.atr(qt_atrP)[1]
float qtEps = qt_mult * qtAtr
float qtMbase = (qtH + qtL) / 2
var float qtM = na
qtM := bar_index > qt_p ? nz(qtM[1], qtMbase) : qtMbase
bool qtChangeUp = ta.crossover(qt_src, qtM + qtEps) or qt_src > qtM + qtEps
bool qtChangeDn = ta.crossunder(qt_src, qtM - qtEps) or qt_src < qtM - qtEps
qtM := (qtChangeUp or qtChangeDn) and qtM != qtM[1] ? qtM : qtChangeUp ? qtM + qtEps : qtChangeDn ? qtM - qtEps : nz(qtM[1], qtM)
var string qtLs = na
qtLs := qtChangeUp ? "B" : qtChangeDn ? "S" : qtLs[1]
bool qtEvtLong    = qtChangeUp and qtLs[1] != "B"
bool qtStateLong  = qtLs == "B"
bool qtEvtShort   = qtChangeDn and qtLs[1] != "S"
bool qtStateShort = qtLs == "S"

// ── NEW Indicator 20 — AlphaTrend ─────────────────────────────────────────────
float atAtr = ta.sma(ta.tr, at_ap)
float atUpT = low  - atAtr * at_coeff
float atDnT = high + atAtr * at_coeff
float atMom = at_novol ? ta.rsi(at_src, at_ap) : ta.mfi(hlc3, at_ap)
var float AlphaTrend = 0.0
AlphaTrend := (atMom >= 50) ? (atUpT < nz(AlphaTrend[1]) ? nz(AlphaTrend[1]) : atUpT) : (atDnT > nz(AlphaTrend[1]) ? nz(AlphaTrend[1]) : atDnT)
// Raw crossovers of the AlphaTrend line vs its 2-bar-lagged value.
bool atBuyRaw     = ta.crossover(AlphaTrend, AlphaTrend[2])
bool atSellRaw    = ta.crossunder(AlphaTrend, AlphaTrend[2])
// Alternation guard (matches the AlphaTrend indicator's displayed BUY/SELL labels):
// a BUY counts only when a SELL has occurred more recently than the previous BUY, and
// vice-versa. This collapses repeated same-direction crossovers into one real signal.
int  atK1 = ta.barssince(atBuyRaw)
int  atK2 = ta.barssince(atSellRaw)
int  atO1 = ta.barssince(atBuyRaw[1])
int  atO2 = ta.barssince(atSellRaw[1])
bool atEvtLong    = atBuyRaw  and atO1 > atK2   // confirmed AlphaTrend BUY signal
bool atEvtShort   = atSellRaw and atO2 > atK1   // confirmed AlphaTrend SELL signal
bool atStateLong  = AlphaTrend > AlphaTrend[2]
bool atStateShort = AlphaTrend < AlphaTrend[2]

// ── NEW Indicator 21 — HACOLT (Vervoort Heiken-Ashi Candlestick Oscillator) ───
f_tema(float src, int len) =>
    float e1 = ta.ema(src, len)
    float e2 = ta.ema(e1, len)
    float e3 = ta.ema(e2, len)
    3 * (e1 - e2) + e3
var float hacHaOpen = na
hacHaOpen := na(hacHaOpen[1]) ? ohlc4 : (hacHaOpen[1] + ohlc4) / 2
float hacHaClose = (hacHaOpen + math.max(high, hacHaOpen) + math.min(low, hacHaOpen) + ohlc4) / 4
float hacThaClose = f_tema(hacHaClose, hac_length)
float hacThl2     = f_tema(hl2, hac_length)
float hacCloseSm  = 2 * hacThaClose - f_tema(hacThaClose, hac_length)
float hacHl2Sm    = 2 * hacThl2 - f_tema(hacThl2, hac_length)
bool hacShortCandle = math.abs(close - open) < ((high - low) * hac_csf)
bool hacKeepn1 = ((hacHaClose >= hacHaOpen) and (hacHaClose[1] >= hacHaOpen[1])) or (close >= hacHaClose) or (high > high[1]) or (low > low[1]) or (hacHl2Sm >= hacCloseSm)
bool hacKeepAll1 = hacKeepn1 or (hacKeepn1[1] and (close >= open) or (close >= close[1]))
bool hacKeep13 = hacShortCandle and (high >= low[1])
bool hacUtr = hacKeepAll1 or (hacKeepAll1[1] and hacKeep13)
bool hacKeepn2 = (hacHaClose < hacHaOpen) and (hacHaClose[1] < hacHaOpen[1]) or (hacHl2Sm < hacCloseSm)
bool hacKeep23 = hacShortCandle and (low <= high[1])
bool hacKeepAll2 = hacKeepn2 or (hacKeepn2[1] and (close < open) or (close < close[1]))
bool hacDtr = hacKeepAll2 or (hacKeepAll2[1] and hacKeep23)
bool hacUpw = (not hacDtr) and hacDtr[1] and hacUtr
bool hacDnw = (not hacUtr) and hacUtr[1] and hacDtr
// Recursive state carried as float (1/0) — Pine v6 bool series can't be nz()/na()-guarded.
var float hacUpwOffsetF = 0.0
hacUpwOffsetF := hacUpw != hacDnw ? (hacUpw ? 1.0 : 0.0) : nz(hacUpwOffsetF[1], 0.0)
bool hacUpwOffset = hacUpwOffsetF > 0
bool hacBuySig = hacUpw or ((not hacDnw) and hacUpwOffset)
bool hacLtSell = close < ta.ema(close, hac_emaLen)
var float hacNeutralF = 0.0
hacNeutralF := hacBuySig ? 1.0 : (hacLtSell ? 0.0 : nz(hacNeutralF[1], 0.0))
bool hacNeutral = hacNeutralF > 0
int hacolt = hacBuySig ? 1 : hacNeutral ? 0 : -1
bool hacEvtLong    = hacolt == 1 and hacolt[1] != 1
bool hacEvtShort   = hacolt == -1 and hacolt[1] != -1
bool hacStateLong  = hacolt > 0
bool hacStateShort = hacolt < 0

// ── Consecutive-loss / choppy-market circuit breaker ─────────────────────────
// Tracks losing streaks and pauses entries after maxConsecLoss straight losses.
// Directly solves the NEAR May 20-21 cluster: 4 back-to-back losses in a tight
// range where all macro MA/VWMA filters remained bullish but every touch failed.
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
            // Arm the pause: block entries for choppyPauseBars from this bar.
            choppyUntilBar := bar_index + choppyPauseBars
            consecLosses   := 0
    else
        consecLosses := 0  // winning trade resets the streak

bool choppyOk = not useChoppyFilter or bar_index > choppyUntilBar

// ── Logic 2 — Entry Bar Body Filter ──────────────────────────────────────────
// A bearish entry bar (close < open) whose body exceeds entryBodyAtrMult × ATR is
// rejected. This filters out high-momentum bearish closes right at support which
// have already shifted intra-bar momentum against the long setup.
bool bodyFilterOkLong = not useBodyFilter or close >= open or (open - close) <= entryBodyAtrMult * atr

// ── Logic 5 — Adaptive TP (computed per entry in entry block below) ───────────
// hhOkLong (already computed) is reused as the trend-strength indicator.
// When useAdaptiveTp is ON and tpMode = "Risk multiple (R)":
//   strong trend (hhOkLong = true)  → adaptiveTpStrong  R target + adaptiveTrailStrong  trigger
//   neutral trend (hhOkLong = false) → adaptiveTpNeutral R target + adaptiveTrailNeutral trigger
// savedTrailTriggerR is set at entry time and used by the bracket manager.
bool  adaptiveTrendStrong   = useAdaptiveTp and hhOkLong
float adaptiveEffTpR        = useAdaptiveTp and tpMode == "Risk multiple (R)" ? (adaptiveTrendStrong ? adaptiveTpStrong : adaptiveTpNeutral) : tpR
float adaptiveEffTrailR     = useAdaptiveTp ? (adaptiveTrendStrong ? adaptiveTrailStrong : adaptiveTrailNeutral) : trailTriggerR

// ── Always-on filters (gated by their own use* toggles) ───────────────────────
// These remaining filters always act as AND-confirmations when enabled (they are
// not part of the Primary/Filter trigger framework). SuperTrend, LinReg, VWMA,
// Local EMA, Volume, Higher-high, Body filter, RSI.
bool filtLong =
     (not useSuperTrend or stBull) and
     lrLongOk and
     vwOkLong and
     localTrendOkLong and volOkLong and hhOkLong and
     bodyFilterOkLong and
     rsiOkLong

bool filtShort =
     (not useSuperTrend or stBear) and
     lrShortOk and
     vwOkShort and
     localTrendOkShort and volOkShort

// ── S/R + MA section conditions (used by the Primary/Filter framework) ─────────
// S/R long readiness: a fresh support retest (touch path) OR a continuation breakout.
bool srTouchLong = retestEntryOk and strengthOkLong
bool srContLong  = useContLong and not deepSupTouch and close > priorHighCont and (not contRequireGreen or close > open) and (contRelaxStrength ? true : strengthOkLong)
bool srCondLong  = long_en and (srTouchLong or srContLong)
bool isContEntry = srEnable and srContLong and not srTouchLong
// S/R short readiness: a resistance touch with optional bounce + strength.
bool srCondShort = short_en and touchShort and bounceShort(nearestTouchRes) and strengthOkShort
// MA section condition (alignment + slope) for the framework.
bool maCondLong  = maUp and maSlopeOkLong
bool maCondShort = maDn and maSlopeOkShort

// ══════════════════════════════════════════════════════════════════════════════
// PRIMARY / FILTER ENTRY FRAMEWORK
//   • Sections set to PRIMARY are entry TRIGGERS — if ANY enabled primary fires
//     (an event), an entry is initiated.
//   • Sections set to NOT-PRIMARY are confirmation FILTERS — ALL enabled filters
//     (state must be bullish/bearish) must pass for the entry to go through.
//   • Participating sections: S/R, MA trend, Range Filter, Q-Trend, AlphaTrend, HACOLT.
//   • An entry requires: ≥1 primary active AND that primary fired AND all framework
//     filters pass AND all always-on filters (filtLong/filtShort) pass.
// ══════════════════════════════════════════════════════════════════════════════
// f_primary applies one section to the trigger/filter accumulators.
//   en      = section enabled, prim = section is primary,
//   evt     = trigger event (used when primary), state = bullish/bearish state (used when filter)

// LONG accumulators
bool priLongActive = false
bool priLongFired  = false
bool fltLongPass   = true
// S/R
if srEnable
    if srPrimary
        priLongActive := true
        priLongFired  := priLongFired or srCondLong
    else
        fltLongPass := fltLongPass and srCondLong
// MA trend
if useMaTrend
    if maPrimary
        priLongActive := true
        priLongFired  := priLongFired or maCondLong
    else
        fltLongPass := fltLongPass and maCondLong
// Range Filter
if rf_en
    if rf_primary
        priLongActive := true
        priLongFired  := priLongFired or rfEvtLong
    else
        fltLongPass := fltLongPass and rfStateLong
// Q-Trend
if qt_en
    if qt_primary
        priLongActive := true
        priLongFired  := priLongFired or qtEvtLong
    else
        fltLongPass := fltLongPass and qtStateLong
// AlphaTrend
if at_en
    if at_primary
        priLongActive := true
        priLongFired  := priLongFired or atEvtLong
    else
        fltLongPass := fltLongPass and atStateLong
// HACOLT
if hac_en
    if hac_primary
        priLongActive := true
        priLongFired  := priLongFired or hacEvtLong
    else
        fltLongPass := fltLongPass and hacStateLong

// SHORT accumulators
bool priShortActive = false
bool priShortFired  = false
bool fltShortPass   = true
if srEnable
    if srPrimary
        priShortActive := true
        priShortFired  := priShortFired or srCondShort
    else
        fltShortPass := fltShortPass and srCondShort
if useMaTrend
    if maPrimary
        priShortActive := true
        priShortFired  := priShortFired or maCondShort
    else
        fltShortPass := fltShortPass and maCondShort
if rf_en
    if rf_primary
        priShortActive := true
        priShortFired  := priShortFired or rfEvtShort
    else
        fltShortPass := fltShortPass and rfStateShort
if qt_en
    if qt_primary
        priShortActive := true
        priShortFired  := priShortFired or qtEvtShort
    else
        fltShortPass := fltShortPass and qtStateShort
if at_en
    if at_primary
        priShortActive := true
        priShortFired  := priShortFired or atEvtShort
    else
        fltShortPass := fltShortPass and atStateShort
if hac_en
    if hac_primary
        priShortActive := true
        priShortFired  := priShortFired or hacEvtShort
    else
        fltShortPass := fltShortPass and hacStateShort

// Final framework gate: at least one primary must be active AND have fired.
bool frameworkLongOk  = priLongActive  and priLongFired  and fltLongPass
bool frameworkShortOk = priShortActive and priShortFired and fltShortPass

confirmed = not useBarConfirm or barstate.isconfirmed

// ── Stop / target calculations ────────────────────────────────────────────────
float swingLow = ta.lowest(low, slSwingLb)
// R:R mode swing low (its own lookback) — stop is placed below this.
float rrSwingLow = ta.lowest(low, rrSwingLb)
// When R:R mode is on, force the single-target bracket (bypass partial-TP engine) for longs.
bool usePartialTpEff = usePartialTp and not useRR

calcLongStop(float entry, float lvl) =>
    float slAtr    = entry - slAtrMult * atr
    float slPct_   = entry * (1 - slPct / 100)
    float slStruct = na(lvl) ? slAtr : lvl - structBuff * atr
    float slSwing  = swingLow
    float sl       = slAtr
    switch slMode
        "ATR multiple"         => sl := slAtr
        "Percent"              => sl := slPct_
        "Below structure & ATR" => sl := math.min(slAtr, math.min(slStruct, slSwing))
        =>                         sl := math.min(slAtr, slSwing)
    float floor = entry - minSlDistAtr * atr
    math.min(sl, floor)

calcShortStop(float entry, float lvl) =>
    float slAtr    = entry + slAtrMult * atr
    float slPct_   = entry * (1 + slPct / 100)
    float slStruct = na(lvl) ? slAtr : lvl + structBuff * atr
    float slSwing  = ta.highest(high, slSwingLb)
    float sl       = slAtr
    switch slMode
        "ATR multiple"         => sl := slAtr
        "Percent"              => sl := slPct_
        "Below structure & ATR" => sl := math.max(slAtr, math.max(slStruct, slSwing))
        =>                         sl := math.max(slAtr, slSwing)
    float ceil = entry + minSlDistAtr * atr
    math.max(sl, ceil)

calcLongTp(float entry, float stop_, float rMultOverride = na) =>
    float risk = entry - stop_
    float rUse = na(rMultOverride) ? tpR : rMultOverride
    switch tpMode
        "Risk multiple (R)" => entry + risk * rUse
        "ATR multiple"      => entry + tpAtrMult * atr
        =>                     entry * (1 + tpPct / 100)

calcShortTp(float entry, float stop_) =>
    float risk = stop_ - entry
    switch tpMode
        "Risk multiple (R)" => entry - risk * tpR
        "ATR multiple"      => entry - tpAtrMult * atr
        =>                     entry * (1 - tpPct / 100)

// ── State variables ───────────────────────────────────────────────────────────
var float savedLongStop       = na
var float savedLongTp         = na
var float savedShortStop      = na
var float savedShortTp        = na
var float trailAnchorLong     = na
var float trailAnchorShort    = na
var bool  trailActiveLong     = false
var bool  trailActiveShort    = false
var bool  longHtfBreakMode    = false
var float trailSwingHiLong    = na
var float trailSwingLoShort   = na
// break-even stop level: set to entry price once the trade reaches breakEvenTriggerR.
var float breakEvenStop       = na
// Logic 5 — saved trail trigger R for the current trade (adaptive per-entry value).
var float savedTrailTriggerR  = na
// Live effective protective stop (for plotting) — set by the bracket managers below.
var float liveLongStop        = na
var float liveShortStop       = na

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 entryBarOk = not ordersOnConfirmedBar or barstate.isconfirmed

// Entry = framework trigger/filter result AND the always-on filters AND master enables.
bool rawLongSetup  = confirmed and strategy.position_size == 0 and long_en  and frameworkLongOk  and filtLong
bool rawShortSetup = confirmed and strategy.position_size == 0 and short_en and frameworkShortOk and filtShort

// ── One-trade-per-indicator-signal latch ──────────────────────────────────────
// A fresh buy trigger on any ENABLED RF / Q-Trend / AlphaTrend arms the gate.
// An executed long consumes it (set false in the entry block). While unconsumed,
// at most one long may be taken; no further long until another indicator fires.
bool anyIndBuyTrig = (rf_en and rfEvtLong) or (qt_en and qtEvtLong) or (at_en and atEvtLong)
var bool indSigArmed = false
if anyIndBuyTrig
    indSigArmed := true
bool oneTradeGateOk = not useOneTradePerSignal or indSigArmed

// choppyOk gates are applied here — both longs and shorts respect the pause.
bool longSignal  = rawLongSetup  and cooldownExitOk and choppyOk and entryBarOk and oneTradeGateOk
bool shortSignal = rawShortSetup and cooldownExitOk and choppyOk and entryBarOk

var float lastPubLongStop  = na
var float lastPubLongTp    = na
var float lastPubShortStop = na
var float lastPubShortTp   = na

if longSignal
    longHtfBreakMode := false
    float lvlStop = isContEntry ? na : (na(lastRetestLvl) ? nearestTouchSup : lastRetestLvl)
    lastRetestBar := na
    float sl      = calcLongStop(close, lvlStop)
    // Logic 5: use adaptive R target when applicable, otherwise fall back to tpR.
    // Continuation entries add contExtraTpR on top of the effective base R.
    float baseR   = adaptiveEffTpR
    float rAdj    = tpMode == "Risk multiple (R)"
         ? (isContEntry ? baseR + contExtraTpR : baseR)
         : na
    float tp      = calcLongTp(close, sl, rAdj)
    // R:R mode override — SL below the previous swing low, TP at reward:risk ratio.
    if useRR
        float rrSl = math.min(rrSwingLow - rrBufAtr * atr, close - minSlDistAtr * atr)
        sl := rrSl
        tp := close + (close - rrSl) * rrRatio
    // One-trade-per-signal: consume the armed indicator buy signal on this entry.
    indSigArmed          := false
    savedLongStop        := sl
    savedLongTp          := tp
    savedTrailTriggerR   := adaptiveEffTrailR   // Logic 5: per-trade trail trigger
    trailAnchorLong      := na
    trailActiveLong      := false
    trailSwingHiLong     := na
    breakEvenStop        := na
    lastPubLongStop      := na
    lastPubLongTp        := na
    strategy.entry("Long", strategy.long)

if shortSignal
    float sl = calcShortStop(close, nearestTouchRes)
    float tp = calcShortTp(close, sl)
    savedShortStop    := sl
    savedShortTp      := tp
    trailAnchorShort  := na
    trailActiveShort  := false
    trailSwingLoShort := na
    lastPubShortStop  := na
    lastPubShortTp    := na
    strategy.entry("Short", strategy.short)

// ── Unrealized R (gates soft exits) ──────────────────────────────────────────
float longUnrealR   = strategy.position_size > 0 and not na(savedLongStop) and strategy.position_avg_price > savedLongStop
     ? (close - strategy.position_avg_price) / (strategy.position_avg_price - savedLongStop)
     : 0.0
bool softExitRGateOk = minRForSoftExit <= 0 or longUnrealR >= minRForSoftExit

// ── Micro exits (SuperTrend, TF1 MA, LinReg) ─────────────────────────────────
float stLineLong      = stBull ? stUp : stDn
bool exitBelowStNow   = useExitBelowSt     and not na(stLineLong) and close < stLineLong
bool exitBelowMa1Now  = useExitBelowMa1    and ma1_en and not na(ma1) and close < ma1
bool exitBelowLrNow   = useExitBelowLinReg and useLinReg and close < bclose

// ── Volume Exhaustion Exit ────────────────────────────────────────────────────
// Condition: bar is printing a new N-bar high (momentum present) but volume is
// collapsing to less than volExhaustMult × SMA. This divergence between price
// extension and volume dry-up is a classic distribution / topping signal.
// The volExhaustMinR gate prevents this from firing too early in the trade.
bool  volExhaustRGateOk = volExhaustMinR <= 0 or longUnrealR >= volExhaustMinR
// Pre-compute ta.highest() unconditionally so its lookback buffer is always maintained.
float _volExhaustHi     = ta.highest(high[1], volExhaustLb)
bool exitVolExhaustNow  = useVolExhaustExit
     and strategy.position_size > 0
     and barstate.isconfirmed
     and volExhaustRGateOk
     and not na(volMaSeries) and volMaSeries > 0
     and volume < volExhaustMult * volMaSeries   // volume well below average
     and high >= _volExhaustHi                   // but price at new N-bar high

// ── HL Structure Break Exit ───────────────────────────────────────────────────
// A confirmed bar close below the most recent pivot low on hlBreakTf means the
// local uptrend has printed its first lower-low: structural invalidation.
// Fires significantly earlier than waiting for the 4h MA cross or full trailing
// stop to trigger — avoids giving back 2-3% of gained profit on breakdown bars.
float hlBreakPivLow  = useHlBreakExit ? f_last_pivot_low(hlBreakTf, hlBreakPivLen) : na
bool  hlBreakRGateOk = hlBreakMinR <= 0 or longUnrealR >= hlBreakMinR
bool  exitHlBreakNow = useHlBreakExit
     and strategy.position_size > 0
     and barstate.isconfirmed
     and hlBreakRGateOk
     and not na(hlBreakPivLow)
     and close < hlBreakPivLow   // confirmed close below last pivot low = first lower-low

// ── Composite long exit ───────────────────────────────────────────────────────
// Soft exits (RSI rollover) are folded in here alongside the MA/ST/LinReg exits.
bool exitLongSignalNow = strategy.position_size > 0 and confirmed and softExitRGateOk
     and (exitLongMaTrigger or exitBelowStNow or exitBelowMa1Now or exitBelowLrNow
          or rsiExitNow)

bool maCloseLongNow  = exitLongSignalNow or exitVolExhaustNow or exitHlBreakNow
bool maCloseShortNow = strategy.position_size < 0 and exitShortMaTrigger

if maCloseLongNow
    string exitWhy =
         exitVolExhaustNow  ? "Vol Exhaust"   :
         exitHlBreakNow     ? "HL Break"      :
         exitLongMaTrigger  ? "MTF MA exit"   :
         exitBelowStNow     ? "Below ST"      :
         exitBelowMa1Now    ? "Below TF1 MA"  :
         rsiExitNow         ? "RSI rollover"  :
         "Below LinReg"
    strategy.close("Long", comment = exitWhy)

if maCloseShortNow
    strategy.close("Short", comment = "MTF MA exit")

// ── HTF resistance break → runner trail ──────────────────────────────────────
if strategy.position_size > 0 and rawHtfResBreakNow
    bool firstHtfBreak = not longHtfBreakMode
    longHtfBreakMode  := true
    if firstHtfBreak and htfResetTrailAnchorOnBreak
        trailAnchorLong  := na
        trailSwingHiLong := na

// ── Break-even stop ───────────────────────────────────────────────────────────
// Once unrealized profit reaches breakEvenTriggerR × initial risk, move the hard
// stop to entry price. After this point the trade cannot result in a loss — only
// a scratch exit. Arms once (na → entry price) and is then held by the bracket.
if useBreakEven and strategy.position_size > 0 and not na(savedLongStop) and na(breakEvenStop)
    float risk = strategy.position_avg_price - savedLongStop
    if risk > 0 and high >= strategy.position_avg_price + risk * breakEvenTriggerR
        // Offset above entry covers fees so a "break-even" exit is net flat / tiny gain.
        breakEvenStop := strategy.position_avg_price * (1 + breakEvenOffsetPct / 100)

// ── Bracket management (SL + TP + trail + break-even) ────────────────────────
tickEps = syminfo.mintick * 2

// ══════════════════════════════════════════════════════════════════════════════
// STEP 3 — MULTI-STAGE PARTIAL TAKE PROFIT + POST-TP1 TRAILING (longs)
// ──────────────────────────────────────────────────────────────────────────────
// Engine overview (how the maths interact):
//   • At fill we capture the ORIGINAL filled quantity (ptpEntryQty) and avg fill
//     price (ptpEntryPx). Every tier sizes against ptpEntryQty — NOT the shrinking
//     remainder — so "close 25%" realises 25% of the trade you actually opened.
//   • Tier prices are pure % moves above entry:
//        tp1Price = ptpEntryPx·(1+tp1Pct/100), etc.
//   • Tiers are placed as independent strategy.exit orders, each an OCO of
//        {limit = tpNPrice, stop = stopUse}. TP1/TP2 use absolute qty = ptpEntryQty·sizeN/100.
//        TP3 carries NO qty, so it sweeps ALL remaining contracts (the runner) — this
//        guarantees the position is fully bracketed even if the three % sizes sum < 100.
//   • INTRA-BAR: because each leg is a resting stop/limit order, TradingView fills it
//     the instant price trades through the level on the live bar — SL and TP both
//     trigger mid-bar, not on close. (Entries remain bar-close via the entry block.)
//   • stopUse (shared by all three legs) ratchets up = max(hard SL, break-even,
//     post-TP1 trailing level). Once TP1 fills, the trailing source (Percent / Chandelier
//     Exit / ALMA SuperTrend) takes over the remainder and can only move the stop higher.
// ──────────────────────────────────────────────────────────────────────────────
var float ptpEntryQty   = na
var float ptpEntryPx    = na
var bool  ptpTrailArmed = false
var float ptpTrailAnchor = na
var bool  ptpTp1Done    = false   // set once TP1 price is reached → stop re-issuing TP1
var bool  ptpTp2Done    = false   // set once TP2 price is reached → stop re-issuing TP2

// Capture original size/price the bar the long position first appears.
bool justEnteredLong = strategy.position_size > 0 and nz(strategy.position_size[1]) <= 0
if justEnteredLong
    ptpEntryQty    := strategy.position_size
    ptpEntryPx     := strategy.position_avg_price
    ptpTrailArmed  := false
    ptpTrailAnchor := na
    ptpTp1Done     := false
    ptpTp2Done     := false

// Tier price levels (% move above the actual fill price).
float tp1Price = na(ptpEntryPx) ? na : ptpEntryPx * (1 + tp1Pct / 100)
float tp2Price = na(ptpEntryPx) ? na : ptpEntryPx * (1 + tp2Pct / 100)
float tp3Price = na(ptpEntryPx) ? na : ptpEntryPx * (1 + tp3Pct / 100)
// Absolute contracts to close at TP1 / TP2 (fractions of the ORIGINAL fill).
float tp1Qty   = na(ptpEntryQty) ? na : ptpEntryQty * tp1Size / 100
float tp2Qty   = na(ptpEntryQty) ? na : ptpEntryQty * tp2Size / 100

if usePartialTpEff and strategy.position_size > 0 and not na(ptpEntryPx) and not na(savedLongStop) and not maCloseLongNow
    // Mark a tier "done" once price trades through its limit (intra-bar high). A done
    // tier is NOT re-issued — this prevents strategy.exit from creating a fresh order
    // for the same chunk after it already filled (which would over-sell the position).
    if not ptpTp1Done and not na(tp1Price) and high >= tp1Price
        ptpTp1Done := true
    if not ptpTp2Done and not na(tp2Price) and high >= tp2Price
        ptpTp2Done := true

    // Arm the runner trail the instant TP1 is reached.
    if ptpTrailAfterTp1 and not ptpTrailArmed and ptpTp1Done
        ptpTrailArmed := true

    // Build the post-TP1 trailing level (Percent source); ratchet up only.
    float ptpTrailLvl = na
    if ptpTrailArmed and ptpTrailAfterTp1
        float cand    = close * (1 - ptpTrailPct / 100)
        ptpTrailAnchor := na(ptpTrailAnchor) ? cand : math.max(ptpTrailAnchor, cand)
        ptpTrailLvl    := ptpTrailAnchor

    // Shared protective stop for all remaining contracts = highest of:
    //   hard SL (if enabled / always in R:R mode)  ▸  break-even (entry)  ▸  post-TP1 trailing level.
    float stopUse = (useStopLoss or useRR) ? savedLongStop : na
    if useBreakEven and not na(breakEvenStop)
        stopUse := na(stopUse) ? breakEvenStop : math.max(stopUse, breakEvenStop)
    if ptpTrailArmed and not na(ptpTrailLvl)
        stopUse := na(stopUse) ? ptpTrailLvl : math.max(stopUse, ptpTrailLvl)
    liveLongStop := stopUse

    // Place / refresh the OCO exit legs. Unfilled tiers are re-issued every bar so the
    // shared stop ratchets up; a filled tier (flag set) is skipped so it isn't recreated.
    // TP3 (runner, no qty) always carries the stop, so the remainder is bracketed even
    // after TP1/TP2 fill or if the three % sizes sum to less than 100%.
    if not ptpTp1Done
        strategy.exit("TP1", from_entry = "Long", qty = tp1Qty, limit = tp1Price, stop = stopUse, comment_profit = "TP1", comment_loss = "SL")
    if not ptpTp2Done
        strategy.exit("TP2", from_entry = "Long", qty = tp2Qty, limit = tp2Price, stop = stopUse, comment_profit = "TP2", comment_loss = "SL")
    strategy.exit("TP3", from_entry = "Long",                    limit = tp3Price, stop = stopUse, comment_profit = "TP3", comment_loss = "SL")

// Legacy single-target bracket — runs only when the partial-TP engine AND R:R mode are off.
if not usePartialTpEff and not useRR and strategy.position_size > 0 and not na(savedLongStop) and not na(savedLongTp) and not maCloseLongNow
    float limitTpLong = longHtfBreakMode ? na : savedLongTp
    float risk        = strategy.position_avg_price - savedLongStop
    bool  trailLongEff   = useTrail or longHtfBreakMode
    // Logic 5: use savedTrailTriggerR (set at entry) so adaptive trail trigger
    // is preserved across bars. Falls back to trailTriggerR if not set (safety).
    float effTrailR      = nz(savedTrailTriggerR, trailTriggerR)
    float trTrigEff      = longHtfBreakMode ? htfTrailTriggerR : effTrailR
    float trMultEff      = longHtfBreakMode ? htfTrailAtrMult  : trailAtrMult
    bool  armTrailProfit = risk > 0 and high >= strategy.position_avg_price + risk * trTrigEff
    bool  armTrailBreak  = longHtfBreakMode and htfTrailImmediate
    if trailLongEff and (armTrailBreak or armTrailProfit)
        trailActiveLong := true
    float trailLvl = na
    if trailActiveLong and trailLongEff
        if trailStyle == "Chandelier (swing ± ATR)"
            trailSwingHiLong := na(trailSwingHiLong) ? high : math.max(trailSwingHiLong, high)
            trailLvl := trailSwingHiLong - trMultEff * atr
        else
            trailLvl := na(trailAnchorLong) ? close - trMultEff * atr : math.max(trailAnchorLong, close - trMultEff * atr)
            trailAnchorLong := trailLvl

    // Final stop = highest of: hard SL (if enabled / always in R:R mode), trailing level, break-even level.
    // This means: once break-even is armed, the stop can never drop back below entry.
    float stopUse = (useStopLoss or useRR) ? savedLongStop : na
    if trailActiveLong and not na(trailLvl)
        stopUse := na(stopUse) ? trailLvl : math.max(stopUse, trailLvl)
    if useBreakEven and not na(breakEvenStop)
        stopUse := na(stopUse) ? breakEvenStop : math.max(stopUse, breakEvenStop)
    liveLongStop := stopUse

    bool tpLegChanged       = not (na(limitTpLong) and na(lastPubLongTp)) and (na(limitTpLong) or na(lastPubLongTp) or math.abs(limitTpLong - lastPubLongTp) >= tickEps)
    bool stopLegChanged     = na(stopUse) != na(lastPubLongStop) or (not na(stopUse) and not na(lastPubLongStop) and math.abs(stopUse - lastPubLongStop) >= tickEps)
    bool longBracketChanged = stopLegChanged or tpLegChanged
    // Don't issue an exit order with neither a stop nor a limit (would be a no-op error).
    if longBracketChanged and not (na(stopUse) and na(limitTpLong))
        strategy.exit("Long X", from_entry = "Long", stop = stopUse, limit = limitTpLong)
        lastPubLongStop := stopUse
        lastPubLongTp   := limitTpLong

// ══════════════════════════════════════════════════════════════════════════════
// R:R EXIT ENGINE — swing-low stop + ratio TP, optional % partial TPs + % trailing
//   • Base stop  = savedLongStop (swing low − buffer), set at entry.
//   • Optional % trailing stop ratchets the stop up (never down).
//   • Optional partial TPs: scale out at TP1 % and TP2 %; the runner exits at the
//     reward:risk target (savedLongTp). When partials are off, a single ratio TP is used.
//   • The shared protective stop (max of swing-low SL, break-even, % trail) brackets
//     every leg, so the position is always protected.
// ══════════════════════════════════════════════════════════════════════════════
if useRR and strategy.position_size > 0 and not na(savedLongStop) and not na(ptpEntryPx) and not maCloseLongNow
    float rrEntryPx = ptpEntryPx
    // Percentage partial-TP tier prices + sizes (fractions of the ORIGINAL fill).
    float rrTp1P = rrEntryPx * (1 + rrTp1Pct / 100)
    float rrTp2P = rrEntryPx * (1 + rrTp2Pct / 100)
    float rrTp1Q = na(ptpEntryQty) ? na : ptpEntryQty * rrTp1Size / 100
    float rrTp2Q = na(ptpEntryQty) ? na : ptpEntryQty * rrTp2Size / 100
    // Mark a tier done once price trades through it (don't re-issue → no over-sell).
    if rrUsePartialTp and not ptpTp1Done and high >= rrTp1P
        ptpTp1Done := true
    if rrUsePartialTp and not ptpTp2Done and high >= rrTp2P
        ptpTp2Done := true
    // Percentage trailing stop — arms when in profit (optionally after +rrTrailActPct%).
    if rrUseTrailSl and not ptpTrailArmed and (rrTrailActPct <= 0 ? close > rrEntryPx : high >= rrEntryPx * (1 + rrTrailActPct / 100))
        ptpTrailArmed := true
    float rrTrailLvl = na
    if rrUseTrailSl and ptpTrailArmed
        float cand = close * (1 - rrTrailPct / 100)
        ptpTrailAnchor := na(ptpTrailAnchor) ? cand : math.max(ptpTrailAnchor, cand)
        rrTrailLvl     := ptpTrailAnchor
    // Shared protective stop = max(swing-low SL, break-even, % trail).
    float rrStopUse = savedLongStop
    if useBreakEven and not na(breakEvenStop)
        rrStopUse := math.max(rrStopUse, breakEvenStop)
    if rrUseTrailSl and not na(rrTrailLvl)
        rrStopUse := math.max(rrStopUse, rrTrailLvl)
    liveLongStop := rrStopUse
    // Place the exit legs. Runner always targets the reward:risk TP (savedLongTp).
    if rrUsePartialTp
        if not ptpTp1Done
            strategy.exit("RR1", from_entry = "Long", qty = rrTp1Q, limit = rrTp1P, stop = rrStopUse, comment_profit = "TP1", comment_loss = "SL")
        if not ptpTp2Done
            strategy.exit("RR2", from_entry = "Long", qty = rrTp2Q, limit = rrTp2P, stop = rrStopUse, comment_profit = "TP2", comment_loss = "SL")
        strategy.exit("RR3", from_entry = "Long",                   limit = savedLongTp, stop = rrStopUse, comment_profit = "TP", comment_loss = "SL")
    else
        strategy.exit("RR X", from_entry = "Long", limit = savedLongTp, stop = rrStopUse, comment_profit = "TP", comment_loss = "SL")

if strategy.position_size < 0 and not na(savedShortStop) and not na(savedShortTp) and not maCloseShortNow
    float risk = savedShortStop - strategy.position_avg_price
    if useTrail and risk > 0 and low <= strategy.position_avg_price - risk * trailTriggerR
        trailActiveShort := true
    float trailLvl = na
    if trailActiveShort and useTrail
        if trailStyle == "Chandelier (swing ± ATR)"
            trailSwingLoShort := na(trailSwingLoShort) ? low : math.min(trailSwingLoShort, low)
            trailLvl := trailSwingLoShort + trailAtrMult * atr
        else
            trailLvl := na(trailAnchorShort) ? close + trailAtrMult * atr : math.min(trailAnchorShort, close + trailAtrMult * atr)
            trailAnchorShort := trailLvl
    float _hardShortStop   = useStopLoss ? savedShortStop : na
    float stopUse          = trailActiveShort and not na(trailLvl) ? (na(_hardShortStop) ? trailLvl : math.min(_hardShortStop, trailLvl)) : _hardShortStop
    liveShortStop          := stopUse
    bool  stopLegChangedS  = na(stopUse) != na(lastPubShortStop) or (not na(stopUse) and not na(lastPubShortStop) and math.abs(stopUse - lastPubShortStop) >= tickEps)
    bool  shortBracketChanged = stopLegChangedS or na(lastPubShortTp) or math.abs(savedShortTp - lastPubShortTp) >= tickEps
    if shortBracketChanged and not (na(stopUse) and na(savedShortTp))
        strategy.exit("Short X", from_entry = "Short", stop = stopUse, limit = savedShortTp)
        lastPubShortStop := stopUse
        lastPubShortTp   := savedShortTp

// Reset all state when flat (but not on the bar that just opened a new position).
flat = strategy.position_size == 0
if flat and not longSignal and not shortSignal
    savedLongStop       := na
    savedLongTp         := na
    savedShortStop      := na
    savedShortTp        := na
    trailAnchorLong     := na
    trailAnchorShort    := na
    trailActiveLong     := false
    trailActiveShort    := false
    trailSwingHiLong    := na
    trailSwingLoShort   := na
    longHtfBreakMode    := false
    breakEvenStop       := na
    liveLongStop        := na
    liveShortStop       := na
    savedTrailTriggerR  := na  // Logic 5: clear adaptive trail trigger
    lastPubLongStop     := na
    lastPubLongTp       := na
    lastPubShortStop    := na
    lastPubShortTp      := na
    // Step 3: clear partial-TP engine state so the next trade starts clean.
    ptpEntryQty         := na
    ptpEntryPx          := na
    ptpTrailArmed       := false
    ptpTrailAnchor      := na
    ptpTp1Done          := false
    ptpTp2Done          := false

// ── Plotting ──────────────────────────────────────────────────────────────────
plot(showSrPlots and tf5_en ? sup5 : na,    "Support 5m",  color = color.new(color.aqua,  40), linewidth = 1, style = plot.style_linebr)
plot(showSrPlots ? sup15 : na,              "Support 15m", color = color.new(color.teal,  30), linewidth = 1, style = plot.style_linebr)
plot(showSrPlots ? sup60 : na,              "Support 1h",  color = color.new(color.green, 30), linewidth = 2, style = plot.style_linebr)
plot(showSrPlots ? sup240 : na,             "Support 4h",  color = color.new(color.lime,  30), linewidth = 2, style = plot.style_linebr)

plot(showSrPlots and tf5_en  and not (showFlipSup and flipR5)   ? res5   : na, "Resistance 5m",  color = color.new(color.yellow, 50), linewidth = 1, style = plot.style_linebr)
plot(showSrPlots and tf15_en and not (showFlipSup and flipR15)  ? res15  : na, "Resistance 15m", color = color.new(color.orange, 30), linewidth = 1, style = plot.style_linebr)
plot(showSrPlots and tf60_en and not (showFlipSup and flipR60)  ? res60  : na, "Resistance 1h",  color = color.new(color.red,    30), linewidth = 2, style = plot.style_linebr)
plot(showSrPlots and tf240_en and not (showFlipSup and flipR240) ? res240 : na, "Resistance 4h", color = color.new(color.maroon, 30), linewidth = 2, style = plot.style_linebr)

plot(showSrPlots and showFlipSup and tf5_en   and flipR5   ? res5   : na, "5m broken res → sup",  color = color.new(color.lime, 0), linewidth = 2, style = plot.style_linebr)
plot(showSrPlots and showFlipSup and tf15_en  and flipR15  ? res15  : na, "15m broken res → sup", color = color.new(color.lime, 0), linewidth = 2, style = plot.style_linebr)
plot(showSrPlots and showFlipSup and tf60_en  and flipR60  ? res60  : na, "1h broken res → sup",  color = color.new(color.aqua, 0), linewidth = 2, style = plot.style_linebr)
plot(showSrPlots and showFlipSup and tf240_en and flipR240 ? res240 : na, "4h broken res → sup",  color = color.new(color.teal, 0), linewidth = 2, style = plot.style_linebr)

plot(showStBand ? (stTrendVal == 1 ? stUp : na) : na, "ST up band", color = color.new(color.green, 40), linewidth = 2)
plot(showStBand ? (stTrendVal == -1 ? stDn : na) : na, "ST dn band", color = color.new(color.red,   40), linewidth = 2)

plot(plotMaLines and ma1_en ? ma1 : na, "MA TF1", color = color.new(color.orange,  0), linewidth = 2)
plot(plotMaLines and ma2_en ? ma2 : na, "MA TF2", color = color.new(color.white,  15), linewidth = 2)
plot(plotMaLines and ma3_en ? ma3 : na, "MA TF3", color = color.new(color.fuchsia, 0), linewidth = 2)
plot(plotMaLines and ma4_en ? ma4 : na, "MA TF4", color = color.new(color.aqua,    0), linewidth = 2)
plot(plotVwmaLine  and useVwmaTrend  ? vwTrend  : na, "VWMA (trend)",   color = color.new(color.yellow, 0), linewidth = 2)
plot(plotLocalEma and useLocalTrend ? localFastEma : na, "Local EMA Fast", color = color.new(color.blue, 20), linewidth = 1)
plot(plotLocalEma and useLocalTrend ? localSlowEma : na, "Local EMA Slow", color = color.new(color.navy, 30), linewidth = 1)
plot(plotExitMa and (useExitLongMa or useExitShortMa) ? exitMaAligned : na, "Exit MA (MTF)", color = color.new(color.purple, 0), linewidth = 2)

// ── Higher-high structure plot (last + prior pivot highs on HH TF) ────────────
plot(plotHhStruct ? hhLastPH  : na, "HH last pivot high",  color = color.new(color.lime,   0), linewidth = 2, style = plot.style_linebr)
plot(plotHhStruct ? hhPriorPH : na, "HH prior pivot high", color = color.new(color.orange, 0), linewidth = 1, style = plot.style_linebr)

// ── Stop-loss level plot (live effective stop, whichever SL mode) ─────────────
plot(plotSlLevel and strategy.position_size > 0 ? liveLongStop  : na, "Long stop loss",  color = color.new(color.red, 0), linewidth = 2, style = plot.style_linebr)
plot(plotSlLevel and strategy.position_size < 0 ? liveShortStop : na, "Short stop loss", color = color.new(color.red, 0), linewidth = 2, style = plot.style_linebr)

// ── Swing low reference + R:R levels ──────────────────────────────────────────
plot(plotSwingLow ? swingLow : na, "Swing low (SL ref)", color = color.new(color.orange, 0), linewidth = 1, style = plot.style_linebr)
// Projected R:R levels (shown while flat so you can preview the next trade's SL/TP).
float rrProjSl = math.min(rrSwingLow - rrBufAtr * atr, close - minSlDistAtr * atr)
float rrProjTp = close + (close - rrProjSl) * rrRatio
plot(plotRR and useRR ? (strategy.position_size > 0 ? liveLongStop  : rrProjSl) : na, "R:R Stop",   color = color.new(color.red,   0), linewidth = 2, style = plot.style_linebr)
plot(plotRR and useRR ? (strategy.position_size > 0 ? savedLongTp   : rrProjTp) : na, "R:R Target", color = color.new(color.green, 0), linewidth = 2, style = plot.style_linebr)

// ── Extra requested plots — LinReg close, ATR, Volume SMA (all default OFF) ────
plot(plotLinReg ? bclose : na, "LinReg close", color = color.new(color.aqua, 0), linewidth = 2)
plot(plotAtr ? atr : na, "ATR (risk)", color = color.new(color.gray, 0), linewidth = 1)
plot(plotVolSma ? volMaSeries : na, "Volume SMA", color = color.new(color.gray, 0), linewidth = 1)

// ── NEW indicator plots (all default OFF) ─────────────────────────────────────
// Range Filter line + target bands.
plot(plotRangeFilter ? rfFilt : na, "Range Filter", color = rfUpward > 0 ? color.new(color.white, 0) : rfDownward > 0 ? color.new(color.blue, 0) : color.new(color.rgb(144, 191, 249), 0), linewidth = 2)
plot(plotRangeFilter ? rfHband : na, "RF High Target", color = color.new(color.white, 70))
plot(plotRangeFilter ? rfLband : na, "RF Low Target",  color = color.new(color.blue, 70))
// Q-Trend trend line (colored by last signal).
plot(plotQTrend ? qtM : na, "Q-Trend", color = qtLs == "B" ? color.new(color.green, 0) : color.new(color.red, 0), linewidth = 3)
// AlphaTrend (line + 2-bar lag line, like the original).
plot(plotAlphaTrend ? AlphaTrend : na,    "AlphaTrend",     color = color.new(color.rgb(0, 34, 252), 0), linewidth = 3)
plot(plotAlphaTrend ? AlphaTrend[2] : na, "AlphaTrend lag", color = color.new(color.rgb(252, 4, 0), 0), linewidth = 3)
// HACOLT state as a background tint (oscillator can't share the price scale cleanly).
bgcolor(plotHacolt ? (hacolt > 0 ? color.new(color.green, 85) : hacolt < 0 ? color.new(color.red, 85) : color.new(color.blue, 90)) : na, title = "HACOLT state")

// RSI availability check marker (plots a small dot when RSI is in the long band).
plotshape(plotRsiState and not na(rsiVal) and rsiVal >= rsiLongMin and rsiVal <= rsiLongMax ? high : na,
     title = "RSI in long band", style = shape.circle, location = location.abovebar, color = color.new(color.purple, 40), size = size.tiny)

plotshape(showMarkers and longSignal and isContEntry  ? low : na, title = "Cont long", style = shape.labelup,   location = location.absolute, color = color.new(color.teal,  0), textcolor = color.white, text = "Cont", size = size.small)
plotshape(showMarkers and longSignal and not isContEntry ? low : na, title = "Buy long", style = shape.labelup, location = location.absolute, color = color.new(color.green, 0), textcolor = color.white, text = "Buy",  size = size.small)
plotshape(showMarkers and shortSignal ? high : na, title = "Sell", style = shape.labeldown, location = location.absolute, color = color.new(color.red, 0), textcolor = color.white, text = "Sell", size = size.small)

// Small ✕ at top of bar while the choppy-market pause is active and flat.
plotshape(useChoppyFilter and bar_index <= choppyUntilBar and strategy.position_size == 0 ? high : na,
     title = "Choppy pause", style = shape.xcross, location = location.absolute,
     color = color.new(color.red, 40), size = size.tiny)

// Closed-trade SELL marker + P/L label (fires on each closed trade, incl. partial TPs).
int ctClosed = strategy.closedtrades
if (showExitPnL or showMarkers) and ctClosed > nz(ctClosed[1])
    int   ti   = ctClosed - 1
    float esz  = strategy.closedtrades.size(ti)
    float ep   = strategy.closedtrades.entry_price(ti)
    float xp   = strategy.closedtrades.exit_price(ti)
    float pctx = na
    if ep != 0 and esz != 0
        pctx := esz > 0 ? (xp - ep) / ep * 100.0 : (ep - xp) / ep * 100.0
    string side = esz > 0 ? "Sell" : "Cover"
    string pStr = na(pctx) ? "—" : (pctx >= 0 ? "+" : "") + str.tostring(pctx, "#.##") + "%"
    string tTxt = side + " " + pStr
    color  tCol = na(pctx) ? color.gray : pctx >= 0 ? color.new(color.green, 0) : color.new(color.red, 0)
    float  yAt  = esz > 0 ? high : low
    label.new(bar_index, yAt, tTxt, xloc = xloc.bar_index, style = esz > 0 ? label.style_label_down : label.style_label_up,
         color = tCol, textcolor = color.white, size = size.small)

// Cumulative P/L table (top-right, last bar)
var table tblCum = na
if showCumPnL
    if na(tblCum)
        tblCum := table.new(position.top_right, 2, 2, border_width = 1, frame_color = color.new(color.gray, 50), frame_width = 1)
    if barstate.islastconfirmedhistory
        float netPct = strategy.initial_capital != 0 ? 100.0 * strategy.netprofit / strategy.initial_capital : na
        table.cell(tblCum, 0, 0, "Net P/L %",      text_color = color.white, bgcolor = color.new(color.black, 75))
        table.cell(tblCum, 1, 0, str.tostring(netPct, "#.##") + "%", text_color = color.white, bgcolor = color.new(color.black, 75))
        table.cell(tblCum, 0, 1, "Closed trades",  text_color = color.white, bgcolor = color.new(color.black, 75))
        table.cell(tblCum, 1, 1, str.tostring(strategy.closedtrades), text_color = color.white, bgcolor = color.new(color.black, 75))

plotshape(showHtfBreak and strategy.position_size > 0 and longHtfBreakMode and not longHtfBreakMode[1] ? high : na,
     title = "HTF break runner", style = shape.diamond, location = location.absolute,
     color = color.new(color.purple, 0), size = size.tiny)

// ── Alerts — Custom FastAPI bot ───────────────────────────────────────────────
if use_custom_webhook and (ALERT_SECRET == "REPLACE_ME" or str.length(ALERT_SECRET) < 32)
    runtime.error("Set [Custom bot] secret to your full bot webhook secret")

f_bot_json_buy() =>
    "{\"secret\":\"" + ALERT_SECRET + "\",\"action\":\"buy\",\"symbol\":\"" + syminfo.ticker +
     "\",\"quote_order_qty\":" + str.tostring(alert_buy_usdt) +
     ",\"dedupe_key\":\"L-" + str.tostring(bar_index) + "-" + str.tostring(time) + "\"}"

f_bot_json_sell_exit() =>
    "{\"secret\":\"" + ALERT_SECRET + "\",\"action\":\"sell\",\"symbol\":\"" + syminfo.ticker +
     "\",\"dedupe_key\":\"X-" + str.tostring(bar_index) + "-" + str.tostring(time) + "\"}"

if use_custom_webhook and longSignal
    alert(f_bot_json_buy(), alert.freq_once_per_bar_close)

if use_custom_webhook and shortSignal
    alert(
         "{\"secret\":\"" + ALERT_SECRET + "\",\"action\":\"sell\",\"symbol\":\"" + syminfo.ticker +
          "\",\"quantity\":" + str.tostring(alert_sell_base) +
          ",\"dedupe_key\":\"S-" + str.tostring(bar_index) + "-" + str.tostring(time) + "\"}",
         alert.freq_once_per_bar_close)

bool longJustClosed = strategy.position_size == 0 and strategy.position_size[1] > 0
if use_custom_webhook and longJustClosed
    alert(f_bot_json_sell_exit(), alert.freq_once_per_bar_close)

Overview

When enabled, it uses pivots on four timeframes, ATR-sized touch zones and a retest window. Trend filters include moving averages, SuperTrend, regression, VWMA and volume, plus an embedded RSI check. Exits include a swing-low stop with ratio target and partial take profits. It pauses after consecutive losses.

Details

Published by

MuhammadMuhamid on GitHub

Type

Strategy, Pine Script v6

Draws

On the price chart

Category

Support & resistance, Trend

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
Pause entries after N consecutive lossesbooltrueuseChoppyFilter
Max consecutive losses before pauseint2maxConsecLoss
Pause duration (bars) after max losses hitint20choppyPauseBars
Signal deliverystring3Commas Signal Botsignal_delivery
[Custom bot] secret → WEBHOOK_SECRETstringREPLACE_MEALERT_SECRET
[Custom bot] BUY quote USDTfloat800.0alert_buy_usdt
[Custom bot] SELL base qtyfloat0.001alert_sell_base
[Custom bot] symbolstringAPTUSDTalert_symbol
Enable Support / Resistance sectionboolfalsesrEnable
Show all 40 settings
SettingTypeDefaultVariable
S/R is PRIMARY (entry trigger)boolfalsesrPrimary
Touch zone modestringATRtouchBandMode
Touch zone × ATRfloat0.85touchAtrMult
or ticksint5touchTicks
Minimum pivot touchesint1srStrengthMin
Strength: pivot-touch TFtimeframe15strengthTf
Strength TF pivot lengthint14strengthPivotLen
Use 5m S/Rbooltruetf5_en
pivLen5int13pivLen5
Use 15m S/Rbooltruetf15_en
pivLen15int9pivLen15
Use 1h S/Rbooltruetf60_en
pivLen60int9pivLen60
Use 4h S/Rbooltruetf240_en
pivLen240int5pivLen240
Use: bars after support touch to confirm entrybooltrueuseRetestWindow
Bars after support touch to confirm entryint8retestConfirmBars
Use: pullback — prior bars close above supportbooltrueusePriorAbove
Pullback: prior bars close above supportint4priorAboveLb
Block pierce-and-reclaim entriesbooltrueblockBreakReclaim
Pierce below support (× ATR) = reclaim blockfloat0.05reclaimPierceAtr
Broken MTF resistance → support (retest longs)boolfalseuseResFlipSup
Flip on confirmed bar onlyboolfalseflipConfirmOnClose
Flip when close above res (+ × ATR)float0.05flipBreakBufAtr
Unflip when close below res (− × ATR)float0.2flipInvalidBufAtr
Require MA uptrend / downtrendboolfalseuseMaTrend
MA trend is PRIMARY (entry trigger)boolfalsemaPrimary
MA: block entries while MTF MA is nabooltruemaBlockWhenNa
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

This trend-following strategy executes a classic Donchian channel breakout system designed to cross-validate backtest results against external framework benchmarks. It triggers a long entry when the closing price breaks above the highest high of the prior entr

Strategy v6 BreakoutTrend
Updated yesterday · AGPL-3.0 GitHub repository of Forven S01509 - Simple Donchian 20/10 Breakout

Omar Edited WF

by Alorse
330 stars

Omar Edited WF is a trading strategy overlay built around Williams Fractals and multiple Exponential Moving Averages. It detects high and low fractal pivot points over configurable lookback periods alongside three adjustable EMA trendlines. Long entries are ma

Strategy v5 TrendMoving averages
Updated 2 months ago · None stated GitHub repository of Omar Edited WF
257 stars

This strategy calculates dynamic support and resistance levels using pivot highs and lows alongside a custom smoothed trend filter and a 3-period EMA. It identifies entry opportunities when price breaks out relative to the trend filter and short-term EMA cross

Strategy v5 TrendSupport & resistance Repaints
Updated 6 weeks ago · None stated GitHub repository of 30s - ES 1m Strategy Candle Bar
106 stars

This overlay strategy plots a two-timeframe Ichimoku setup on the price chart, with Tenkan, Kijun, Chikou, and Kumo from a base higher timeframe and an optional highest-timeframe cloud. It allows two entries in the same direction and uses ATR-based trailing st

Strategy v4 TrendVolatility & bands
Updated 2 years ago · GPL-3.0 GitHub repository of 2Tier Ichimoku Pyramiding [Takazudo]
104 stars

This strategy combines three Exponential Moving Averages with Williams Fractals to execute trend-following or mean-reverting trades. It analyzes price structure using short, medium, and long-term EMAs along with fractal highs and lows. Entries trigger when fra

Strategy v5 TrendMoving averages
Updated 3 years ago · MIT GitHub repository of 3MA and Fractals Strategy

More strategys

Trade Your Charts With a Regulated Broker

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

Compare brokers