Strategy · Pine Script v6 · Own pane

WaveTrend v4 [WavesUnchained] Strategy

by casoon

This strategy calculates the WaveTrend oscillator alongside Stochastic RSI to identify reversal setups at overbought and oversold extremes.

View on GitHub

Repaints

GitHub stars9on the repository
Pine versionv6current
Lines of code1,242
Settings40inputs
LicenceMIT
Last update3 days ago27 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,242 lines of Pine Script v6 from strategies/wavetrend/wavetrend_v4_strategy.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

wavetrend_v4_strategy.pine
//@version=6
// ============================================================================
// WaveTrend v4 [WavesUnchained] — Strategy (standalone)
// Version: 4.92
// Author: WavesUnchained
// Build: 2026-10-12
// ============================================================================
// WT oscillator cross at OS/OB extremes — entry, ATR stop/target, break-even.
// Optional gates: StochRSI, ATR rank, BB expansion, chop, pivot proximity, sweep.
//
// Features:
//   ✓ WT cross below OS level → long; above OB level → short
//   ✓ ATR frozen at signal bar; SL/TP set from fill price + frozen ATR
//   ✓ Break-even confirmed on close from next bar only (calc_on_order_fills)
//   ✓ Hard 1-bar post-exit cooldown prevents same-bar re-entry after fill
//   ✓ Exit reason logged from exit_comment (tp / sl / be / trail / struct)
//   ✓ Session filter, date range, max drawdown, max intraday loss, losing streak limit
//   ✓ Diagnostic logs: WT4 PIVOT, WT4 BLOCKED, WT4 ENTRY, WT4 REAL EXIT
//
// ============================================================================

strategy("WaveTrend v4 [WavesUnchained] — Strategy",
     overlay             = false,
     calc_on_order_fills = true,
     default_qty_type    = strategy.percent_of_equity,
     default_qty_value   = 10,
     initial_capital     = 10000,
     commission_type     = strategy.commission.percent,
     commission_value    = 0.02,
     slippage            = 1)

// Synthetische Bar-Preise (Heikin Ashi, Renko …): Orders füllen zu Marktpreisen,
// der Backtest gilt dort nicht. Kurzer Hinweis unten rechts statt auf der Kerze.
var table chartTypeWarn = table.new(position.bottom_right, 1, 1)
if barstate.islast and not chart.is_standard
    table.cell(chartTypeWarn, 0, 0, "WT v4 Strategy · backtest invalid on this chart type",
         bgcolor = color.new(color.red, 10), text_color = color.white, text_size = size.small)

// ─── Inputs ──────────────────────────────────────────────────────────────────
g_wt      = "WaveTrend"
g_srsi    = "Stochastic RSI"
g_risk    = "Risk"
g_be      = "Break-Even"
g_strat   = "Strategy"
g_pivot   = "Pivots"
g_quality = "Quality Gates (data-driven)"
g_filters = "Filters"
g_div     = "Divergences"

chLen   = input.int(10,    "Channel Length",         minval=1,              group=g_wt)
avgLen  = input.int(21,    "Average Length",         minval=1,              group=g_wt)
sigLen  = input.int(4,     "Signal Smoothing",       minval=1,              group=g_wt)
osLevel15M   = input.float(-10, "OS Level 15M (long)",  step=1,                group=g_wt,
     tooltip="RTC analysis: WT depth < 10 correlates with bad 15M setups (good=17.3, bad=6.4). Requires |wt1| ≥ 10.")
osLevel1H    = input.float(-20, "OS Level 1H  (long)",  step=1,                group=g_wt)
osLevel4H    = input.float(-5,  "OS Level 4H  (long)",  step=1,                group=g_wt)
osLevel1D    = input.float(-20, "OS Level 1D  (long)",  step=1,                group=g_wt)
obLevel15M   = input.float(10,  "OB Level 15M (short)", step=1,                group=g_wt)
obLevel1H    = input.float(20,  "OB Level 1H  (short)", step=1,                group=g_wt)
obLevel4H    = input.float(5,   "OB Level 4H  (short)", step=1,                group=g_wt)
obLevel1D    = input.float(20,  "OB Level 1D  (short)", step=1,                group=g_wt)
// Upper depth cap: block entries that are too deep into OS/OB (Reversal-Type B filter for 4H/1D)
osFloor4H    = input.float(-999, "OS Floor 4H (long cap)",  step=1, group=g_wt,
     tooltip="4H long only when wt1 ≥ this value. Blocks entries too deep in OS. Set to -999 to disable (default).")
osFloor1D    = input.float(-48,  "OS Floor 1D (long cap)",  step=1, group=g_wt,
     tooltip="1D long only when wt1 ≥ this value. RTC analysis: |wt1| > 48 correlates with bad 1D setups (WT Extreme good=27%, bad=43%).")
obFloor4H    = input.float(999,  "OB Floor 4H (short cap)", step=1, group=g_wt,
     tooltip="4H short only when wt1 ≤ this value. Set to 999 to disable (default).")
obFloor1D    = input.float(48,   "OB Floor 1D (short cap)", step=1, group=g_wt,
     tooltip="1D short only when wt1 ≤ this value. RTC analysis: |wt1| > 48 correlates with bad 1D setups.")
// Grind Engine: disable noFollow for deep 1H entries (abs_wt1 ≥ threshold) — B-type trades
useGrindEngine  = input.bool(false, "Grind Engine: no noFollow for deep 1H entries", group=g_wt,
     tooltip="When abs(wt1) at entry ≥ Grind abs(wt1) min, disables noFollow for that trade. Tested negative (test142) — keep off.")
grindAbsWt1Min  = input.float(60,   "  Grind abs(wt1) min (1H)",                    group=g_wt, step=5)
smooth15M    = input.string("RMA", "Smoothing 15M",
     options=["EMA", "RMA", "SMA", "WMA", "HMA", "JMA"],                         group=g_wt)
smooth1H     = input.string("HMA", "Smoothing 1H",
     options=["EMA", "RMA", "SMA", "WMA", "HMA", "JMA"],                         group=g_wt)
smooth4H     = input.string("JMA", "Smoothing 4H",
     options=["EMA", "RMA", "SMA", "WMA", "HMA", "JMA"],                         group=g_wt)
smooth1D     = input.string("SMA", "Smoothing 1D",
     options=["EMA", "RMA", "SMA", "WMA", "HMA", "JMA"],                         group=g_wt)
jmaPhase     = input.int(50,   "  JMA Phase", minval=-100, maxval=100,            group=g_wt)
jmaPower     = input.float(2.0,"  JMA Power", minval=1.0,  maxval=4.0, step=0.25, group=g_wt)

srsiRsiLen   = input.int(14,   "RSI Length",        minval=1,               group=g_srsi)
srsiStochLen = input.int(14,   "Stoch Length",      minval=1,               group=g_srsi)
srsiKSmooth  = input.int(3,    "K Smooth",          minval=1,               group=g_srsi)
srsiDSmooth  = input.int(3,    "D Smooth",          minval=1,               group=g_srsi)
srsiOsLevel  = input.float(20, "OS Level",           step=1,                 group=g_srsi)
srsiObLevel  = input.float(80, "OB Level",           step=1,                 group=g_srsi)
srsiPersist  = input.int(5,    "Trend Block (Bars stuck at OS/OB)", minval=1, group=g_srsi,
     tooltip="Block entry if K has been at OS/OB for this many consecutive bars — signals a trend, not a reversal.")
useSrsiGate  = input.bool(false, "Use StochRSI Gate",                          group=g_srsi)

atrLen  = input.int(14,    "ATR Length",  minval=1,                         group=g_risk)
slMult  = input.float(1.5, "SL (ATR×)",  minval=0.1, step=0.1,             group=g_risk,
     tooltip="Stop-Loss multiplier (= risk = 1R). Same across all TFs.")
tpMult15M = input.float(3.0, "TP 15M (ATR×)",  minval=0.1, step=0.1,        group=g_risk)
tpMult1H  = input.float(3.0, "TP 1H  (ATR×)",  minval=0.1, step=0.1,        group=g_risk,
     tooltip="1H reagiert empfindlich auf TP-Erhöhung (test119: 3.5 → -0.13 avgR).")
tpMult4H  = input.float(4.0, "TP 4H  (ATR×)",  minval=0.1, step=0.1,        group=g_risk,
     tooltip="4H pivot moves laufen oft >2R — moderate Erhöhung erlaubt mehr Capture.")
tpMult1D  = input.float(4.0, "TP 1D  (ATR×)",  minval=0.1, step=0.1,        group=g_risk,
     tooltip="1D pivot moves sind groß — höherer TP lohnt sich (test119: +0.10/+0.20 avgR).")

enableBE = input.bool(true,  "Enable Break-Even Stop",                      group=g_be)
beTrigR  = input.float(1.0,  "Trigger (R×)", minval=0.1, step=0.1,         group=g_be)

// Trailing Stop — activates after trade reaches trailTriggerR progress.
// SL trails behind highest-since-entry price by trailDistanceR (in R units).
// Only moves the stop UP (long) / DOWN (short) — never against us.
enableTrail   = input.bool(true,  "Enable Trailing Stop",                  group=g_be,
     tooltip="Trails SL after the trade reaches trailTriggerR progress. " +
             "When enabled the fixed ATR TP is not placed as an order — the trail (and the structural exit) " +
             "take over the profit side. With TP at 2R (15M/1H) a TP limit would fill before the trail could ever arm.")
trailTriggerR = input.float(2.0, "  Trail Trigger (R×)",  minval=0.1, step=0.1, group=g_be)
trailDistanceR= input.float(1.0, "  Trail Distance (R×)", minval=0.1, step=0.1, group=g_be,
     tooltip="Stop trails this distance behind extreme price (in R units).")

showSignals   = input.bool(true, "Show Entry Labels on Chart",                 group=g_strat)
dirMode       = input.string("Both", "Trade Direction",
     options=["Both", "Long Only", "Short Only"],                            group=g_strat)
cooldown      = input.int(0, "Cooldown Bars After Exit", minval=0,           group=g_strat)
useStructExit   = input.bool(true, "Structural Exit (unconfirmed pivot)",     group=g_strat,
     tooltip="Close trade when price forms a potential swing against the position (high[1] > high[0] and high[2]) before ATR target is reached.")
structMinR15M = input.float(2.0,  "  Struct Exit Min Profit 15M (R×)", minval=0.0, step=0.1, group=g_strat)
structMinR1H  = input.float(2.0,  "  Struct Exit Min Profit 1H (R×)", minval=0.0, step=0.1, group=g_strat)
structMinR4H  = input.float(2.5,  "  Struct Exit Min Profit 4H (R×)", minval=0.0, step=0.1, group=g_strat)
structMinR1D  = input.float(3.0,  "  Struct Exit Min Profit 1D (R×)", minval=0.0, step=0.1, group=g_strat)

pivLen = input.int(5, "Pivot Length", minval=2, maxval=20,                   group=g_pivot,
     tooltip="Bars on each side to confirm a swing high/low.")

// ─── Quality Gates (data-driven) ─────────────────────────────────────────────
// Inputs in alphabetical order A → I.

// A — atrRank Mid-Band Killer (per TF, per dir)
// Blocks entries when atrRank falls in a "loser-cluster" band identified from log
// analysis. Defaults derived from test108 data (NatGas baseline). Set lo == hi to
// disable the filter for that TF/dir pair.
useGateA            = input.bool(true, "GateA — ATR Rank Block Zone", group=g_quality,
     tooltip="Block entries when atrRank falls in the specified zone (per TF/dir). " +
             "Set lo == hi to disable for that TF/dir pair. " +
             "15M defaults from test141; 1H long defaults from test151 log analysis (bad bucket [40,60) atrRank, WR=14% avgR=-0.28 → block [40,65)).")
gateA_15M_long_lo   = input.float(30, "[A] 15M long  block atrRank in [",  inline="A_15M_long",  group=g_quality, step=1)
gateA_15M_long_hi   = input.float(50, ", )",                                inline="A_15M_long",  group=g_quality, step=1)
gateA_15M_short_lo  = input.float(30, "[A] 15M short block atrRank in [",  inline="A_15M_short", group=g_quality, step=1)
gateA_15M_short_hi  = input.float(50, ", )",                                inline="A_15M_short", group=g_quality, step=1)
gateA_1H_long_lo    = input.float(40, "[A] 1H  long  block atrRank in [",  inline="A_1H_long",   group=g_quality, step=1)
gateA_1H_long_hi    = input.float(65, ", )",                                inline="A_1H_long",   group=g_quality, step=1)
gateA_1H_short_lo   = input.float(0,  "[A] 1H  short block atrRank in [",  inline="A_1H_short",  group=g_quality, step=1)
gateA_1H_short_hi   = input.float(60, ", )",                                inline="A_1H_short",  group=g_quality, step=1)
gateA_4H_long_lo    = input.float(20, "[A] 4H  long  block atrRank in [",  inline="A_4H_long",   group=g_quality, step=1)
gateA_4H_long_hi    = input.float(40, ", )",                                inline="A_4H_long",   group=g_quality, step=1)
gateA_4H_short_lo   = input.float(30, "[A] 4H  short block atrRank in [",  inline="A_4H_short",  group=g_quality, step=1)
gateA_4H_short_hi   = input.float(60, ", )",                                inline="A_4H_short",  group=g_quality, step=1)
gateA_1D_long_lo    = input.float(20, "[A] 1D  long  block atrRank in [",  inline="A_1D_long",   group=g_quality, step=1)
gateA_1D_long_hi    = input.float(40, ", )",                                inline="A_1D_long",   group=g_quality, step=1)
gateA_1D_short_lo   = input.float(40, "[A] 1D  short block atrRank in [",  inline="A_1D_short",  group=g_quality, step=1)
gateA_1D_short_hi   = input.float(60, ", )",                                inline="A_1D_short",  group=g_quality, step=1)

// B — Regular Divergence Required
// Long requires a recent regular bullish divergence (price LL + WT HL).
// Short requires a recent regular bearish divergence (price HH + WT LH).
useGateB            = input.bool(false, "GateB — Regular Divergence Required", group=g_quality,
     tooltip="Long: requires a regular bullish divergence (price Lower Low + WT Higher Low) within last N bars. " +
             "Short: requires a regular bearish divergence (price Higher High + WT Lower High) within last N bars. " +
             "N is per-timeframe.")
maxDivBars15M       = input.int(30, "  Max bars since divergence 15M",  minval=1, group=g_quality)
maxDivBars1H        = input.int(20, "  Max bars since divergence 1H",   minval=1, group=g_quality)
maxDivBars4H        = input.int(10, "  Max bars since divergence 4H",   minval=1, group=g_quality)
maxDivBars1D        = input.int(5,  "  Max bars since divergence 1D",   minval=1, group=g_quality)

// C — HTF Exhaustion Filter
// Block entries when the next-higher TF's WT is already in extreme zone.
// Mapping: 15M→1H, 1H→4H, 4H→1D, 1D→1W.
useGateC            = input.bool(true, "C: HTF Exhaustion Filter", group=g_quality,
     tooltip="Block entries when higher-TF WaveTrend is already in OB/OS extreme. " +
             "Prevents 'late entry into exhausted move'. " +
             "OB threshold blocks LONG, OS threshold blocks SHORT.")
htfObLevel15M       = input.float(45,  "  15M: HTF (1H) OB block long ≥",  step=1, group=g_quality)
htfOsLevel15M       = input.float(-45, "  15M: HTF (1H) OS block short ≤", step=1, group=g_quality)
htfObLevel1H        = input.float(45,  "  1H:  HTF (4H) OB block long ≥",  step=1, group=g_quality)
htfOsLevel1H        = input.float(-45, "  1H:  HTF (4H) OS block short ≤", step=1, group=g_quality)
htfObLevel4H        = input.float(45,  "  4H:  HTF (1D) OB block long ≥",  step=1, group=g_quality)
htfOsLevel4H        = input.float(-45, "  4H:  HTF (1D) OS block short ≤", step=1, group=g_quality)
htfObLevel1D        = input.float(45,  "  1D:  HTF (1W) OB block long ≥",  step=1, group=g_quality)
htfOsLevel1D        = input.float(-45, "  1D:  HTF (1W) OS block short ≤", step=1, group=g_quality)

// D — bbWidthRank Band Killer (per TF, per dir)
// Defaults from test115: 4H long/short [60, 90); others disabled.
useGateD            = input.bool(true, "D: bbWidthRank Band Killer", group=g_quality,
     tooltip="Block entries when bbWidthRank is in the bad mid-band (per TF/dir). " +
             "Set lo == hi to disable for that TF/dir pair. " +
             "Default: 4H long/short [60, 90). 1H disabled — test145: threshold 45 blocked more winners than losers (WR 37%→26.5%).")
gateD_15M_long_lo   = input.float(0,  "15M long  block bbWidthRank in [", inline="D_15M_long",  group=g_quality, step=1)
gateD_15M_long_hi   = input.float(0,  ", )",                              inline="D_15M_long",  group=g_quality, step=1)
gateD_15M_short_lo  = input.float(0,  "15M short block bbWidthRank in [", inline="D_15M_short", group=g_quality, step=1)
gateD_15M_short_hi  = input.float(0,  ", )",                              inline="D_15M_short", group=g_quality, step=1)
gateD_1H_long_lo    = input.float(0,  "1H  long  block bbWidthRank in [", inline="D_1H_long",   group=g_quality, step=1)
gateD_1H_long_hi    = input.float(0,  ", )",                              inline="D_1H_long",   group=g_quality, step=1)
gateD_1H_short_lo   = input.float(0,  "1H  short block bbWidthRank in [", inline="D_1H_short",  group=g_quality, step=1)
gateD_1H_short_hi   = input.float(0,  ", )",                              inline="D_1H_short",  group=g_quality, step=1)
gateD_4H_long_lo    = input.float(60, "4H  long  block bbWidthRank in [", inline="D_4H_long",   group=g_quality, step=1)
gateD_4H_long_hi    = input.float(90, ", )",                              inline="D_4H_long",   group=g_quality, step=1)
gateD_4H_short_lo   = input.float(60, "4H  short block bbWidthRank in [", inline="D_4H_short",  group=g_quality, step=1)
gateD_4H_short_hi   = input.float(90, ", )",                              inline="D_4H_short",  group=g_quality, step=1)
gateD_1D_long_lo    = input.float(0,  "1D  long  block bbWidthRank in [", inline="D_1D_long",   group=g_quality, step=1)
gateD_1D_long_hi    = input.float(0,  ", )",                              inline="D_1D_long",   group=g_quality, step=1)
gateD_1D_short_lo   = input.float(0,  "1D  short block bbWidthRank in [", inline="D_1D_short",  group=g_quality, step=1)
gateD_1D_short_hi   = input.float(0,  ", )",                              inline="D_1D_short",  group=g_quality, step=1)

// E — Fail-Fast Exit (post-entry, hardcoded 1H only)
useFailFast         = input.bool(true, "E: Fail-Fast Exit (1H only)", group=g_quality,
     tooltip="Close trade if within N bars after entry the position is already X R adverse. " +
             "Cuts immediate-reversal trades fast. Active only on 1H timeframe.")
failFastBars        = input.int(1,    "  Fail-fast bars",          minval=1, maxval=5, group=g_quality)
failFastR           = input.float(-0.35, "  Fail-fast adverse R",  step=0.05, group=g_quality)

// F — No-Follow Exit (post-entry, hardcoded 1H only)
useNoFollow         = input.bool(true, "F: No-Follow Exit (1H only)", group=g_quality,
     tooltip="At bar N == window: if progressR < requiredR → exit (single check). " +
             "Hardcoded to 1H — empirically destructive on other TFs.")
nftWindow           = input.int(3,    "  Window bars",                  minval=1, group=g_quality)
nftRequiredR        = input.float(0.25, "  Required R at window end",   step=0.05, group=g_quality)

// G — Trend-aware Counter-Trend Range Filter
// Hardcoded ON for: 15M (long+short), 1H (long+short).
// Hardcoded OFF for: 4H + 1D — Gate G blockt dort echte Reversal-Winner (test117).
// Trend uses TF-specific smoothing mode (matches WT smoothing per TF).
useGateG            = input.bool(true, "G: Counter-Trend Range Filter (15M+1H only)", group=g_quality,
     tooltip="Filters only counter-trend entries on 15M and 1H. " +
             "Hardcoded OFF on 4H and 1D (verified test117). " +
             "Trend = TF-specific smoothing (fast vs slow length).")
gTrendFastLen       = input.int(20, "  Trend smoothing fast length", minval=1, group=g_quality)
gTrendSlowLen       = input.int(50, "  Trend smoothing slow length", minval=1, group=g_quality)
gRangeLen           = input.int(20, "  Local range length",     minval=2, group=g_quality)
gMaxRangePosLong    = input.float(0.30, "  Max range pos for counter-trend long",   step=0.05, minval=0.0, maxval=1.0, group=g_quality)
gMinRangePosShort   = input.float(0.70, "  Min range pos for counter-trend short",  step=0.05, minval=0.0, maxval=1.0, group=g_quality)

// H — Chop Band Killer (per TF, per dir)
// Default: 15M short [40, 50) only.
useGateH            = input.bool(true, "H: Chop Band Killer", group=g_quality,
     tooltip="Block entries when Choppiness Index is in a bad mid-band (per TF/dir). " +
             "Set lo == hi to disable for that TF/dir pair. " +
             "Defaults: 15M short [40, 50), 1H long [30, 40). " +
             "Low chop = trending market = WT reversals fail on 1H long (test163: chop<40 n=49 avgR=-0.008 vs chop>=40 avgR=+0.31).")
gateH_15M_long_lo   = input.float(0,  "15M long  block chop in [",     inline="H_15M_long",  group=g_quality, step=1)
gateH_15M_long_hi   = input.float(0,  ", )",                            inline="H_15M_long",  group=g_quality, step=1)
gateH_15M_short_lo  = input.float(40, "15M short block chop in [",     inline="H_15M_short", group=g_quality, step=1)
gateH_15M_short_hi  = input.float(50, ", )",                            inline="H_15M_short", group=g_quality, step=1)
gateH_1H_long_lo    = input.float(30, "1H  long  block chop in [",     inline="H_1H_long",   group=g_quality, step=1)
gateH_1H_long_hi    = input.float(40, ", )",                            inline="H_1H_long",   group=g_quality, step=1)
gateH_1H_short_lo   = input.float(50, "1H  short block chop in [",     inline="H_1H_short",  group=g_quality, step=1)
gateH_1H_short_hi   = input.float(60, ", )",                            inline="H_1H_short",  group=g_quality, step=1)
gateH_4H_long_lo    = input.float(30, "4H  long  block chop in [",     inline="H_4H_long",   group=g_quality, step=1)
gateH_4H_long_hi    = input.float(40, ", )",                            inline="H_4H_long",   group=g_quality, step=1)
gateH_4H_short_lo   = input.float(0,  "4H  short block chop in [",     inline="H_4H_short",  group=g_quality, step=1)
gateH_4H_short_hi   = input.float(0,  ", )",                            inline="H_4H_short",  group=g_quality, step=1)
gateH_1D_long_lo    = input.float(0,  "1D  long  block chop in [",     inline="H_1D_long",   group=g_quality, step=1)
gateH_1D_long_hi    = input.float(0,  ", )",                            inline="H_1D_long",   group=g_quality, step=1)
gateH_1D_short_lo   = input.float(0,  "1D  short block chop in [",     inline="H_1D_short",  group=g_quality, step=1)
gateH_1D_short_hi   = input.float(0,  ", )",                            inline="H_1D_short",  group=g_quality, step=1)

// I — srsi Extension Killer (per TF, per dir)
// Threshold = 0 disables. Defaults from test115: 4H long ≥ 8, 4H short ≥ 3.
useGateI            = input.bool(true, "I: srsi Extension Killer", group=g_quality,
     tooltip="Block entries when StochRSI has been stuck at OS/OB for too many bars. " +
             "Threshold = 0 disables per TF/dir.")
gateI_15M_long      = input.int(0, "15M long  block if srsiOsBars >=", minval=0, group=g_quality)
gateI_15M_short     = input.int(0, "15M short block if srsiObBars >=", minval=0, group=g_quality)
gateI_1H_long       = input.int(0, "1H  long  block if srsiOsBars >=", minval=0, group=g_quality)
gateI_1H_short      = input.int(0, "1H  short block if srsiObBars >=", minval=0, group=g_quality)
gateI_4H_long       = input.int(8, "4H  long  block if srsiOsBars >=", minval=0, group=g_quality)
gateI_4H_short      = input.int(3, "4H  short block if srsiObBars >=", minval=0, group=g_quality)
gateI_1D_long       = input.int(0, "1D  long  block if srsiOsBars >=", minval=0, group=g_quality)
gateI_1D_short      = input.int(0, "1D  short block if srsiObBars >=", minval=0, group=g_quality)

useGateB2    = input.bool(true, "B2: 1H-Confluence for 15M (HTF OS/OB required)", group=g_quality,
     tooltip="15M Long only when 1H wt1 ≤ b2OsLevel; 15M Short only when 1H wt1 ≥ b2ObLevel. " +
             "No-op on 1H/4H/1D. Stricter threshold than osLevel/obLevel — filters neutral-1H trades.")
b2OsLevel = input.float(-30.0, "  B2 1H OS threshold (long)",  step=1.0, group=g_quality,
     tooltip="1H wt1 must be ≤ this value for 15M longs. Default -20 (stricter than osLevel -5).")
b2ObLevel = input.float( 30.0, "  B2 1H OB threshold (short)", step=1.0, group=g_quality,
     tooltip="1H wt1 must be ≥ this value for 15M shorts. Default +20 (stricter than obLevel +5).")

// J — srsiD Zone Killer (per TF, per dir)
// Zone lo >= hi disables. Block when srsiD falls in [lo, hi).
// For longs: high srsiD = StochRSI not deep enough in OS = bad reversal quality.
useGateJ           = input.bool(true, "J: srsiD Zone Killer", group=g_quality,
     tooltip="Block when srsiD falls in [lo, hi). Zone lo >= hi disables per TF/dir.")
gateJ_15M_long_lo  = input.float(0,   "15M long  block srsiD in [",  inline="J_15M_long",  group=g_quality, step=1)
gateJ_15M_long_hi  = input.float(0,   ", )",                          inline="J_15M_long",  group=g_quality, step=1)
gateJ_15M_short_lo = input.float(0,   "15M short block srsiD in [",  inline="J_15M_short", group=g_quality, step=1)
gateJ_15M_short_hi = input.float(0,   ", )",                          inline="J_15M_short", group=g_quality, step=1)
gateJ_1H_long_lo   = input.float(0,   "1H  long  block srsiD in [",  inline="J_1H_long",   group=g_quality, step=1)
gateJ_1H_long_hi   = input.float(0,   ", )",                          inline="J_1H_long",   group=g_quality, step=1)
gateJ_1H_short_lo  = input.float(0,   "1H  short block srsiD in [",  inline="J_1H_short",  group=g_quality, step=1)
gateJ_1H_short_hi  = input.float(0,   ", )",                          inline="J_1H_short",  group=g_quality, step=1)
gateJ_4H_long_lo   = input.float(50,  "4H  long  block srsiD in [",  inline="J_4H_long",   group=g_quality, step=1)
gateJ_4H_long_hi   = input.float(100, ", )",                          inline="J_4H_long",   group=g_quality, step=1)
gateJ_4H_short_lo  = input.float(0,   "4H  short block srsiD in [",  inline="J_4H_short",  group=g_quality, step=1)
gateJ_4H_short_hi  = input.float(0,   ", )",                          inline="J_4H_short",  group=g_quality, step=1)
gateJ_1D_long_lo   = input.float(0,   "1D  long  block srsiD in [",  inline="J_1D_long",   group=g_quality, step=1)
gateJ_1D_long_hi   = input.float(0,   ", )",                          inline="J_1D_long",   group=g_quality, step=1)
gateJ_1D_short_lo  = input.float(0,   "1D  short block srsiD in [",  inline="J_1D_short",  group=g_quality, step=1)
gateJ_1D_short_hi  = input.float(0,   ", )",                          inline="J_1D_short",  group=g_quality, step=1)

// K — wt1 Zone Killer (per TF, per dir)
// Zone lo >= hi disables. Block when wt1 falls in [lo, hi).
// For shorts: shallow OB (low wt1) = insufficient momentum = bad reversal.
useGateK           = input.bool(true, "K: wt1 Zone Killer", group=g_quality,
     tooltip="Block when wt1 falls in [lo, hi). Zone lo >= hi disables per TF/dir.")
gateK_15M_long_lo  = input.float(0,   "15M long  block wt1 in [",  inline="K_15M_long",  group=g_quality, step=1)
gateK_15M_long_hi  = input.float(0,   ", )",                        inline="K_15M_long",  group=g_quality, step=1)
gateK_15M_short_lo = input.float(0,   "15M short block wt1 in [",  inline="K_15M_short", group=g_quality, step=1)
gateK_15M_short_hi = input.float(0,   ", )",                        inline="K_15M_short", group=g_quality, step=1)
gateK_1H_long_lo   = input.float(0,   "1H  long  block wt1 in [",  inline="K_1H_long",   group=g_quality, step=1)
gateK_1H_long_hi   = input.float(0,   ", )",                        inline="K_1H_long",   group=g_quality, step=1)
gateK_1H_short_lo  = input.float(60,  "1H  short block wt1 in [",  inline="K_1H_short",  group=g_quality, step=1)
gateK_1H_short_hi  = input.float(80,  ", )",                        inline="K_1H_short",  group=g_quality, step=1)
gateK_4H_long_lo   = input.float(0,   "4H  long  block wt1 in [",  inline="K_4H_long",   group=g_quality, step=1)
gateK_4H_long_hi   = input.float(0,   ", )",                        inline="K_4H_long",   group=g_quality, step=1)
gateK_4H_short_lo  = input.float(40,  "4H  short block wt1 in [",  inline="K_4H_short",  group=g_quality, step=1)
gateK_4H_short_hi  = input.float(50,  ", )",                        inline="K_4H_short",  group=g_quality, step=1)
gateK_1D_long_lo   = input.float(0,   "1D  long  block wt1 in [",  inline="K_1D_long",   group=g_quality, step=1)
gateK_1D_long_hi   = input.float(0,   ", )",                        inline="K_1D_long",   group=g_quality, step=1)
gateK_1D_short_lo  = input.float(0,   "1D  short block wt1 in [",  inline="K_1D_short",  group=g_quality, step=1)
gateK_1D_short_hi  = input.float(0,   ", )",                        inline="K_1D_short",  group=g_quality, step=1)

// L — Pivot Staleness Killer (per TF, per dir)
// Zone lo >= hi disables. For longs: block when highPivBars in [lo, hi).
// For shorts: block when lowPivBars in [lo, hi).
// Rationale: if opposite-direction pivot is too far away, structure is absent.
useGateL           = input.bool(true, "L: Pivot Staleness Killer", group=g_quality,
     tooltip="Longs: block when highPivBars in [lo,hi). Shorts: block when lowPivBars in [lo,hi). lo>=hi disables.")
gateL_15M_long_lo  = input.float(20,  "15M long  block highPivBars in [",  inline="L_15M_long",  group=g_quality, step=1)
gateL_15M_long_hi  = input.float(30,  ", )",                               inline="L_15M_long",  group=g_quality, step=1)
gateL_15M_short_lo = input.float(0,   "15M short block lowPivBars in [",   inline="L_15M_short", group=g_quality, step=1)
gateL_15M_short_hi = input.float(0,   ", )",                               inline="L_15M_short", group=g_quality, step=1)
gateL_1H_long_lo   = input.float(0,   "1H  long  block highPivBars in [",  inline="L_1H_long",   group=g_quality, step=1)
gateL_1H_long_hi   = input.float(0,   ", )",                               inline="L_1H_long",   group=g_quality, step=1)
gateL_1H_short_lo  = input.float(0,   "1H  short block lowPivBars in [",   inline="L_1H_short",  group=g_quality, step=1)
gateL_1H_short_hi  = input.float(0,   ", )",                               inline="L_1H_short",  group=g_quality, step=1)
gateL_4H_long_lo   = input.float(0,   "4H  long  block highPivBars in [",  inline="L_4H_long",   group=g_quality, step=1)
gateL_4H_long_hi   = input.float(0,   ", )",                               inline="L_4H_long",   group=g_quality, step=1)
gateL_4H_short_lo  = input.float(0,   "4H  short block lowPivBars in [",   inline="L_4H_short",  group=g_quality, step=1)
gateL_4H_short_hi  = input.float(0,   ", )",                               inline="L_4H_short",  group=g_quality, step=1)
gateL_1D_long_lo   = input.float(0,   "1D  long  block highPivBars in [",  inline="L_1D_long",   group=g_quality, step=1)
gateL_1D_long_hi   = input.float(0,   ", )",                               inline="L_1D_long",   group=g_quality, step=1)
gateL_1D_short_lo  = input.float(0,   "1D  short block lowPivBars in [",   inline="L_1D_short",  group=g_quality, step=1)
gateL_1D_short_hi  = input.float(0,   ", )",                               inline="L_1D_short",  group=g_quality, step=1)

// M — srsiK Zone Killer (per TF, per dir)
// Zone lo >= hi disables. Block when srsiK falls in [lo, hi).
useGateM           = input.bool(true, "M: srsiK Zone Killer", group=g_quality,
     tooltip="Block when srsiK falls in [lo, hi). Zone lo >= hi disables per TF/dir.")
gateM_15M_long_lo  = input.float(0,   "15M long  block srsiK in [",  inline="M_15M_long",  group=g_quality, step=1)
gateM_15M_long_hi  = input.float(0,   ", )",                          inline="M_15M_long",  group=g_quality, step=1)
gateM_15M_short_lo = input.float(0,   "15M short block srsiK in [",  inline="M_15M_short", group=g_quality, step=1)
gateM_15M_short_hi = input.float(0,   ", )",                          inline="M_15M_short", group=g_quality, step=1)
gateM_1H_long_lo   = input.float(0,   "1H  long  block srsiK in [",  inline="M_1H_long",   group=g_quality, step=1)
gateM_1H_long_hi   = input.float(0,   ", )",                          inline="M_1H_long",   group=g_quality, step=1)
gateM_1H_short_lo  = input.float(70,  "1H  short block srsiK in [",  inline="M_1H_short",  group=g_quality, step=1)
gateM_1H_short_hi  = input.float(80,  ", )",                          inline="M_1H_short",  group=g_quality, step=1)
gateM_4H_long_lo   = input.float(0,   "4H  long  block srsiK in [",  inline="M_4H_long",   group=g_quality, step=1)
gateM_4H_long_hi   = input.float(0,   ", )",                          inline="M_4H_long",   group=g_quality, step=1)
gateM_4H_short_lo  = input.float(0,   "4H  short block srsiK in [",  inline="M_4H_short",  group=g_quality, step=1)
gateM_4H_short_hi  = input.float(0,   ", )",                          inline="M_4H_short",  group=g_quality, step=1)
gateM_1D_long_lo   = input.float(0,   "1D  long  block srsiK in [",  inline="M_1D_long",   group=g_quality, step=1)
gateM_1D_long_hi   = input.float(0,   ", )",                          inline="M_1D_long",   group=g_quality, step=1)
gateM_1D_short_lo  = input.float(0,   "1D  short block srsiK in [",  inline="M_1D_short",  group=g_quality, step=1)
gateM_1D_short_hi  = input.float(0,   ", )",                          inline="M_1D_short",  group=g_quality, step=1)

useSession    = input.bool(false,  "Session Filter",                         group=g_filters)
sessionInput  = input.session("0000-2345", "Session",                        group=g_filters)
useDateRange  = input.bool(false,  "Date Range",                             group=g_filters)
dateFrom      = input.time(timestamp("01 Jan 2020 00:00 +0000"), "From",     group=g_filters)
dateTo        = input.time(timestamp("31 Dec 2030 23:59 +0000"), "To",       group=g_filters)
useMaxDD      = input.bool(false,  "Max Drawdown",                           group=g_filters)
maxDD         = input.float(20.0,  "Max Drawdown (%)", minval=1, step=1,     group=g_filters)
useMaxIntraDD = input.bool(false,  "Max Intraday Loss",                      group=g_filters)
maxIntraDD    = input.float(5.0,   "Max Intraday Loss (%)", minval=1, step=1, group=g_filters)
useLossStreak = input.bool(false,  "Losing Streak Limit",                    group=g_filters)
maxLossStreak = input.int(5,       "Max Consecutive Losses", minval=1,       group=g_filters)
useAtrRankGate = input.bool(false, "Use ATR Rank Gate",                        group=g_filters)
atrPctLen     = input.int(100,     "  ATR Percentile Length",    minval=10,  group=g_filters)
minAtrRank15M = input.float(35,   "  Min ATR Rank 15M",          step=1,     group=g_filters)
maxAtrRank15M = input.float(85,   "  Max ATR Rank 15M",          step=1,     group=g_filters)
minAtrRank1H  = input.float(35,   "  Min ATR Rank 1H",           step=1,     group=g_filters)
maxAtrRank1H  = input.float(85,   "  Max ATR Rank 1H",           step=1,     group=g_filters)
minAtrRank4H  = input.float(30,   "  Min ATR Rank 4H",           step=1,     group=g_filters)
maxAtrRank4H  = input.float(90,   "  Max ATR Rank 4H",           step=1,     group=g_filters)
minAtrRank1D  = input.float(20,   "  Min ATR Rank 1D",           step=1,     group=g_filters)
maxAtrRank1D  = input.float(95,   "  Max ATR Rank 1D",           step=1,     group=g_filters)
useBBExpGate  = input.bool(false, "Use BB Expansion Gate",                    group=g_filters)
bbLen         = input.int(20,     "  BB Width Length",           minval=1,   group=g_filters)
bbMult        = input.float(2.0,  "  BB Mult",                   step=0.1,   group=g_filters)
useChopGate   = input.bool(false, "Use Chop Gate",                            group=g_filters)
chopLen       = input.int(14,     "  Chop Length",               minval=1,   group=g_filters)
maxChop       = input.float(58,   "  Max Chop",                  step=0.5,   group=g_filters)
usePivotProx  = input.bool(false, "Use Pivot Proximity",                     group=g_filters)
maxPivBars15M = input.int(15,     "  Max Bars From Pivot 15M",  minval=1,   group=g_filters)
maxPivBars1H  = input.int(12,     "  Max Bars From Pivot 1H",   minval=1,   group=g_filters)
maxPivBars4H  = input.int(9,      "  Max Bars From Pivot 4H",   minval=1,   group=g_filters)
maxPivBars1D  = input.int(5,      "  Max Bars From Pivot 1D",   minval=1,   group=g_filters)
maxDistAtr    = input.float(1.2,  "Max Distance From Pivot (ATR×)", step=0.1, group=g_filters)
useSweepGate          = input.bool(false, "Use Liquidity Sweep Gate",          group=g_filters)
sweepLookback15M      = input.int(10,  "  Sweep Lookback 15M", minval=1,     group=g_filters)
sweepLookback1H       = input.int(8,   "  Sweep Lookback 1H",  minval=1,      group=g_filters)
sweepLookback4H       = input.int(6,   "  Sweep Lookback 4H",  minval=1,      group=g_filters)
sweepLookback1D       = input.int(4,   "  Sweep Lookback 1D",  minval=1,      group=g_filters)
sweepValidBars15M     = input.int(4,   "  Sweep Valid Bars 15M", minval=1,    group=g_filters)
sweepValidBars1H      = input.int(5,   "  Sweep Valid Bars 1H", minval=1,     group=g_filters)
sweepValidBars4H      = input.int(8,   "  Sweep Valid Bars 4H", minval=1,     group=g_filters)
sweepValidBars1D      = input.int(12,  "  Sweep Valid Bars 1D", minval=1,     group=g_filters)
useSweepAsHardGate1D  = input.bool(false, "  Daily Sweep as Hard Gate",       group=g_filters)

showDiv     = input.bool(true,  "Show Divergences",   group=g_div)
divSrc      = input.string("Wave Trend", "Source",    group=g_div, options=["Wave Trend", "Signal", "Histogram"])
divLbR      = input.int(5,  "Pivot Lookback Right", minval=1, group=g_div)
divLbL      = input.int(5,  "Pivot Lookback Left",  minval=1, group=g_div)
divEqTol    = input.float(2.0, "Equal Peak/Trough Tolerance (WT units)", minval=0.0, step=0.5, group=g_div,
     tooltip="Erlaubt equal highs/lows als Divergenz. 0 = strikt, 2 = bis zu 2 WT-Einheiten Abweichung zulässig.")
plotBull    = input.bool(true,  "Bullish",        inline="div_bull", group=g_div)
plotHidBull = input.bool(false, "Hidden Bullish", inline="div_bull", group=g_div)
plotBear    = input.bool(true,  "Bearish",        inline="div_bear", group=g_div)
plotHidBear = input.bool(false, "Hidden Bearish", inline="div_bear", group=g_div)
showDivPivots = input.bool(false, "Debug: alle Osc-Pivots anzeigen", group=g_div,
     tooltip="Zeigt graue Punkte an jedem erkannten WT-Oszillator-Pivot. Damit sieht man ob _divPhFound feuert.")

// ─── Risk limits (fire-and-forget, built-in strategy.risk) ───────────────────
strategy.risk.max_drawdown(useMaxDD      ? maxDD      : 100, strategy.percent_of_equity)
strategy.risk.max_intraday_loss(useMaxIntraDD ? maxIntraDD : 100, strategy.percent_of_equity)

// ─── WaveTrend ───────────────────────────────────────────────────────────────
f_jma(src, length, phase, power) =>
    var float jma = na
    var float e0  = na
    var float e1  = na
    var float e2  = na
    _pr    = phase < -100 ? 0.5 : phase > 100 ? 2.5 : phase / 100.0 + 1.5
    _beta  = 0.45 * (length - 1) / (0.45 * (length - 1) + 2)
    _alpha = math.pow(_beta, power)
    e0    := (1 - _alpha) * src + _alpha * nz(e0[1], src)
    e1    := (src - e0) * (1 - _beta) + _beta * nz(e1[1], 0)
    e2    := (e0 + _pr * e1 - nz(jma[1], src)) * math.pow(1 - _alpha, 2) + math.pow(_alpha, 2) * nz(e2[1], 0)
    jma   := nz(jma[1], src) + e2
    jma

f_smooth(src, length, mode) =>
    switch mode
        'SMA' => ta.sma(src, length)
        'RMA' => ta.rma(src, length)
        'HMA' => ta.hma(src, length)
        'WMA' => ta.wma(src, length)
        'JMA' => f_jma(src, length, jmaPhase, jmaPower)
        =>       ta.ema(src, length)

_smoothMode = timeframe.in_seconds() == 900 ? smooth15M : timeframe.in_seconds() == 3600 ? smooth1H : timeframe.in_seconds() == 14400 ? smooth4H : timeframe.in_seconds() == 86400 ? smooth1D : 'EMA'
_esa        = ta.ema(hlc3, chLen)
_d          = ta.ema(math.abs(hlc3 - _esa), chLen)
_ci         = _d != 0 ? (hlc3 - _esa) / (0.015 * _d) : 0.0
wt1         = f_smooth(_ci, avgLen, _smoothMode)
wt2         = ta.sma(wt1, sigLen)

// ─── Stochastic RSI ──────────────────────────────────────────────────────────
_rsi      = ta.rsi(close, srsiRsiLen)
_stoch    = ta.stoch(_rsi, _rsi, _rsi, srsiStochLen)
srsiK     = ta.sma(_stoch, srsiKSmooth)
srsiD     = ta.sma(srsiK,  srsiDSmooth)

var int _srsiOsBars = 0
var int _srsiObBars = 0
_srsiOsBars := srsiK <= srsiOsLevel ? _srsiOsBars + 1 : 0
_srsiObBars := srsiK >= srsiObLevel ? _srsiObBars + 1 : 0

srsiLongOk  = not useSrsiGate or (srsiK > srsiD and _srsiOsBars <= srsiPersist)
srsiShortOk = not useSrsiGate or (srsiK < srsiD and _srsiObBars <= srsiPersist)

// ─── Pivots ───────────────────────────────────────────────────────────────────
_phVal   = ta.pivothigh(high, pivLen, pivLen)
_plVal   = ta.pivotlow(low,  pivLen, pivLen)
_phFound = not na(_phVal)
_plFound = not na(_plVal)

var int _lastHighBar = na
var int _lastLowBar  = na
if _phFound
    _lastHighBar := bar_index - pivLen
if _plFound
    _lastLowBar  := bar_index - pivLen

lastPivHigh = ta.valuewhen(_phFound, high[pivLen], 0)
lastPivLow  = ta.valuewhen(_plFound, low[pivLen],  0)

barsSinceHighPiv = not na(_lastHighBar) ? bar_index - _lastHighBar : na
barsSinceLowPiv  = not na(_lastLowBar)  ? bar_index - _lastLowBar  : na

// ─── Regular Divergence detection (uses WT value at pivot bars) ──────────────
// Bullish divergence: price makes Lower Low while WT makes Higher Low → momentum
// weakens as price falls → reversal-up setup.
// Bearish divergence: price makes Higher High while WT makes Lower High → momentum
// weakens as price rises → reversal-down setup.
wtAtLastLow   = ta.valuewhen(_plFound, wt1[pivLen],  0)
wtAtPrevLow   = ta.valuewhen(_plFound, wt1[pivLen],  1)
priceAtPrevLow  = ta.valuewhen(_plFound, low[pivLen], 1)

wtAtLastHigh  = ta.valuewhen(_phFound, wt1[pivLen], 0)
wtAtPrevHigh  = ta.valuewhen(_phFound, wt1[pivLen], 1)
priceAtPrevHigh = ta.valuewhen(_phFound, high[pivLen], 1)

regularBullDiv = _plFound and not na(wtAtPrevLow)  and not na(priceAtPrevLow)  and wtAtLastLow  > wtAtPrevLow  and lastPivLow  < priceAtPrevLow
regularBearDiv = _phFound and not na(wtAtPrevHigh) and not na(priceAtPrevHigh) and wtAtLastHigh < wtAtPrevHigh and lastPivHigh > priceAtPrevHigh

barsSinceBullDiv = ta.barssince(regularBullDiv)
barsSinceBearDiv = ta.barssince(regularBearDiv)

// ─── Divergence visualization (oscillator-pivot based) ───────────────────────
// Custom pivot detection: right side strict > (peak confirmed), left side >= (flat
// tops allowed). ta.pivothigh requires strict > on both sides and misses flat-topped
// WT peaks that are common on slower timeframes like 4H.
wt3_hist    = wt1 - wt2
_divOsc     = not showDiv ? na : divSrc == "Signal" ? wt2 : divSrc == "Histogram" ? wt3_hist : wt1
_oscRightHi = ta.highest(_divOsc, divLbR)
_oscLeftHi  = ta.highest(_divOsc, divLbL)[divLbR + 1]
_oscRightLo = ta.lowest(_divOsc,  divLbR)
_oscLeftLo  = ta.lowest(_divOsc,  divLbL)[divLbR + 1]
_divPhFound = not na(_divOsc[divLbR]) and _divOsc[divLbR] > _oscRightHi and _divOsc[divLbR] >= _oscLeftHi
_divPlFound = not na(_divOsc[divLbR]) and _divOsc[divLbR] < _oscRightLo and _divOsc[divLbR] <= _oscLeftLo

// Wide price window to account for WT lag: on 4H, WT peaks ~15 bars after price peaks.
// We need to look further left than just the WT pivot detection window.
// divLbR + divLbL covers the oscillator window; +20 covers typical WT lag.
// Clamped to the bars since the previous oscillator pivot of the same type, so two
// consecutive pivots never share price bars (overlapping windows could return the
// same high/low for both and hide or fake a HH/LL).
_divWinMax  = divLbR + divLbL + 20
_divPhGap   = nz(ta.barssince(_divPhFound)[1] + 1, _divWinMax)
_divPlGap   = nz(ta.barssince(_divPlFound)[1] + 1, _divWinMax)
_divWinHi   = ta.highest(high, math.min(_divWinMax, _divPhGap))
_divWinLo   = ta.lowest(low,   math.min(_divWinMax, _divPlGap))

_divOscLH   = _divOsc[divLbR] < ta.valuewhen(_divPhFound, _divOsc[divLbR], 1) + divEqTol
_divPriceHH = _divWinHi > ta.valuewhen(_divPhFound, _divWinHi, 1)
bearDivCond = showDiv and plotBear    and _divPhFound and not na(ta.valuewhen(_divPhFound, _divOsc[divLbR], 1)) and _divPriceHH and _divOscLH

_divOscHL   = _divOsc[divLbR] > ta.valuewhen(_divPlFound, _divOsc[divLbR], 1) - divEqTol
_divPriceLL = _divWinLo < ta.valuewhen(_divPlFound, _divWinLo, 1)
bullDivCond = showDiv and plotBull    and _divPlFound and not na(ta.valuewhen(_divPlFound, _divOsc[divLbR], 1)) and _divPriceLL and _divOscHL

_divOscHH   = _divOsc[divLbR] > ta.valuewhen(_divPhFound, _divOsc[divLbR], 1)
_divPriceLH = _divWinHi < ta.valuewhen(_divPhFound, _divWinHi, 1)
hidBearDivCond = showDiv and plotHidBear and _divPhFound and not na(ta.valuewhen(_divPhFound, _divOsc[divLbR], 1)) and _divPriceLH and _divOscHH

_divOscLL   = _divOsc[divLbR] < ta.valuewhen(_divPlFound, _divOsc[divLbR], 1)
_divPriceHL = _divWinLo > ta.valuewhen(_divPlFound, _divWinLo, 1)
hidBullDivCond = showDiv and plotHidBull and _divPlFound and not na(ta.valuewhen(_divPlFound, _divOsc[divLbR], 1)) and _divPriceHL and _divOscLL

// ─── ATR ─────────────────────────────────────────────────────────────────────
atrVal  = ta.atr(atrLen)
atrRank = ta.percentrank(atrVal, atrPctLen)

// ─── Choppiness Index ────────────────────────────────────────────────────────
_trSum     = math.sum(ta.tr(true), chopLen)
_chopRange = ta.highest(high, chopLen) - ta.lowest(low, chopLen)
chop       = _chopRange > 0 ? 100 * math.log10(_trSum / _chopRange) / math.log10(chopLen) : 100.0
chopGate   = not useChopGate or chop < maxChop

// ─── Bollinger Band width expansion ──────────────────────────────────────────
bbBasis             = ta.sma(close, bbLen)
bbDev               = bbMult * ta.stdev(close, bbLen)
bbWidth             = math.abs(bbBasis) > syminfo.mintick ? (2.0 * bbDev) / math.abs(bbBasis) : na
bbWidthRank         = ta.percentrank(bbWidth, 100)
compressionRecently = ta.lowest(bbWidthRank, 20) < 20
expandingNow        = bbWidthRank > bbWidthRank[1] and bbWidthRank > 30
volExpansionGate    = not useBBExpGate or (compressionRecently and expandingNow)

// ─── Filters ─────────────────────────────────────────────────────────────────
_inSession   = not useSession  or not na(time(timeframe.period, sessionInput))
_inDateRange = not useDateRange or (time >= dateFrom and time <= dateTo)

var int  _lossStreak    = 0
var bool _streakBlocked = false
_newLoss = strategy.losstrades > strategy.losstrades[1]
_newWin  = strategy.wintrades  > strategy.wintrades[1]
if _newLoss
    _lossStreak := _lossStreak + 1
if _newWin
    _lossStreak := 0
_streakBlocked := useLossStreak and _lossStreak >= maxLossStreak

_tfSec       = timeframe.in_seconds()
_tfOk        = _tfSec == 900 or _tfSec == 3600 or _tfSec == 14400 or _tfSec == 86400
_tpMult      = _tfSec == 900 ? tpMult15M    : _tfSec == 3600 ? tpMult1H    : _tfSec == 14400 ? tpMult4H    : tpMult1D
_osLevel     = _tfSec == 900 ? osLevel15M   : _tfSec == 3600 ? osLevel1H   : _tfSec == 14400 ? osLevel4H   : osLevel1D
_obLevel     = _tfSec == 900 ? obLevel15M   : _tfSec == 3600 ? obLevel1H   : _tfSec == 14400 ? obLevel4H   : obLevel1D
_osFloor     = _tfSec == 14400 ? osFloor4H : _tfSec == 86400 ? osFloor1D : -999.0
_obFloor     = _tfSec == 14400 ? obFloor4H : _tfSec == 86400 ? obFloor1D : +999.0
_minAtrRank  = _tfSec == 900 ? minAtrRank15M : _tfSec == 3600 ? minAtrRank1H : _tfSec == 14400 ? minAtrRank4H : minAtrRank1D
_maxAtrRank  = _tfSec == 900 ? maxAtrRank15M : _tfSec == 3600 ? maxAtrRank1H : _tfSec == 14400 ? maxAtrRank4H : maxAtrRank1D
_volOk       = not useAtrRankGate or (atrRank >= _minAtrRank and atrRank <= _maxAtrRank)

_canTrade = _inSession and _inDateRange and not _streakBlocked and _tfOk and _volOk and volExpansionGate and chopGate

_maxPivBars    = _tfSec == 900 ? maxPivBars15M : _tfSec == 3600 ? maxPivBars1H : _tfSec == 14400 ? maxPivBars4H : maxPivBars1D
pivotLongGate  = not usePivotProx or (not na(barsSinceLowPiv)  and barsSinceLowPiv  <= _maxPivBars)
pivotShortGate = not usePivotProx or (not na(barsSinceHighPiv) and barsSinceHighPiv <= _maxPivBars)

distFromLowAtr     = not na(lastPivLow)  ? math.abs(close - lastPivLow)  / atrVal : na
distFromHighAtr    = not na(lastPivHigh) ? math.abs(close - lastPivHigh) / atrVal : na
pivotDistLongGate  = not usePivotProx or (not na(distFromLowAtr)  and distFromLowAtr  <= maxDistAtr)
pivotDistShortGate = not usePivotProx or (not na(distFromHighAtr) and distFromHighAtr <= maxDistAtr)

// ─── Liquidity Sweep Gate ────────────────────────────────────────────────────
_sweepLookback  = _tfSec == 900 ? sweepLookback15M  : _tfSec == 3600 ? sweepLookback1H  : _tfSec == 14400 ? sweepLookback4H  : sweepLookback1D
_sweepValidBars = _tfSec == 900 ? sweepValidBars15M : _tfSec == 3600 ? sweepValidBars1H : _tfSec == 14400 ? sweepValidBars4H : sweepValidBars1D

rawSweptLow  = not na(lastPivLow)  and ta.lowest(low,  _sweepLookback) < lastPivLow  and close > lastPivLow
rawSweptHigh = not na(lastPivHigh) and ta.highest(high, _sweepLookback) > lastPivHigh and close < lastPivHigh

sweepLongContext  = not na(ta.barssince(rawSweptLow))  and ta.barssince(rawSweptLow)  <= _sweepValidBars
sweepShortContext = not na(ta.barssince(rawSweptHigh)) and ta.barssince(rawSweptHigh) <= _sweepValidBars

_dailyBypass = _tfSec == 86400 and not useSweepAsHardGate1D
sweepLongGate  = not useSweepGate or _dailyBypass or sweepLongContext
sweepShortGate = not useSweepGate or _dailyBypass or sweepShortContext

// ─── Gate A — atrRank Mid-Band Killer (per TF, per dir) ──────────────────────
_gateA_long_lo  = _tfSec == 900 ? gateA_15M_long_lo  : _tfSec == 3600 ? gateA_1H_long_lo  : _tfSec == 14400 ? gateA_4H_long_lo  : gateA_1D_long_lo
_gateA_long_hi  = _tfSec == 900 ? gateA_15M_long_hi  : _tfSec == 3600 ? gateA_1H_long_hi  : _tfSec == 14400 ? gateA_4H_long_hi  : gateA_1D_long_hi
_gateA_short_lo = _tfSec == 900 ? gateA_15M_short_lo : _tfSec == 3600 ? gateA_1H_short_lo : _tfSec == 14400 ? gateA_4H_short_lo : gateA_1D_short_lo
_gateA_short_hi = _tfSec == 900 ? gateA_15M_short_hi : _tfSec == 3600 ? gateA_1H_short_hi : _tfSec == 14400 ? gateA_4H_short_hi : gateA_1D_short_hi

// Trade is OK (passes gate) if: gate disabled, atrRank is na, lo==hi (disabled per TF),
// or atrRank is OUTSIDE the bad band [lo, hi).
gateALongOk  = not useGateA or na(atrRank) or _gateA_long_lo  >= _gateA_long_hi  or atrRank < _gateA_long_lo  or atrRank >= _gateA_long_hi
gateAShortOk = not useGateA or na(atrRank) or _gateA_short_lo >= _gateA_short_hi or atrRank < _gateA_short_lo or atrRank >= _gateA_short_hi

// ─── Gate D — bbWidthRank Band Killer (per TF, per dir) ──────────────────────
_gateD_long_lo  = _tfSec == 900 ? gateD_15M_long_lo  : _tfSec == 3600 ? gateD_1H_long_lo  : _tfSec == 14400 ? gateD_4H_long_lo  : gateD_1D_long_lo
_gateD_long_hi  = _tfSec == 900 ? gateD_15M_long_hi  : _tfSec == 3600 ? gateD_1H_long_hi  : _tfSec == 14400 ? gateD_4H_long_hi  : gateD_1D_long_hi
_gateD_short_lo = _tfSec == 900 ? gateD_15M_short_lo : _tfSec == 3600 ? gateD_1H_short_lo : _tfSec == 14400 ? gateD_4H_short_lo : gateD_1D_short_lo
_gateD_short_hi = _tfSec == 900 ? gateD_15M_short_hi : _tfSec == 3600 ? gateD_1H_short_hi : _tfSec == 14400 ? gateD_4H_short_hi : gateD_1D_short_hi

gateDLongOk  = not useGateD or na(bbWidthRank) or _gateD_long_lo  >= _gateD_long_hi  or bbWidthRank < _gateD_long_lo  or bbWidthRank >= _gateD_long_hi
gateDShortOk = not useGateD or na(bbWidthRank) or _gateD_short_lo >= _gateD_short_hi or bbWidthRank < _gateD_short_lo or bbWidthRank >= _gateD_short_hi

// ─── Gate H — Chop Band Killer (per TF, per dir) ─────────────────────────────
_gateH_long_lo  = _tfSec == 900 ? gateH_15M_long_lo  : _tfSec == 3600 ? gateH_1H_long_lo  : _tfSec == 14400 ? gateH_4H_long_lo  : gateH_1D_long_lo
_gateH_long_hi  = _tfSec == 900 ? gateH_15M_long_hi  : _tfSec == 3600 ? gateH_1H_long_hi  : _tfSec == 14400 ? gateH_4H_long_hi  : gateH_1D_long_hi
_gateH_short_lo = _tfSec == 900 ? gateH_15M_short_lo : _tfSec == 3600 ? gateH_1H_short_lo : _tfSec == 14400 ? gateH_4H_short_lo : gateH_1D_short_lo
_gateH_short_hi = _tfSec == 900 ? gateH_15M_short_hi : _tfSec == 3600 ? gateH_1H_short_hi : _tfSec == 14400 ? gateH_4H_short_hi : gateH_1D_short_hi

gateHLongOk  = not useGateH or na(chop) or _gateH_long_lo  >= _gateH_long_hi  or chop < _gateH_long_lo  or chop >= _gateH_long_hi
gateHShortOk = not useGateH or na(chop) or _gateH_short_lo >= _gateH_short_hi or chop < _gateH_short_lo or chop >= _gateH_short_hi

// ─── Gate I — srsi Extension Killer (per TF, per dir) ────────────────────────
_gateI_long_thresh  = _tfSec == 900 ? gateI_15M_long  : _tfSec == 3600 ? gateI_1H_long  : _tfSec == 14400 ? gateI_4H_long  : gateI_1D_long
_gateI_short_thresh = _tfSec == 900 ? gateI_15M_short : _tfSec == 3600 ? gateI_1H_short : _tfSec == 14400 ? gateI_4H_short : gateI_1D_short

gateILongOk  = not useGateI or _gateI_long_thresh  == 0 or _srsiOsBars < _gateI_long_thresh
gateIShortOk = not useGateI or _gateI_short_thresh == 0 or _srsiObBars < _gateI_short_thresh

// ─── Gate J — srsiD Zone Killer (per TF, per dir) ────────────────────────────
_gateJ_long_lo  = _tfSec == 900 ? gateJ_15M_long_lo  : _tfSec == 3600 ? gateJ_1H_long_lo  : _tfSec == 14400 ? gateJ_4H_long_lo  : gateJ_1D_long_lo
_gateJ_long_hi  = _tfSec == 900 ? gateJ_15M_long_hi  : _tfSec == 3600 ? gateJ_1H_long_hi  : _tfSec == 14400 ? gateJ_4H_long_hi  : gateJ_1D_long_hi
_gateJ_short_lo = _tfSec == 900 ? gateJ_15M_short_lo : _tfSec == 3600 ? gateJ_1H_short_lo : _tfSec == 14400 ? gateJ_4H_short_lo : gateJ_1D_short_lo
_gateJ_short_hi = _tfSec == 900 ? gateJ_15M_short_hi : _tfSec == 3600 ? gateJ_1H_short_hi : _tfSec == 14400 ? gateJ_4H_short_hi : gateJ_1D_short_hi

gateJLongOk  = not useGateJ or na(srsiD) or _gateJ_long_lo  >= _gateJ_long_hi  or srsiD < _gateJ_long_lo  or srsiD >= _gateJ_long_hi
gateJShortOk = not useGateJ or na(srsiD) or _gateJ_short_lo >= _gateJ_short_hi or srsiD < _gateJ_short_lo or srsiD >= _gateJ_short_hi

// ─── Gate K — wt1 Zone Killer (per TF, per dir) ──────────────────────────────
_gateK_long_lo  = _tfSec == 900 ? gateK_15M_long_lo  : _tfSec == 3600 ? gateK_1H_long_lo  : _tfSec == 14400 ? gateK_4H_long_lo  : gateK_1D_long_lo
_gateK_long_hi  = _tfSec == 900 ? gateK_15M_long_hi  : _tfSec == 3600 ? gateK_1H_long_hi  : _tfSec == 14400 ? gateK_4H_long_hi  : gateK_1D_long_hi
_gateK_short_lo = _tfSec == 900 ? gateK_15M_short_lo : _tfSec == 3600 ? gateK_1H_short_lo : _tfSec == 14400 ? gateK_4H_short_lo : gateK_1D_short_lo
_gateK_short_hi = _tfSec == 900 ? gateK_15M_short_hi : _tfSec == 3600 ? gateK_1H_short_hi : _tfSec == 14400 ? gateK_4H_short_hi : gateK_1D_short_hi

gateKLongOk  = not useGateK or _gateK_long_lo  >= _gateK_long_hi  or wt1 < _gateK_long_lo  or wt1 >= _gateK_long_hi
gateKShortOk = not useGateK or _gateK_short_lo >= _gateK_short_hi or wt1 < _gateK_short_lo or wt1 >= _gateK_short_hi

// ─── Gate L — Pivot Staleness Killer (per TF, per dir) ───────────────────────
_gateL_long_lo  = _tfSec == 900 ? gateL_15M_long_lo  : _tfSec == 3600 ? gateL_1H_long_lo  : _tfSec == 14400 ? gateL_4H_long_lo  : gateL_1D_long_lo
_gateL_long_hi  = _tfSec == 900 ? gateL_15M_long_hi  : _tfSec == 3600 ? gateL_1H_long_hi  : _tfSec == 14400 ? gateL_4H_long_hi  : gateL_1D_long_hi
_gateL_short_lo = _tfSec == 900 ? gateL_15M_short_lo : _tfSec == 3600 ? gateL_1H_short_lo : _tfSec == 14400 ? gateL_4H_short_lo : gateL_1D_short_lo
_gateL_short_hi = _tfSec == 900 ? gateL_15M_short_hi : _tfSec == 3600 ? gateL_1H_short_hi : _tfSec == 14400 ? gateL_4H_short_hi : gateL_1D_short_hi

gateLLongOk  = not useGateL or na(barsSinceHighPiv) or _gateL_long_lo  >= _gateL_long_hi  or barsSinceHighPiv < _gateL_long_lo  or barsSinceHighPiv >= _gateL_long_hi
gateLShortOk = not useGateL or na(barsSinceLowPiv)  or _gateL_short_lo >= _gateL_short_hi or barsSinceLowPiv  < _gateL_short_lo or barsSinceLowPiv  >= _gateL_short_hi

// ─── Gate M — srsiK Zone Killer (per TF, per dir) ────────────────────────────
_gateM_long_lo  = _tfSec == 900 ? gateM_15M_long_lo  : _tfSec == 3600 ? gateM_1H_long_lo  : _tfSec == 14400 ? gateM_4H_long_lo  : gateM_1D_long_lo
_gateM_long_hi  = _tfSec == 900 ? gateM_15M_long_hi  : _tfSec == 3600 ? gateM_1H_long_hi  : _tfSec == 14400 ? gateM_4H_long_hi  : gateM_1D_long_hi
_gateM_short_lo = _tfSec == 900 ? gateM_15M_short_lo : _tfSec == 3600 ? gateM_1H_short_lo : _tfSec == 14400 ? gateM_4H_short_lo : gateM_1D_short_lo
_gateM_short_hi = _tfSec == 900 ? gateM_15M_short_hi : _tfSec == 3600 ? gateM_1H_short_hi : _tfSec == 14400 ? gateM_4H_short_hi : gateM_1D_short_hi

gateMlongOk  = not useGateM or na(srsiK) or _gateM_long_lo  >= _gateM_long_hi  or srsiK < _gateM_long_lo  or srsiK >= _gateM_long_hi
gateMShortOk = not useGateM or na(srsiK) or _gateM_short_lo >= _gateM_short_hi or srsiK < _gateM_short_lo or srsiK >= _gateM_short_hi

// ─── Gate B — Regular Divergence Required (per TF) ───────────────────────────
_maxDivBars = _tfSec == 900 ? maxDivBars15M : _tfSec == 3600 ? maxDivBars1H : _tfSec == 14400 ? maxDivBars4H : maxDivBars1D
gateBLongOk  = not useGateB or (not na(barsSinceBullDiv) and barsSinceBullDiv <= _maxDivBars)
gateBShortOk = not useGateB or (not na(barsSinceBearDiv) and barsSinceBearDiv <= _maxDivBars)

// ─── Gate C — HTF Exhaustion Filter ──────────────────────────────────────────
// Map current TF to one-step-up HTF
_htfPeriod = _tfSec == 900 ? "60" : _tfSec == 3600 ? "240" : _tfSec == 14400 ? "D" : _tfSec == 86400 ? "W" : "60"
// Fetch HTF wt1. wt1's smoothing depends on timeframe.in_seconds() so it auto-resolves
// to the HTF's smoothing mode when evaluated in HTF context.
htfWt1 = request.security(syminfo.tickerid, _htfPeriod, wt1, barmerge.gaps_off, barmerge.lookahead_off)

_htfObLvl = _tfSec == 900 ? htfObLevel15M : _tfSec == 3600 ? htfObLevel1H : _tfSec == 14400 ? htfObLevel4H : htfObLevel1D
_htfOsLvl = _tfSec == 900 ? htfOsLevel15M : _tfSec == 3600 ? htfOsLevel1H : _tfSec == 14400 ? htfOsLevel4H : htfOsLevel1D

gateCLongOk  = not useGateC or na(htfWt1) or htfWt1 < _htfObLvl
gateCShortOk = not useGateC or na(htfWt1) or htfWt1 > _htfOsLvl

// B2 — 1H-Confluence for 15M: only trade 15M when 1H wt1 is in OS (long) or OB (short).
// htfWt1 on 15M = 1H wt1 (fetched above). No-op on all other TFs.
gateB2LongOk  = not useGateB2 or _tfSec != 900 or (not na(htfWt1) and htfWt1 <= b2OsLevel)
gateB2ShortOk = not useGateB2 or _tfSec != 900 or (not na(htfWt1) and htfWt1 >= b2ObLevel)

// ─── Gate G — Trend-aware Counter-Trend Range Filter ─────────────────────────
// Active ONLY on 15M and 1H. On 4H/1D Gate G blocks real reversal winners (test117).
_gateG_active_tf = _tfSec == 900 or _tfSec == 3600
// Use TF-specific smoothing (matches WT smoothing — sensitive on fast TFs, stable on slow)
_gFast = f_smooth(close, gTrendFastLen, _smoothMode)
_gSlow = f_smooth(close, gTrendSlowLen, _smoothMode)
_gTrendUp   = _gFast > _gSlow
_gTrendDown = _gFast < _gSlow

_gRangeLow  = ta.lowest(low,  gRangeLen)
_gRangeHigh = ta.highest(high, gRangeLen)
_gRangePos  = (close - _gRangeLow) / math.max(_gRangeHigh - _gRangeLow, syminfo.mintick)

// Long is COUNTER-trend when trend is down → require low range pos.
// Long WITH trend (uptrend) → bypass filter (pullback continuation).
// Outside active TFs (4H, 1D), gate always passes.
gateGLongOk  = not useGateG or not _gateG_active_tf or _gTrendUp   or _gRangePos <= gMaxRangePosLong
gateGShortOk = not useGateG or not _gateG_active_tf or _gTrendDown or _gRangePos >= gMinRangePosShort

// ─── Signals ─────────────────────────────────────────────────────────────────
_wtLongRaw  = ta.crossover(wt1, wt2)  and wt1 <= _osLevel and wt1 >= _osFloor
_wtShortRaw = ta.crossunder(wt1, wt2) and wt1 >= _obLevel and wt1 <= _obFloor
longSignal  = _wtLongRaw  and srsiLongOk
shortSignal = _wtShortRaw and srsiShortOk

canLong  = dirMode != "Short Only"
canShort = dirMode != "Long Only"

// ─── Trade state ─────────────────────────────────────────────────────────────
// varip: must survive across intra-bar recalculations (calc_on_order_fills).
// Assigned in the fill recalculation; read in the close recalculation.
varip float _longSL   = na
varip float _longTP   = na
varip float _longRisk = na
varip bool  _longBE   = false

varip float _shortSL   = na
varip float _shortTP   = na
varip float _shortRisk = na
varip bool  _shortBE   = false

// Trailing-stop state: highest close since long entry / lowest close since short entry.
varip float _maxCloseLong  = na
varip float _minCloseShort = na
// Trail stays armed once triggered — otherwise the stop falls back to SL/BE when close dips below the trigger
varip bool  _longTrailArmed  = false
varip bool  _shortTrailArmed = false

varip int _lastExitBar = na

// Frozen at signal bar — used for SL/TP and exit log
var float _pendingLongAtr    = na
var float _pendingShortAtr   = na
var float _tradeAbsWt1       = na  // abs(wt1) at entry signal bar — used by Grind Engine
var int   _pendingLowPivBars  = na
var int   _pendingHighPivBars = na

// Per-trade entry context, indexed by trade idx. Filled when a fill is detected
// in the retroactive logger below; read in the exit log. Avoids the fragile
// large-lookback approach (snapshots are taken when lookback is small).
var array<float> _tradeAtr      = array.new<float>()
var array<int>   _tradeLpb      = array.new<int>()
var array<int>   _tradeHpb      = array.new<int>()

// Reliable trade-event detection.
// _totalTrades = closedtrades + opentrades catches all entry events including
// the case where a trade opens and closes in the same simulation tick (which
// _isNewLong via opentrades alone misses).
varip int  _prevOpenTrades   = 0
varip int  _prevClosedTrades = 0
varip int  _prevTotalTrades  = 0
varip int  _lastLoggedFill   = 0
varip int  _barLockIndex     = -1
varip bool _exitLockThisBar  = false

// Reset per-bar lock on first recalculation of each new bar
if bar_index != _barLockIndex
    _barLockIndex    := bar_index
    _exitLockThisBar := false

_totalTrades = strategy.closedtrades + strategy.opentrades
_isNewEntry  = _totalTrades > _prevTotalTrades
_isNewLong   = _isNewEntry and strategy.position_size > 0
_isNewShort  = _isNewEntry and strategy.position_size < 0
_isFlat      = strategy.position_size == 0
_isNewFlat   = strategy.closedtrades > _prevClosedTrades

// Set SL/TP/metadata when a new entry is currently open. Same-tick close
// (entry+TP in one tick) skips this since position_size==0 — fill is still
// logged retroactively below.
if _isNewLong
    _atrUsed   = na(_pendingLongAtr) ? atrVal : _pendingLongAtr
    _entry     = strategy.position_avg_price
    _longSL   := _entry - slMult * _atrUsed
    _longTP   := _entry + _tpMult * _atrUsed
    _longRisk := math.max(_entry - _longSL, syminfo.mintick)
    _longBE   := false
    _maxCloseLong := _entry
    _longTrailArmed := false
    _pendingLongAtr     := na
    _pendingShortAtr    := na
    _pendingLowPivBars  := na
    _pendingHighPivBars := na

if _isNewShort
    _atrUsed    = na(_pendingShortAtr) ? atrVal : _pendingShortAtr
    _entry      = strategy.position_avg_price
    _shortSL   := _entry + slMult * _atrUsed
    _shortTP   := _entry - _tpMult * _atrUsed
    _shortRisk := math.max(_shortSL - _entry, syminfo.mintick)
    _shortBE   := false
    _minCloseShort := _entry
    _shortTrailArmed := false
    _pendingLongAtr     := na
    _pendingShortAtr    := na
    _pendingLowPivBars  := na
    _pendingHighPivBars := na

// Retroactive fill logging: iterates through any unlogged trades and pulls
// data from strategy.closedtrades or strategy.opentrades. Catches every entry
// event including same-tick entry+exit cases.
if _totalTrades > _lastLoggedFill
    for _i = _lastLoggedFill to _totalTrades - 1
        int    _ebarF = -1
        float  _epF   = na
        float  _szF   = 0.0
        string _src   = ""
        if _i < strategy.closedtrades
            _epF   := strategy.closedtrades.entry_price(_i)
            _szF   := strategy.closedtrades.size(_i)
            _ebarF := strategy.closedtrades.entry_bar_index(_i)
            _src   := "closed"
        else
            _idxOpen = _i - strategy.closedtrades
            _epF   := strategy.opentrades.entry_price(_idxOpen)
            _szF   := strategy.opentrades.size(_idxOpen)
            _ebarF := strategy.opentrades.entry_bar_index(_idxOpen)
            _src   := "open"
        _dirF  = _szF > 0 ? "long" : "short"
        // Snapshot entry context — lookback is small here (fill just detected).
        _lookbackFresh = math.max(0, bar_index - _ebarF)
        _atrSnap = na(atrVal[_lookbackFresh])         ? atrVal         : atrVal[_lookbackFresh]
        _lpbSnap = na(barsSinceLowPiv[_lookbackFresh])  ? -1 : barsSinceLowPiv[_lookbackFresh]
        _hpbSnap = na(barsSinceHighPiv[_lookbackFresh]) ? -1 : barsSinceHighPiv[_lookbackFresh]
        array.push(_tradeAtr, _atrSnap)
        array.push(_tradeLpb, _lpbSnap)
        array.push(_tradeHpb, _hpbSnap)
        log.info(str.format('WT4 ENTRY FILL | tradeId={0} | dir={1} | bar={2} | entry={3} | tf={4} | source={5}',
             _i + 1, _dirF, _ebarF, str.tostring(_epF, format.mintick), timeframe.period, _src))
    _lastLoggedFill := _totalTrades

if _isNewFlat
    _exitLockThisBar := true
    _lastExitBar     := bar_index
    if (longSignal and canLong) or (shortSignal and canShort)
        log.info(str.format('WT4 REENTRY_BLOCKED | bar={0} | longSig={1} | shortSig={2} | tf={3}',
             bar_index, longSignal and canLong, shortSignal and canShort, timeframe.period))

// Must come AFTER all detection blocks
_prevOpenTrades   := strategy.opentrades
_prevClosedTrades := strategy.closedtrades
_prevTotalTrades  := _totalTrades

if _isFlat
    _longBE    := false
    _shortBE   := false
    _longSL    := na
    _longTP    := na
    _longRisk  := na
    _shortSL   := na
    _shortTP   := na
    _shortRisk := na

// ─── Bracket exit + break-even + trailing stop ───────────────────────────────
if strategy.position_size > 0 and not na(_longRisk)
    _nextBar = bar_index > strategy.opentrades.entry_bar_index(0)
    if enableBE and _nextBar and close >= strategy.position_avg_price + beTrigR * _longRisk
        _longBE := true
    // Track highest close since entry for trailing
    if na(_maxCloseLong) or close > _maxCloseLong
        _maxCloseLong := close
    // Trail SL once trade has progressed by trailTriggerR. Stop never moves down.
    if enableTrail and _nextBar and (close - strategy.position_avg_price) >= trailTriggerR * _longRisk
        _longTrailArmed := true
    _longTrailLevel = _longTrailArmed ? _maxCloseLong - trailDistanceR * _longRisk : na
    _longBaseStop = _longBE ? strategy.position_avg_price : _longSL
    _longStop     = na(_longTrailLevel) ? _longBaseStop : math.max(_longBaseStop, _longTrailLevel)
    _longExitComment = _longStop != _longBaseStop ? "trail" : _longBE ? "be" : "sl"
    // Trail replaces the fixed TP: a TP limit at ≤ trailTriggerR would fill before the trail arms
    strategy.exit("Long Exit", "Long", stop=_longStop, limit=enableTrail ? na : _longTP,
         comment_profit="tp", comment_loss=_longExitComment)

if strategy.position_size < 0 and not na(_shortRisk)
    _nextBar = bar_index > strategy.opentrades.entry_bar_index(0)
    if enableBE and _nextBar and close <= strategy.position_avg_price - beTrigR * _shortRisk
        _shortBE := true
    // Track lowest close since entry for trailing
    if na(_minCloseShort) or close < _minCloseShort
        _minCloseShort := close
    // Trail SL once trade has progressed by trailTriggerR. Stop never moves up.
    if enableTrail and _nextBar and (strategy.position_avg_price - close) >= trailTriggerR * _shortRisk
        _shortTrailArmed := true
    _shortTrailLevel = _shortTrailArmed ? _minCloseShort + trailDistanceR * _shortRisk : na
    _shortBaseStop = _shortBE ? strategy.position_avg_price : _shortSL
    _shortStop     = na(_shortTrailLevel) ? _shortBaseStop : math.min(_shortBaseStop, _shortTrailLevel)
    _shortExitComment = _shortStop != _shortBaseStop ? "trail" : _shortBE ? "be" : "sl"
    // Trail replaces the fixed TP: a TP limit at ≤ trailTriggerR would fill before the trail arms
    strategy.exit("Short Exit", "Short", stop=_shortStop, limit=enableTrail ? na : _shortTP,
         comment_profit="tp", comment_loss=_shortExitComment)

// ─── Structural exit (unconfirmed pivot) ─────────────────────────────────────
_structMinR = _tfSec == 900 ? structMinR15M : _tfSec == 3600 ? structMinR1H : _tfSec == 14400 ? structMinR4H : structMinR1D

if useStructExit
    _potentialHigh = high[1] > high[0] and high[1] > high[2]
    _potentialLow  = low[1]  < low[0]  and low[1]  < low[2]
    _longProfitR   = not na(_longRisk)  and _longRisk  > 0 ? (close - strategy.position_avg_price) / _longRisk  : 0.0
    _shortProfitR  = not na(_shortRisk) and _shortRisk > 0 ? (strategy.position_avg_price - close) / _shortRisk : 0.0
    if strategy.position_size > 0 and _potentialHigh and _longProfitR >= _structMinR
        strategy.close("Long", comment="struct")
    if strategy.position_size < 0 and _potentialLow and _shortProfitR >= _structMinR
        strategy.close("Short", comment="struct")

// ─── Post-entry quality exits (E, F) ─────────────────────────────────────────
// E — Fail-Fast: cut quickly if trade is immediately adverse (1H only)
// Skip the entry bar itself (_barsInTrade > 0): wait for at least one new bar
// before evaluating adverse move.
_failFastActive = useFailFast and _tfSec == 3600
if _failFastActive and strategy.position_size > 0 and not na(_longRisk) and _longRisk > 0
    _barsInTrade = bar_index - strategy.opentrades.entry_bar_index(0)
    _adverseR    = (close - strategy.position_avg_price) / _longRisk
    if _barsInTrade > 0 and _barsInTrade <= failFastBars and _adverseR <= failFastR
        strategy.close("Long", comment="failfast")

if _failFastActive and strategy.position_size < 0 and not na(_shortRisk) and _shortRisk > 0
    _barsInTrade = bar_index - strategy.opentrades.entry_bar_index(0)
    _adverseR    = (strategy.position_avg_price - close) / _shortRisk
    if _barsInTrade > 0 and _barsInTrade <= failFastBars and _adverseR <= failFastR
        strategy.close("Short", comment="failfast")

// F — No-Follow Exit: SINGLE check at window end. Hardcoded to 1H only.
_isGrindTrade     = useGrindEngine and not na(_tradeAbsWt1) and _tradeAbsWt1 >= grindAbsWt1Min
_noFollowActiveTF = _tfSec == 3600 and not _isGrindTrade
if useNoFollow and _noFollowActiveTF and strategy.position_size > 0 and not na(_longRisk) and _longRisk > 0
    _barsInTrade = bar_index - strategy.opentrades.entry_bar_index(0)
    _progressR   = (close - strategy.position_avg_price) / _longRisk
    if _barsInTrade == nftWindow and _progressR < nftRequiredR
        strategy.close("Long", comment="noFollow")

if useNoFollow and _noFollowActiveTF and strategy.position_size < 0 and not na(_shortRisk) and _shortRisk > 0
    _barsInTrade = bar_index - strategy.opentrades.entry_bar_index(0)
    _progressR   = (strategy.position_avg_price - close) / _shortRisk
    if _barsInTrade == nftWindow and _progressR < nftRequiredR
        strategy.close("Short", comment="noFollow")

// ─── Pivot event log (validator reference) ───────────────────────────────────
// Logs confirmed pivots as reference points. WT signals use barsSinceLowPiv /
// barsSinceHighPiv to measure proximity — pivot validates signal quality.
if _plFound
    log.info(str.format('WT4 PIVOT | dir=long | pivBar={0} | pivLow={1} | pivAtr={2} | wt1={3} | tf={4}',
         bar_index - pivLen,
         str.tostring(low[pivLen],  format.mintick),
         str.tostring(atrVal[pivLen], format.mintick),
         str.tostring(wt1[pivLen], '#.##'),
         timeframe.period))

if _phFound
    log.info(str.format('WT4 PIVOT | dir=short | pivBar={0} | pivHigh={1} | pivAtr={2} | wt1={3} | tf={4}',
         bar_index - pivLen,
         str.tostring(high[pivLen], format.mintick),
         str.tostring(atrVal[pivLen], format.mintick),
         str.tostring(wt1[pivLen], '#.##'),
         timeframe.period))

// ─── Entries + Block diagnostics ─────────────────────────────────────────────
_cooldownOk = na(_lastExitBar) or (
     bar_index > _lastExitBar and
     (cooldown == 0 or (bar_index - _lastExitBar) > cooldown))

f_blockReason(wtRaw, srsiOk, cooldownOk, inSession, inDateRange, tfOk, volOk, bbExpOk, chopOk, pivBarsOk, pivDistOk, sweepOk, gateAOk, gateBOk, gateCOk, gateDOk, gateGOk, gateHOk, gateIOk, gateB2Ok, gateJOk, gateKOk, gateLOk, gateMOk) =>
    string _r = ""
    if wtRaw
        if not srsiOk
            _r := "srsi"
        else if not cooldownOk
            _r := "cooldown"
        else if not inSession
            _r := "session"
        else if not inDateRange
            _r := "dateRange"
        else if not tfOk
            _r := "tf"
        else if not volOk
            _r := "atrRank"
        else if not bbExpOk
            _r := "bbExpansion"
        else if not chopOk
            _r := "chop"
        else if not pivBarsOk
            _r := "pivotBars"
        else if not pivDistOk
            _r := "pivotDist"
        else if not sweepOk
            _r := "sweep"
        else if not gateAOk
            _r := "gateA"
        else if not gateBOk
            _r := "gateB"
        else if not gateCOk
            _r := "gateC"
        else if not gateDOk
            _r := "gateD"
        else if not gateGOk
            _r := "gateG"
        else if not gateHOk
            _r := "gateH"
        else if not gateIOk
            _r := "gateI"
        else if not gateB2Ok
            _r := "gateB2"
        else if not gateJOk
            _r := "gateJ"
        else if not gateKOk
            _r := "gateK"
        else if not gateLOk
            _r := "gateL"
        else if not gateMOk
            _r := "gateM"
    _r

_blockLong  = f_blockReason(_wtLongRaw,  srsiLongOk,  _cooldownOk, _inSession, _inDateRange, _tfOk, _volOk, volExpansionGate, chopGate, pivotLongGate,  pivotDistLongGate,  sweepLongGate,  gateALongOk,  gateBLongOk,  gateCLongOk,  gateDLongOk,  gateGLongOk,  gateHLongOk,  gateILongOk,  gateB2LongOk,  gateJLongOk,  gateKLongOk,  gateLLongOk,  gateMlongOk)
_blockShort = f_blockReason(_wtShortRaw, srsiShortOk, _cooldownOk, _inSession, _inDateRange, _tfOk, _volOk, volExpansionGate, chopGate, pivotShortGate, pivotDistShortGate, sweepShortGate, gateAShortOk, gateBShortOk, gateCShortOk, gateDShortOk, gateGShortOk, gateHShortOk, gateIShortOk, gateB2ShortOk, gateJShortOk, gateKShortOk, gateLShortOk, gateMShortOk)

// Gate-flags string for full visibility of which Quality Gates passed/failed.
// Allows offline simulation: "what if Gate X were off?" — read all flags from BLOCKED log.
// T = passed, F = blocked. Only A-I quality gates included (cooldown/session etc. are baseline).
f_flags(a, b, c, d, g, h, i, b2, j, k, l, m) =>
    sa = a ? "T" : "F"
    sb = b ? "T" : "F"
    sc = c ? "T" : "F"
    sd = d ? "T" : "F"
    sg = g ? "T" : "F"
    sh = h ? "T" : "F"
    si = i ? "T" : "F"
    sb2 = b2 ? "T" : "F"
    sj = j ? "T" : "F"
    sk = k ? "T" : "F"
    sl = l ? "T" : "F"
    sm = m ? "T" : "F"
    "A=" + sa + "|B=" + sb + "|C=" + sc + "|D=" + sd + "|G=" + sg + "|H=" + sh + "|I=" + si + "|B2=" + sb2 + "|J=" + sj + "|K=" + sk + "|L=" + sl + "|M=" + sm
_gatesLong  = f_flags(gateALongOk,  gateBLongOk,  gateCLongOk,  gateDLongOk,  gateGLongOk,  gateHLongOk,  gateILongOk,  gateB2LongOk,  gateJLongOk,  gateKLongOk,  gateLLongOk,  gateMlongOk)
_gatesShort = f_flags(gateAShortOk, gateBShortOk, gateCShortOk, gateDShortOk, gateGShortOk, gateHShortOk, gateIShortOk, gateB2ShortOk, gateJShortOk, gateKShortOk, gateLShortOk, gateMShortOk)

if canLong and strategy.position_size == 0 and _blockLong != ""
    log.info(str.format('WT4 BLOCKED | dir=long | reason={0} | bar={1} | wt1={2} | srsiK={3} | srsiD={4} | srsiOsBars={5} | atrRank={6} | chop={7} | bbWidthRank={8} | sweepCtx={9} | lowPivBars={10} | distLowAtr={11} | gates={12} | close={13} | atr={14} | tf={15}',
         _blockLong, bar_index,
         str.tostring(wt1, '#.##'),
         str.tostring(srsiK, '#.##'), str.tostring(srsiD, '#.##'), _srsiOsBars,
         str.tostring(atrRank, '#.#'), str.tostring(chop, '#.#'), str.tostring(bbWidthRank, '#.#'),
         sweepLongContext,
         str.tostring(barsSinceLowPiv),
         str.tostring(distFromLowAtr, '#.##'),
         _gatesLong,
         str.tostring(close, format.mintick),
         str.tostring(atrVal, format.mintick),
         timeframe.period))

if canShort and strategy.position_size == 0 and _blockShort != ""
    log.info(str.format('WT4 BLOCKED | dir=short | reason={0} | bar={1} | wt1={2} | srsiK={3} | srsiD={4} | srsiObBars={5} | atrRank={6} | chop={7} | bbWidthRank={8} | sweepCtx={9} | highPivBars={10} | distHighAtr={11} | gates={12} | close={13} | atr={14} | tf={15}',
         _blockShort, bar_index,
         str.tostring(wt1, '#.##'),
         str.tostring(srsiK, '#.##'), str.tostring(srsiD, '#.##'), _srsiObBars,
         str.tostring(atrRank, '#.#'), str.tostring(chop, '#.#'), str.tostring(bbWidthRank, '#.#'),
         sweepShortContext,
         str.tostring(barsSinceHighPiv),
         str.tostring(distFromHighAtr, '#.##'),
         _gatesShort,
         str.tostring(close, format.mintick),
         str.tostring(atrVal, format.mintick),
         timeframe.period))

_longEntry  = longSignal  and canLong  and strategy.position_size == 0 and not _exitLockThisBar and _cooldownOk and _canTrade and pivotLongGate  and pivotDistLongGate  and sweepLongGate  and gateALongOk  and gateBLongOk  and gateCLongOk  and gateDLongOk  and gateGLongOk  and gateHLongOk  and gateILongOk  and gateB2LongOk  and gateJLongOk  and gateKLongOk  and gateLLongOk  and gateMlongOk
_shortEntry = shortSignal and canShort and strategy.position_size == 0 and not _exitLockThisBar and _cooldownOk and _canTrade and pivotShortGate and pivotDistShortGate and sweepShortGate and gateAShortOk and gateBShortOk and gateCShortOk and gateDShortOk and gateGShortOk and gateHShortOk and gateIShortOk and gateB2ShortOk and gateJShortOk and gateKShortOk and gateLShortOk and gateMShortOk

if _longEntry
    _pendingLongAtr    := atrVal
    _pendingLowPivBars  := barsSinceLowPiv
    _pendingHighPivBars := barsSinceHighPiv
    _tradeAbsWt1       := math.abs(wt1)
    log.info(str.format('WT4 ENTRY | dir=long | bar={0} | wt1={1} | wt2={2} | osLevel={3} | srsiK={4} | srsiD={5} | srsiOsBars={6} | lowPivBars={7} | highPivBars={8} | atr={9} | signalSL={10} | signalTP={11} | atrRank={12} | chop={13} | bbWidthRank={14} | sweepCtx={15} | distLowAtr={16} | wt1Delta={17} | tpMult={18} | tf={19}',
         bar_index, str.tostring(wt1, '#.##'), str.tostring(wt2, '#.##'), _osLevel,
         str.tostring(srsiK, '#.##'), str.tostring(srsiD, '#.##'), _srsiOsBars,
         str.tostring(barsSinceLowPiv), str.tostring(barsSinceHighPiv),
         str.tostring(atrVal, format.mintick),
         str.tostring(close - slMult * atrVal, format.mintick),
         str.tostring(close + _tpMult * atrVal, format.mintick),
         str.tostring(atrRank, '#.#'), str.tostring(chop, '#.#'), str.tostring(bbWidthRank, '#.#'),
         sweepLongContext,
         str.tostring(distFromLowAtr, '#.##'), str.tostring(wt1 - wt1[1], '#.##'), str.tostring(_tpMult, '#.#'),
         timeframe.period))
    strategy.entry("Long", strategy.long)

if _shortEntry
    _pendingShortAtr    := atrVal
    _pendingLowPivBars  := barsSinceLowPiv
    _pendingHighPivBars := barsSinceHighPiv
    _tradeAbsWt1       := math.abs(wt1)
    log.info(str.format('WT4 ENTRY | dir=short | bar={0} | wt1={1} | wt2={2} | obLevel={3} | srsiK={4} | srsiD={5} | srsiObBars={6} | lowPivBars={7} | highPivBars={8} | atr={9} | signalSL={10} | signalTP={11} | atrRank={12} | chop={13} | bbWidthRank={14} | sweepCtx={15} | distHighAtr={16} | wt1Delta={17} | tpMult={18} | tf={19}',
         bar_index, str.tostring(wt1, '#.##'), str.tostring(wt2, '#.##'), _obLevel,
         str.tostring(srsiK, '#.##'), str.tostring(srsiD, '#.##'), _srsiObBars,
         str.tostring(barsSinceLowPiv), str.tostring(barsSinceHighPiv),
         str.tostring(atrVal, format.mintick),
         str.tostring(close + slMult * atrVal, format.mintick),
         str.tostring(close - _tpMult * atrVal, format.mintick),
         str.tostring(atrRank, '#.#'), str.tostring(chop, '#.#'), str.tostring(bbWidthRank, '#.#'),
         sweepShortContext,
         str.tostring(distFromHighAtr, '#.##'), str.tostring(wt1 - wt1[1], '#.##'), str.tostring(_tpMult, '#.#'),
         timeframe.period))
    strategy.entry("Short", strategy.short)

// ─── Exit log ────────────────────────────────────────────────────────────────
varip int _lastLoggedTrade = 0
if strategy.closedtrades > _lastLoggedTrade
    for _idx = _lastLoggedTrade to strategy.closedtrades - 1
        _ep    = strategy.closedtrades.entry_price(_idx)
        _xp    = strategy.closedtrades.exit_price(_idx)
        _pnl   = strategy.closedtrades.profit(_idx)
        _barsH = strategy.closedtrades.exit_bar_index(_idx) - strategy.closedtrades.entry_bar_index(_idx)
        _mfe   = strategy.closedtrades.max_runup(_idx)
        _mae   = strategy.closedtrades.max_drawdown(_idx)
        _size = strategy.closedtrades.size(_idx)
        _dir  = _size > 0 ? "long" : "short"
        _eb_idx    = strategy.closedtrades.entry_bar_index(_idx)
        // Entry-frozen ATR & pivot bars from per-trade arrays (snapshot at fill time).
        // Robust against Pine's max_bars_back limits — no large dynamic lookbacks.
        _atrEntry  = array.size(_tradeAtr) > _idx ? array.get(_tradeAtr, _idx) : atrVal
        _lpbEntry  = array.size(_tradeLpb) > _idx ? array.get(_tradeLpb, _idx) : -1
        _hpbEntry  = array.size(_tradeHpb) > _idx ? array.get(_tradeHpb, _idx) : -1
        _riskDenom = math.max(slMult * _atrEntry, syminfo.mintick)
        _R         = _dir == "long" ? (_xp - _ep) / _riskDenom : (_ep - _xp) / _riskDenom
        _exitComment = strategy.closedtrades.exit_comment(_idx)
        _exitReason  = na(_exitComment) or _exitComment == "" ? "unknown" : _exitComment
        log.info(str.format('WT4 REAL EXIT | tradeId={0} | dir={1} | entryBar={2} | exitBar={3} | entry={4} | exitPx={5} | pnl={6} | R={7} | barsHeld={8} | MFE={9} | MAE={10} | lowPivBars={11} | highPivBars={12} | exitReason={13} | tf={14}',
             _idx + 1, _dir,
             _eb_idx,
             strategy.closedtrades.exit_bar_index(_idx),
             str.tostring(_ep, format.mintick),
             str.tostring(_xp, format.mintick),
             str.tostring(_pnl, '#.##'),
             str.tostring(_R, '#.##'),
             _barsH,
             str.tostring(_mfe, '#.##'),
             str.tostring(_mae, '#.##'),
             str.tostring(_lpbEntry),
             str.tostring(_hpbEntry),
             _exitReason,
             timeframe.period))
    _lastLoggedTrade := strategy.closedtrades

// ─── Plots ───────────────────────────────────────────────────────────────────
plot(wt1,     "WT",     color=color.new(color.teal,   0), linewidth=2)
plot(wt2,     "Signal", color=color.new(color.orange, 0), linewidth=1)
plot(_osLevel, "OS",    color=color.new(#00c853, 50),     linewidth=1)
plot(_obLevel, "OB",    color=color.new(#d50000, 50),     linewidth=1)
hline(0, "Zero", color=color.new(color.gray, 60))

// Raw WT cross (dim) — shows signal before gate filtering, pinned to curve
plotshape(longSignal  and canLong  and not _longEntry  ? wt1 - 6 : na, title="Long Blocked",  style=shape.circle,       location=location.absolute, color=color.new(#00c853, 70), size=size.tiny)
plotshape(shortSignal and canShort and not _shortEntry ? wt1 + 6 : na, title="Short Blocked", style=shape.circle,       location=location.absolute, color=color.new(#d50000, 70), size=size.tiny)
// Gated entry — bright arrow pinned to curve
plotshape(_longEntry  ? wt1 - 8 : na, title="Long Entry",  style=shape.triangleup,   location=location.absolute, color=color.new(#00c853, 0), size=size.tiny)
plotshape(_shortEntry ? wt1 + 8 : na, title="Short Entry", style=shape.triangledown, location=location.absolute, color=color.new(#d50000, 0), size=size.tiny)

// Price-chart labels with context tooltip
if showSignals and _longEntry
    label.new(bar_index, low, '▲',
         xloc=xloc.bar_index, yloc=yloc.price,
         style=label.style_label_up, color=color.new(#00c853, 0), textcolor=color.white,
         size=size.normal, force_overlay=true,
         tooltip='Long Entry · ' + timeframe.period
              + '\n────────────'
              + '\nWT1: '    + str.tostring(wt1, '#.##') + '  WT2: ' + str.tostring(wt2, '#.##') + '  OS: ' + str.tostring(_osLevel)
              + '\nATR: '    + str.tostring(atrVal, format.mintick)
              + '\nSL ~'     + str.tostring(close - slMult * atrVal, format.mintick)
              + '   TP ~'   + str.tostring(close + _tpMult * atrVal, format.mintick)
              + '\n────────────'
              + '\nLow pivot:  ' + str.tostring(barsSinceLowPiv)  + ' bars ago'
              + '\nHigh pivot: ' + str.tostring(barsSinceHighPiv) + ' bars ago'
              + '\nATR rank: ' + str.tostring(atrRank, '#.#') + '  Chop: ' + str.tostring(chop, '#.#'))

// ─── Divergence plots ─────────────────────────────────────────────────────────
_nc = color.new(color.white, 100)
plot(_divPlFound ? _divOsc[divLbR] : na, "Bull Div",     offset=-divLbR, linewidth=2, color=bullDivCond    ? #00c853                : _nc)
plot(_divPlFound ? _divOsc[divLbR] : na, "Hid Bull Div", offset=-divLbR, linewidth=2, color=hidBullDivCond ? color.new(#00c853, 60) : _nc)
plot(_divPhFound ? _divOsc[divLbR] : na, "Bear Div",     offset=-divLbR, linewidth=2, color=bearDivCond    ? #d50000                : _nc)
plot(_divPhFound ? _divOsc[divLbR] : na, "Hid Bear Div", offset=-divLbR, linewidth=2, color=hidBearDivCond ? color.new(#d50000, 60) : _nc)
plotshape(bullDivCond    ? _divOsc[divLbR] : na, "Bull",   offset=-divLbR, style=shape.labelup,   location=location.absolute, color=#00c853,                textcolor=color.white, text=" Bull ")
plotshape(hidBullDivCond ? _divOsc[divLbR] : na, "H Bull", offset=-divLbR, style=shape.labelup,   location=location.absolute, color=color.new(#00c853, 50), textcolor=color.white, text=" H Bull ")
plotshape(bearDivCond    ? _divOsc[divLbR] : na, "Bear",   offset=-divLbR, style=shape.labeldown, location=location.absolute, color=#d50000,                textcolor=color.white, text=" Bear ")
plotshape(hidBearDivCond ? _divOsc[divLbR] : na, "H Bear", offset=-divLbR, style=shape.labeldown, location=location.absolute, color=color.new(#d50000, 50), textcolor=color.white, text=" H Bear ")
plotshape(showDivPivots and _divPhFound ? _divOsc[divLbR] : na, "Div Ph", offset=-divLbR, style=shape.circle, location=location.absolute, color=color.new(color.gray, 40), size=size.tiny)
plotshape(showDivPivots and _divPlFound ? _divOsc[divLbR] : na, "Div Pl", offset=-divLbR, style=shape.circle, location=location.absolute, color=color.new(color.gray, 40), size=size.tiny)

if showSignals and _shortEntry
    label.new(bar_index, high, '▼',
         xloc=xloc.bar_index, yloc=yloc.price,
         style=label.style_label_down, color=color.new(#d50000, 0), textcolor=color.white,
         size=size.normal, force_overlay=true,
         tooltip='Short Entry · ' + timeframe.period
              + '\n────────────'
              + '\nWT1: '    + str.tostring(wt1, '#.##') + '  WT2: ' + str.tostring(wt2, '#.##') + '  OB: ' + str.tostring(_obLevel)
              + '\nATR: '    + str.tostring(atrVal, format.mintick)
              + '\nSL ~'     + str.tostring(close + slMult * atrVal, format.mintick)
              + '   TP ~'   + str.tostring(close - _tpMult * atrVal, format.mintick)
              + '\n────────────'
              + '\nHigh pivot: ' + str.tostring(barsSinceHighPiv) + ' bars ago'
              + '\nLow pivot:  ' + str.tostring(barsSinceLowPiv)  + ' bars ago'
              + '\nATR rank: ' + str.tostring(atrRank, '#.#') + '  Chop: ' + str.tostring(chop, '#.#'))

Overview

WaveTrend v4 [WavesUnchained] Strategy enters long positions when the WaveTrend oscillator crosses upward below configured oversold levels, and enters short positions on downward crosses above overbought thresholds. Exits are managed with ATR-based stop-loss and take-profit orders alongside break-even management and cooldown timers. User inputs configure smoothing types including JMA and HMA, timeframe-specific overbought and oversold boundaries, and Stochastic RSI gates. It displays in a separate indicator pane.

Details

Published by

casoon on GitHub

Draws

Separate pane below the chart

Markets

Any symbol

Timeframes

M15-H1, H4 and above

Risk Notes

  • Signals can change on the live bar or after it closes (repainting)

Topics

pine-scriptpinescripttechnical-analysistrading-indicatorstrading-strategiestradingview

Settings

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

SettingTypeDefaultVariable
Channel Lengthint10chLen
Average Lengthint21avgLen
Signal Smoothingint4sigLen
OS Level 15M (long)float-10osLevel15M
OS Level 1H (long)float-20osLevel1H
OS Level 4H (long)float-5osLevel4H
OS Level 1D (long)float-20osLevel1D
OB Level 15M (short)float10obLevel15M
OB Level 1H (short)float20obLevel1H
OB Level 4H (short)float5obLevel4H
OB Level 1D (short)float20obLevel1D
OS Floor 4H (long cap)float-999osFloor4H
Show all 40 settings
SettingTypeDefaultVariable
OS Floor 1D (long cap)float-48osFloor1D
OB Floor 4H (short cap)float999obFloor4H
OB Floor 1D (short cap)float48obFloor1D
Grind Engine: no noFollow for deep 1H entriesboolfalseuseGrindEngine
Grind abs(wt1) min (1H)float60grindAbsWt1Min
Smoothing 15MstringRMAsmooth15M
Smoothing 1HstringHMAsmooth1H
Smoothing 4HstringJMAsmooth4H
Smoothing 1DstringSMAsmooth1D
JMA Phaseint50jmaPhase
JMA Powerfloat2.0jmaPower
RSI Lengthint14srsiRsiLen
Stoch Lengthint14srsiStochLen
K Smoothint3srsiKSmooth
D Smoothint3srsiDSmooth
OS Levelfloat20srsiOsLevel
OB Levelfloat80srsiObLevel
Trend Block (Bars stuck at OS/OB)int5srsiPersist
Use StochRSI GateboolfalseuseSrsiGate
ATR Lengthint14atrLen
SL (ATR×)float1.5slMult
TP 15M (ATR×)float3.0tpMult15M
TP 1H (ATR×)float3.0tpMult1H
TP 4H (ATR×)float4.0tpMult4H
TP 1D (ATR×)float4.0tpMult1D
Enable Break-Even StopbooltrueenableBE
Trigger (R×)float1.0beTrigR
Enable Trailing StopbooltrueenableTrail
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 strategy combines Larry Williams' Vix Fix synthetic volatility indicator with Bollinger Bands. It calculates standard and inverted Williams Vix Fix metrics, displaying them as histogram columns with standard deviation bands in a separate pane. Long entrie

Strategy v4 Volatility & bandsOscillators & momentum
Updated 2 months ago · None stated GitHub repository of Williams Vix Fix Strategy [Alorse]

2xTP, SL to Entry

by hasnocool
106 stars

This strategy executes mean-reversion trades based on Bollinger Bands and the Stochastic Oscillator. Long orders trigger when price dips below the lower band alongside a bullish Stochastic crossover, with an optional oversold threshold. Short orders execute on

Strategy v5 Mean reversionOscillators & momentum
Updated 2 years ago · GPL-3.0 GitHub repository of 2xTP, SL to Entry

Greeffer BB Strategy

by SammyEnigma
18 stars

This strategy fades Bollinger Band extremes using the close. It enters short when price crosses above the upper band and the 14-period RSI is above 70, and enters long when price crosses below the lower band and RSI is below 30. It closes half of an open short

Strategy v4 Volatility & bandsOscillators & momentum
Updated 6 years ago · None stated GitHub repository of Greeffer BB Strategy

Williams %R Reversal - Eterna MCP

by EternaHybridExchange
9 stars

This strategy tracks the Williams %R oscillator to trade mean-reversion bounces from overbought and oversold extremes. It opens long or short positions when a reversal signal occurs alongside confirming price direction, while generating JSON-formatted alert pa

Strategy v5 Oscillators & momentumMean reversion
Updated 7 months ago · MIT GitHub repository of Williams %R Reversal - Eterna MCP

RSI mean reversion

by broomva
5 stars

This strategy plots the relative strength index in a separate pane and trades mean reversion. It enters long when RSI crosses above the oversold level and enters short when RSI crosses below the overbought level. Opposite signals reverse the position. Size def

Strategy v5 Oscillators & momentumMean reversion
Updated 2 days ago · MIT GitHub repository of RSI mean reversion

More Oscillators & momentum scripts

Related on ForexR

Trade Your Charts With a Regulated Broker

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

Compare brokers