Indicator · Pine Script v6 · On the chart

Vein: Reversal Zones [WavesUnchained]

by casoon

This indicator draws support and resistance zones on the price chart from reversal bars that later reach ATR-based targets.

View on GitHub

Repaints

GitHub stars9on the repository
Pine versionv6current
Lines of code595
Settings40inputs
LicenceMIT
Last updateyesterday27 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 indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

595 lines of Pine Script v6 from indicators/trend_direction/vein/vein_reversal_zones/vein_reversal_zones.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

vein_reversal_zones.pine
//@version=6
// ============================================================================
// Vein: Reversal Zones [WavesUnchained]
// Version: 2.0.3
// Author: WavesUnchained
// Build: 2026-10-02 00:00:00
// ============================================================================
// S/R zones from forward-validated reversal bars + live signal engine.
// Zones show only the nearest N supports/resistances within ATR range.
// Nearby zones merge; touch counter tracks revisits.
//
// Features:
//   ✓ Support zones [low→close] and resistance zones [close→high] of reversal bars
//   ✓ Visibility filter: ATR range + max nearest N per direction
//   ✓ Merge nearby zones (same direction, within ATR threshold)
//   ✓ Touch counter per zone
//   ✓ Optional deletion when price closes fully through zone
//   ✓ 5 selectable signal triggers: WaveTrend, RSI Reclaim, Supertrend, Candle Sweep, EMA/MACD
//   ✓ Signal modes: Known Zones only / Candidates / Both
//   ✓ Cooldown per direction
//
// ============================================================================

indicator("Vein: Reversal Zones [WavesUnchained]", "Vein RZones", overlay=true,
     max_boxes_count=50, max_labels_count=200)

// ─── Groups ──────────────────────────────────────────────────────────────────
g_label   = "Labeling Parameters"
g_quality = "Quality Criteria"
g_cluster = "Cluster Filter"
g_zones   = "Zones"
g_signals = "Signals"
g_wt      = "  Trigger: WaveTrend"
g_rsi     = "  Trigger: RSI Reclaim"
g_st      = "  Trigger: Supertrend"
g_sweep   = "  Trigger: Candle Sweep"
g_ema     = "  Trigger: MA / Momentum"
g_display = "Display"

// ─── Labeling Parameters ─────────────────────────────────────────────────────
int   lblAtrLen     = input.int(14,   "ATR Length",                        minval=1,             group=g_label)
int   forwardBars   = input.int(8,    "Forward Bars (min target)",          minval=1, maxval=50,  group=g_label)
int   extendedBars  = input.int(16,   "Extended Bars (strong target)",      minval=2, maxval=80,  group=g_label)
float targetATR     = input.float(1.5,"Weak Target (ATR×)",                 minval=0.1, step=0.1, group=g_label)
float strongATR_lbl = input.float(2.5,"Strong Target (ATR×)",               minval=0.5, step=0.1, group=g_label)
float maxAdverseATR = input.float(0.6,"Max Adverse Move (ATR×)",            minval=0.1, step=0.1, group=g_label)
bool  useClose      = input.bool(false,"Use Close instead of High/Low",                           group=g_label)

// ─── Quality Criteria ────────────────────────────────────────────────────────
int   fastBarsThresh  = input.int(4,   "Fast Reversal: bars to target ≤",  minval=1, maxval=20,  group=g_quality)
float maeStrongThresh = input.float(0.3,"Strong: Max Adverse ≤ ATR×",       minval=0.0, step=0.1, group=g_quality)

// ─── Cluster Filter ──────────────────────────────────────────────────────────
int minGapBars = input.int(3, "Min Bars Between Labels (same direction)", minval=0, maxval=20, group=g_cluster)

// ─── Zone Settings ───────────────────────────────────────────────────────────
bool  showStrongSupport = input.bool(true,  "Strong Support (label +2)",     group=g_zones)
bool  showWeakSupport   = input.bool(true,  "Weak Support   (label +1)",     group=g_zones)
bool  showStrongResist  = input.bool(true,  "Strong Resistance (label −2)",  group=g_zones)
bool  showWeakResist    = input.bool(true,  "Weak Resistance   (label −1)",  group=g_zones)
int   maxStoredZones    = input.int(40,  "Max Stored Zones",           minval=4, maxval=100,group=g_zones)
int   maxNearSupport    = input.int(2,   "Max Visible Supports (nearest)",   minval=1, maxval=8,  group=g_zones)
int   maxNearResist     = input.int(2,   "Max Visible Resistances (nearest)",minval=1, maxval=8,  group=g_zones)
float visibleAtrRange   = input.float(8.0,"Visible Zone Range (ATR×)",       minval=1.0, step=0.5, group=g_zones)
float mergeDistATR      = input.float(0.5,"Merge Nearby Zones (ATR×, 0=off)",minval=0.0, step=0.1, group=g_zones)
bool  deleteBroken      = input.bool(true, "Delete When Price Closes Through",group=g_zones)
int   extendBars        = input.int(100,  "Extend Right (bars)",             minval=10, maxval=500, group=g_zones)

// ─── Signal Settings ─────────────────────────────────────────────────────────
string signalMode      = input.string("Known Zones", "Signal Mode",
     options=["Known Zones", "Candidates", "Both"], group=g_signals,
     tooltip="Known Zones: fires only when near an existing zone.\nCandidates: fires on trigger regardless of zone.\nBoth: fires always; solid arrow = zone-confluent, outline = candidate only.")
string sigRequirement  = input.string("Any", "Trigger Requirement",
     options=["Any", "2+"], group=g_signals,
     tooltip="Any: one role bucket suffices.\n2+: at least two directional role buckets (Structure, Momentum, Trend, MA Trigger) must fire on the same bar.")
float zoneProxATR      = input.float(0.5, "Zone Proximity (ATR×)", minval=0.1, step=0.1, group=g_signals,
     tooltip="Max distance from the zone (0 when price is inside it) to count as 'in zone' for signal gate and arrow style.")
int   cooldownBars     = input.int(8, "Cooldown After Signal (bars)", minval=0, group=g_signals)

// ─── WaveTrend ───────────────────────────────────────────────────────────────
bool  useWT     = input.bool(true,   "Use WaveTrend Cross",                group=g_wt)
int   wtChLen   = input.int(10,      "  Channel Length", minval=1,         group=g_wt)
int   wtAvgLen  = input.int(21,      "  Average Length", minval=1,         group=g_wt)
float wtOsLevel = input.float(-53.0, "  OS Level (long)",  step=1.0,       group=g_wt)
float wtObLevel = input.float(53.0,  "  OB Level (short)", step=1.0,       group=g_wt)

// ─── RSI Reclaim ─────────────────────────────────────────────────────────────
bool  useRSI      = input.bool(false, "Use RSI Reclaim",                   group=g_rsi)
int   rsiLen      = input.int(14,    "  RSI Length",   minval=1,           group=g_rsi)
float rsiOsLevel  = input.float(35.0,"  OS Level (long)",  step=1.0,       group=g_rsi)
float rsiObLevel  = input.float(65.0,"  OB Level (short)", step=1.0,       group=g_rsi)

// ─── Supertrend ──────────────────────────────────────────────────────────────
bool  useST     = input.bool(true,  "Use Supertrend Flip",                 group=g_st)
int   stAtrLen  = input.int(10,     "  ATR Length",  minval=1,             group=g_st)
float stFactor  = input.float(3.0,  "  Factor",      minval=0.5, step=0.5, group=g_st)

// ─── Candle Sweep ────────────────────────────────────────────────────────────
bool  useSweep  = input.bool(true,  "Use Candle Sweep (liquidity)",        group=g_sweep)
int   sweepLen  = input.int(10,     "  Swing Lookback (bars)", minval=3,   group=g_sweep)

// ─── MA / Momentum ───────────────────────────────────────────────────────────
bool   useEMA    = input.bool(true,   "Use MA Reclaim + MACD",              group=g_ema)
int    emaLen    = input.int(20,      "  MA Length",    minval=1,           group=g_ema)
string emaType   = input.string("RMA","  MA Type", options=["EMA","RMA","SMA","WMA","HMA","JMA"], group=g_ema)
int    jmaPhase  = input.int(50,      "  JMA Phase",   minval=-100, maxval=100, group=g_ema,
     tooltip="Only relevant when MA Type = JMA.")
float  jmaPower  = input.float(2.0,   "  JMA Power",   minval=1.0, maxval=4.0, step=0.25, group=g_ema,
     tooltip="Only relevant when MA Type = JMA.")
int   macdFast  = input.int(12,      "  MACD Fast",    minval=1,           group=g_ema)
int   macdSlow  = input.int(26,      "  MACD Slow",    minval=1,           group=g_ema)
int   macdSig   = input.int(9,       "  MACD Signal",  minval=1,           group=g_ema)

// ─── Display ─────────────────────────────────────────────────────────────────
color strongSuppColor = input.color(color.new(#00c853, 0), "Strong Support Color",    group=g_display)
color weakSuppColor   = input.color(color.new(#00c853, 0), "Weak Support Color",      group=g_display)
color strongResColor  = input.color(color.new(#d50000, 0), "Strong Resistance Color", group=g_display)
color weakResColor    = input.color(color.new(#d50000, 0), "Weak Resistance Color",   group=g_display)
bool  showTouches     = input.bool(true, "Show Touch Count on Zone",       group=g_display)
bool  showGrade       = input.bool(true, "Show Grade Label (★★ / ★)",      group=g_display)

// ─── Labeler Core ────────────────────────────────────────────────────────────
atrVal = ta.atr(lblAtrLen)
// Relative volatility: current ATR vs its 50-bar mean (feeds the Volatility role in the confluence score)
float relVol = atrVal / ta.sma(atrVal, 50)

int bufSize    = math.max(forwardBars, extendedBars) + 1
int evalOffset = bufSize - 1

var array<float> _bufClose = array.new_float(bufSize, na)
var array<float> _bufHigh  = array.new_float(bufSize, na)
var array<float> _bufLow   = array.new_float(bufSize, na)
var array<float> _bufATR   = array.new_float(bufSize, na)

array.shift(_bufClose)
array.push(_bufClose, close)
array.shift(_bufHigh)
array.push(_bufHigh, high)
array.shift(_bufLow)
array.push(_bufLow, low)
array.shift(_bufATR)
array.push(_bufATR, atrVal)

float _startClose = array.get(_bufClose, 0)
float _startATR   = array.get(_bufATR,   0)
bool  _canEval    = not na(_startClose) and not na(_startATR) and _startATR > 0 and bar_index >= evalOffset

f_evalBull(array<float> hi, array<float> lo, array<float> cl, int bars, float refC, float atr, float tgt, float maxAdv, bool clOnly) =>
    float mae   = 0.0
    int   barsT = bars
    bool  hit   = false
    bool  stop  = false
    for i = 1 to bars
        float bHi = clOnly ? array.get(cl, i) : array.get(hi, i)
        float bLo = clOnly ? array.get(cl, i) : array.get(lo, i)
        // First touch wins: once the target is hit, later adverse moves no longer count
        if not hit
            mae := math.max(mae, refC - bLo)
        if not hit and not stop and (refC - bLo) > maxAdv * atr
            stop := true
        if not hit and not stop and (bHi - refC) >= tgt * atr
            hit   := true
            barsT := i
    [hit and not stop, mae, barsT]

f_evalBear(array<float> hi, array<float> lo, array<float> cl, int bars, float refC, float atr, float tgt, float maxAdv, bool clOnly) =>
    float mae   = 0.0
    int   barsT = bars
    bool  hit   = false
    bool  stop  = false
    for i = 1 to bars
        float bHi = clOnly ? array.get(cl, i) : array.get(hi, i)
        float bLo = clOnly ? array.get(cl, i) : array.get(lo, i)
        // First touch wins: once the target is hit, later adverse moves no longer count
        if not hit
            mae := math.max(mae, bHi - refC)
        if not hit and not stop and (bHi - refC) > maxAdv * atr
            stop := true
        if not hit and not stop and (refC - bLo) >= tgt * atr
            hit   := true
            barsT := i
    [hit and not stop, mae, barsT]

var bool  _bullWeak      = false
var float _bullMAE       = 0.0
var int   _bullBarsToTgt = 0
var bool  _bearWeak      = false
var float _bearMAE       = 0.0
var int   _bearBarsToTgt = 0

if _canEval
    [h1, a1, b1] = f_evalBull(_bufHigh, _bufLow, _bufClose, forwardBars, _startClose, _startATR, targetATR, maxAdverseATR, useClose)
    _bullWeak      := h1
    _bullMAE       := a1
    _bullBarsToTgt := b1
    [h2, a2, b2] = f_evalBear(_bufHigh, _bufLow, _bufClose, forwardBars, _startClose, _startATR, targetATR, maxAdverseATR, useClose)
    _bearWeak      := h2
    _bearMAE       := a2
    _bearBarsToTgt := b2
else
    _bullWeak      := false
    _bullMAE       := 0.0
    _bullBarsToTgt := forwardBars
    _bearWeak      := false
    _bearMAE       := 0.0
    _bearBarsToTgt := forwardBars

var bool _bullStrong = false
var bool _bearStrong = false

if _canEval
    [h3, a3, b3] = f_evalBull(_bufHigh, _bufLow, _bufClose, extendedBars, _startClose, _startATR, strongATR_lbl, maxAdverseATR, useClose)
    _bullStrong := h3
    [h4, a4, b4] = f_evalBear(_bufHigh, _bufLow, _bufClose, extendedBars, _startClose, _startATR, strongATR_lbl, maxAdverseATR, useClose)
    _bearStrong := h4
else
    _bullStrong := false
    _bearStrong := false

bool _bullIsStrong = _bullStrong or (_bullWeak and _bullBarsToTgt <= fastBarsThresh and _startATR > 0 and _bullMAE <= maeStrongThresh * _startATR)
bool _bullIsWeak   = _bullWeak and not _bullIsStrong
bool _bearIsStrong = _bearStrong or (_bearWeak and _bearBarsToTgt <= fastBarsThresh and _startATR > 0 and _bearMAE <= maeStrongThresh * _startATR)
bool _bearIsWeak   = _bearWeak and not _bearIsStrong

int _rawLabel = _bullIsStrong ? 2 : _bullIsWeak ? 1 : _bearIsStrong ? -2 : _bearIsWeak ? -1 : 0
if (_bullIsStrong or _bullIsWeak) and (_bearIsStrong or _bearIsWeak)
    _rawLabel := 0

var int _barsSinceLastBull = 999
var int _barsSinceLastBear = 999
_barsSinceLastBull += 1
_barsSinceLastBear += 1

int reversalLabel = _rawLabel
if minGapBars > 0
    if _rawLabel > 0 and _barsSinceLastBull < minGapBars
        reversalLabel := 0
    if _rawLabel < 0 and _barsSinceLastBear < minGapBars
        reversalLabel := 0

if reversalLabel > 0
    _barsSinceLastBull := 0
if reversalLabel < 0
    _barsSinceLastBear := 0

// ─── MA Helper ───────────────────────────────────────────────────────────────
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)

// ─── Signal Triggers ─────────────────────────────────────────────────────────

// WaveTrend
_wtSrc = hlc3
_wtEsa = ta.ema(_wtSrc, wtChLen)
_wtD   = ta.ema(math.abs(_wtSrc - _wtEsa), wtChLen)
_wtCi  = _wtD != 0 ? (_wtSrc - _wtEsa) / (0.015 * _wtD) : 0.0
_wt1   = ta.ema(_wtCi, wtAvgLen)
_wt2   = ta.sma(_wt1, 4)
wtLong  = useWT and ta.crossover(_wt1, _wt2)  and _wt1 <= wtOsLevel
wtShort = useWT and ta.crossunder(_wt1, _wt2) and _wt1 >= wtObLevel

// RSI Reclaim
_rsi     = ta.rsi(close, rsiLen)
rsiLong  = useRSI and ta.crossover(_rsi, rsiOsLevel)
rsiShort = useRSI and ta.crossunder(_rsi, rsiObLevel)

// Supertrend Flip
[_stLine, _stDir] = ta.supertrend(stFactor, stAtrLen)
stLong   = useST and _stDir < 0 and nz(_stDir[1], _stDir) > 0
stShort  = useST and _stDir > 0 and nz(_stDir[1], _stDir) < 0

// Candle Sweep (liquidity grab + close recovery)
_swingLo  = ta.lowest(low,  sweepLen)[1]
_swingHi  = ta.highest(high, sweepLen)[1]
sweepLong  = useSweep and low  < _swingLo and close > _swingLo
sweepShort = useSweep and high > _swingHi and close < _swingHi

// MA Reclaim + MACD momentum
_maReclaim = f_smooth(close, emaLen, emaType)
_macdLine  = ta.ema(close, macdFast) - ta.ema(close, macdSlow)
_macdHist  = _macdLine - ta.ema(_macdLine, macdSig)
emaLong    = useEMA and ta.crossover(close, _maReclaim)  and _macdHist > _macdHist[1]
emaShort   = useEMA and ta.crossunder(close, _maReclaim) and _macdHist < _macdHist[1]

// Aggregate by role, not by raw indicator count.
bool momentumLong = wtLong or rsiLong
bool momentumShort = wtShort or rsiShort
bool trendLong = stLong
bool trendShort = stShort
bool structureLong = sweepLong
bool structureShort = sweepShort
bool triggerLong = emaLong
bool triggerShort = emaShort

// Confluence scoring based on role weights:
// Structure (Sweep) = 3.0, Momentum (RSI/WT) = 2.0, Volatility (relVol > 1.2) = 2.0, MA/Trigger (EMA/MACD) = 2.0, Trend (ST flip) = 1.0
float conflScoreLong = (structureLong ? 3.0 : 0.0) + (momentumLong ? 2.0 : 0.0) + (relVol > 1.2 ? 2.0 : 0.0) + (emaLong ? 2.0 : 0.0) + (stLong ? 1.0 : 0.0)
float conflScoreShort = (structureShort ? 3.0 : 0.0) + (momentumShort ? 2.0 : 0.0) + (relVol > 1.2 ? 2.0 : 0.0) + (emaShort ? 2.0 : 0.0) + (stShort ? 1.0 : 0.0)

// A valid reversal confluence requires at least 5.0 score AND a primary reversal catalyst (either structure sweep or momentum extreme).
// Trigger Requirement: "2+" additionally needs at least two directional role buckets
// (Structure, Momentum, Trend, MA Trigger) firing on the same bar.
int  roleCountLong  = (structureLong ? 1 : 0) + (momentumLong ? 1 : 0) + (trendLong ? 1 : 0) + (triggerLong ? 1 : 0)
int  roleCountShort = (structureShort ? 1 : 0) + (momentumShort ? 1 : 0) + (trendShort ? 1 : 0) + (triggerShort ? 1 : 0)
bool reqOkLong  = sigRequirement == "Any" or roleCountLong >= 2
bool reqOkShort = sigRequirement == "Any" or roleCountShort >= 2
bool anyLong  = conflScoreLong >= 5.0 and (structureLong or momentumLong) and reqOkLong
bool anyShort = conflScoreShort >= 5.0 and (structureShort or momentumShort) and reqOkShort

float trigLong  = conflScoreLong
float trigShort = conflScoreShort

// ─── Zone Data Arrays ────────────────────────────────────────────────────────
var array<int>   _zDir      = array.new<int>()
var array<int>   _zGrade    = array.new<int>()
var array<float> _zUpper    = array.new<float>()
var array<float> _zLower    = array.new<float>()
var array<int>   _zTouches  = array.new<int>()
var array<bool>  _zInZone   = array.new<bool>()
var array<int>   _zTime     = array.new<int>()

// Render handles rebuilt on barstate.islast
var array<box>   _rBox = array.new<box>()
var array<label> _rLbl = array.new<label>()

// ─── Add Zone When Label Fires ────────────────────────────────────────────────
if reversalLabel != 0
    int   zDir   = reversalLabel > 0 ? 1 : -1
    int   zGrade = math.abs(reversalLabel)
    bool  doAdd  = (zDir ==  1 and zGrade == 2 and showStrongSupport) or
                   (zDir ==  1 and zGrade == 1 and showWeakSupport)   or
                   (zDir == -1 and zGrade == 2 and showStrongResist)  or
                   (zDir == -1 and zGrade == 1 and showWeakResist)

    if doAdd
        float zU    = zDir == 1 ? _startClose : array.get(_bufHigh, 0)
        float zL    = zDir == 1 ? array.get(_bufLow, 0) : _startClose
        float minH  = 0.2 * _startATR
        if zU - zL < minH
            if zDir == 1
                zL := zU - minH
            else
                zU := zL + minH

        // Merge with overlapping zone of same direction
        bool merged = false
        if mergeDistATR > 0.0 and array.size(_zDir) > 0
            float mergeThresh = mergeDistATR * _startATR
            for i = 0 to array.size(_zDir) - 1
                if not merged and array.get(_zDir, i) == zDir
                    float zu = array.get(_zUpper, i)
                    float zl = array.get(_zLower, i)
                    if zL <= zu + mergeThresh and zU >= zl - mergeThresh
                        array.set(_zUpper,  i, math.max(zu, zU))
                        array.set(_zLower,  i, math.min(zl, zL))
                        array.set(_zGrade,  i, math.max(array.get(_zGrade, i), zGrade))
                        merged := true

        if not merged
            if array.size(_zDir) >= maxStoredZones
                array.shift(_zDir)
                array.shift(_zGrade)
                array.shift(_zUpper)
                array.shift(_zLower)
                array.shift(_zTouches)
                array.shift(_zInZone)
                array.shift(_zTime)
            array.push(_zDir,     zDir)
            array.push(_zGrade,   zGrade)
            array.push(_zUpper,   zU)
            array.push(_zLower,   zL)
            array.push(_zTouches, 0)
            array.push(_zInZone,  false)
            array.push(_zTime,    time[evalOffset])

// ─── Zone Update: Touches + Break Deletion ────────────────────────────────────
int _n = array.size(_zDir)
if _n > 0
    for i = _n - 1 to 0
        float zu  = array.get(_zUpper, i)
        float zl  = array.get(_zLower, i)
        int   zd  = array.get(_zDir,   i)
        bool  inZ = close >= zl and close <= zu
        if inZ and not array.get(_zInZone, i)
            array.set(_zTouches, i, array.get(_zTouches, i) + 1)
        array.set(_zInZone, i, inZ)
        if deleteBroken and ((zd == 1 and close < zl) or (zd == -1 and close > zu))
            array.remove(_zDir,     i)
            array.remove(_zGrade,   i)
            array.remove(_zUpper,   i)
            array.remove(_zLower,   i)
            array.remove(_zTouches, i)
            array.remove(_zInZone,  i)
            array.remove(_zTime,    i)

// ─── Signal Gate + Cooldown ──────────────────────────────────────────────────
bool _nearSupport = false
bool _nearResist  = false
float _proxThresh = zoneProxATR * atrVal

int _nz = array.size(_zDir)
if _nz > 0
    for i = 0 to _nz - 1
        float zuG  = array.get(_zUpper, i)
        float zlG  = array.get(_zLower, i)
        int   zd   = array.get(_zDir, i)
        // Distance to the zone itself (0 inside), not to its mid — wide zones stay reachable from their edges
        float dist = close > zuG ? close - zuG : close < zlG ? zlG - close : 0.0
        if dist <= _proxThresh
            if zd == 1
                _nearSupport := true
            else
                _nearResist := true

bool _gateL = signalMode != "Known Zones" or _nearSupport
bool _gateS = signalMode != "Known Zones" or _nearResist

var int _lastLongBar  = na
var int _lastShortBar = na
bool _cdL = na(_lastLongBar)  or (bar_index - _lastLongBar)  > cooldownBars
bool _cdS = na(_lastShortBar) or (bar_index - _lastShortBar) > cooldownBars

bool goLong  = anyLong  and _gateL and _cdL
bool goShort = anyShort and _gateS and _cdS

if goLong
    _lastLongBar  := bar_index
if goShort
    _lastShortBar := bar_index

// ─── Signal Labels ────────────────────────────────────────────────────────────
// Solid arrow = zone-confluent; outline = candidate only
color _cBull = #00c853
color _cBear = #d50000

if goLong
    bool zoneConfl = _nearSupport
    string _ttTriggers = (wtLong    ? "✓ WaveTrend Cross\n" : "") +
                         (rsiLong   ? "✓ RSI Reclaim\n"     : "") +
                         (stLong    ? "✓ Supertrend Flip\n" : "") +
                         (sweepLong ? "✓ Candle Sweep\n"    : "") +
                         (emaLong   ? "✓ EMA Reclaim\n"     : "")
    string _ttZone  = zoneConfl ? "Zone: Support confluence\n" : "Zone: No zone nearby (candidate)\n"
    string _ttScore = "Confluence Score: " + str.tostring(trigLong, "#.#") + " / 10\n"
    string _ttAtr   = "ATR: " + str.tostring(atrVal, format.mintick)
    label.new(bar_index, close,
         zoneConfl ? "▲" : "△",
         style     = label.style_label_up,
         color     = color.new(_cBull, zoneConfl ? 10 : 60),
         textcolor = color.white,
         size      = size.small,
         tooltip   = "LONG SIGNAL\n" + _ttZone + _ttScore + _ttTriggers + _ttAtr)

if goShort
    bool zoneConfl = _nearResist
    string _ttTriggers = (wtShort    ? "✓ WaveTrend Cross\n" : "") +
                         (rsiShort   ? "✓ RSI Reclaim\n"     : "") +
                         (stShort    ? "✓ Supertrend Flip\n" : "") +
                         (sweepShort ? "✓ Candle Sweep\n"    : "") +
                         (emaShort   ? "✓ EMA Reclaim\n"     : "")
    string _ttZone  = zoneConfl ? "Zone: Resistance confluence\n" : "Zone: No zone nearby (candidate)\n"
    string _ttScore = "Confluence Score: " + str.tostring(trigShort, "#.#") + " / 10\n"
    string _ttAtr   = "ATR: " + str.tostring(atrVal, format.mintick)
    label.new(bar_index, close,
         zoneConfl ? "▼" : "▽",
         style     = label.style_label_down,
         color     = color.new(_cBear, zoneConfl ? 10 : 60),
         textcolor = color.white,
         size      = size.small,
         tooltip   = "SHORT SIGNAL\n" + _ttZone + _ttScore + _ttTriggers + _ttAtr)

// ─── Zone Rendering (barstate.islast) ─────────────────────────────────────────
if barstate.islast
    // Clear previous render
    int rn = array.size(_rBox)
    if rn > 0
        for i = 0 to rn - 1
            box.delete(array.get(_rBox, i))
            label.delete(array.get(_rLbl, i))
    array.clear(_rBox)
    array.clear(_rLbl)

    int nz = array.size(_zDir)
    if nz > 0
        float visRange = visibleAtrRange * atrVal

        // Select nearest N supports + N resistances using picked-index tracking
        var array<int> _allPicked = array.new<int>()
        array.clear(_allPicked)

        // Supports
        for _ = 0 to maxNearSupport - 1
            float bestDist = 1.0e10
            int   bestIdx  = -1
            for i = 0 to nz - 1
                if array.get(_zDir, i) == 1
                    float zMid = (array.get(_zUpper, i) + array.get(_zLower, i)) / 2.0
                    float dist = math.abs(close - zMid)
                    if dist <= visRange and dist < bestDist
                        bool already = false
                        int np = array.size(_allPicked)
                        if np > 0
                            for j = 0 to np - 1
                                if array.get(_allPicked, j) == i
                                    already := true
                        if not already
                            bestDist := dist
                            bestIdx  := i
            if bestIdx >= 0
                array.push(_allPicked, bestIdx)

        // Resistances
        for _ = 0 to maxNearResist - 1
            float bestDist = 1.0e10
            int   bestIdx  = -1
            for i = 0 to nz - 1
                if array.get(_zDir, i) == -1
                    float zMid = (array.get(_zUpper, i) + array.get(_zLower, i)) / 2.0
                    float dist = math.abs(close - zMid)
                    if dist <= visRange and dist < bestDist
                        bool already = false
                        int np = array.size(_allPicked)
                        if np > 0
                            for j = 0 to np - 1
                                if array.get(_allPicked, j) == i
                                    already := true
                        if not already
                            bestDist := dist
                            bestIdx  := i
            if bestIdx >= 0
                array.push(_allPicked, bestIdx)

        int ap = array.size(_allPicked)
        if ap > 0
            for k = 0 to ap - 1
                int   i  = array.get(_allPicked, k)
                int   zd = array.get(_zDir,     i)
                int   zg = array.get(_zGrade,   i)
                float zu = array.get(_zUpper,   i)
                float zl = array.get(_zLower,   i)
                int   zt  = array.get(_zTouches, i)
                int   zts = array.get(_zTime,    i)

                color baseC   = zd == 1 ? (zg == 2 ? strongSuppColor : weakSuppColor) : (zg == 2 ? strongResColor : weakResColor)
                int   bgAlpha = zg == 2 ? 75 : 92
                int   bdAlpha = zg == 2 ? 20 : 68

                int x2t = time + extendBars * timeframe.in_seconds() * 1000
                b = box.new(zts, zu, x2t, zl,
                     bgcolor      = color.new(baseC, bgAlpha),
                     border_color = color.new(baseC, bdAlpha),
                     border_width = 1,
                     xloc         = xloc.bar_time)

                string gradeStr = showGrade   ? (zg == 2 ? "★★" : "★") : ""
                string touchStr = showTouches and zt > 0 ? " ×" + str.tostring(zt) : ""
                l = label.new(x2t, (zu + zl) / 2.0, gradeStr + touchStr,
                     style     = label.style_label_left,
                     color     = color.new(color.black, 100),
                     textcolor = color.new(baseC, 15),
                     size      = size.small,
                     xloc      = xloc.bar_time)

                array.push(_rBox, b)
                array.push(_rLbl, l)

// ─── Data Window ─────────────────────────────────────────────────────────────
// Offset aligned to the evaluated bar (evalOffset, not extendedBars — the two differ
// when Forward Bars > Extended Bars)
plot(reversalLabel,        "Reversal Label",  color=color.new(color.gray, 100), display=display.data_window, offset=-evalOffset)
plot(array.size(_zDir),    "Stored Zones",    color=color.new(color.gray, 100), display=display.data_window)
plot(goLong  ? 1 : 0,      "Long Signal",     color=color.new(color.gray, 100), display=display.data_window)
plot(goShort ? 1 : 0,      "Short Signal",    color=color.new(color.gray, 100), display=display.data_window)
plot(_nearSupport ? 1 : 0, "Near Support",    color=color.new(color.gray, 100), display=display.data_window)
plot(_nearResist  ? 1 : 0, "Near Resistance", color=color.new(color.gray, 100), display=display.data_window)

Overview

Weak and strong labels depend on travel distance, adverse move, and how quickly the target is hit. Nearby same-direction zones merge, a touch counter tracks revisits, and a full close through a zone can delete it. Optional signals fire near stored zones from WaveTrend, RSI reclaim, Supertrend, candle sweep, or moving-average and MACD triggers, with cooldown and selectable known-zone modes. Qualification uses later bars, so a label is not known when the reversal bar forms.

Details

Published by

casoon on GitHub

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

Support & resistance, Oscillators & momentum, Trend

Markets

Any symbol

Timeframes

Any timeframe

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
ATR Lengthint14lblAtrLen
Forward Bars (min target)int8forwardBars
Extended Bars (strong target)int16extendedBars
Weak Target (ATR×)float1.5targetATR
Strong Target (ATR×)float2.5strongATR_lbl
Max Adverse Move (ATR×)float0.6maxAdverseATR
Use Close instead of High/LowboolfalseuseClose
Fast Reversal: bars to target ≤int4fastBarsThresh
Strong: Max Adverse ≤ ATR×float0.3maeStrongThresh
Min Bars Between Labels (same direction)int3minGapBars
Strong Support (label +2)booltrueshowStrongSupport
Weak Support (label +1)booltrueshowWeakSupport
Show all 40 settings
SettingTypeDefaultVariable
Strong Resistance (label −2)booltrueshowStrongResist
Weak Resistance (label −1)booltrueshowWeakResist
Max Stored Zonesint40maxStoredZones
Max Visible Supports (nearest)int2maxNearSupport
Max Visible Resistances (nearest)int2maxNearResist
Visible Zone Range (ATR×)float8.0visibleAtrRange
Merge Nearby Zones (ATR×, 0=off)float0.5mergeDistATR
Delete When Price Closes ThroughbooltruedeleteBroken
Extend Right (bars)int100extendBars
Signal ModestringKnown ZonessignalMode
Trigger RequirementstringAnysigRequirement
Zone Proximity (ATR×)float0.5zoneProxATR
Cooldown After Signal (bars)int8cooldownBars
Use WaveTrend CrossbooltrueuseWT
Channel Lengthint10wtChLen
Average Lengthint21wtAvgLen
OS Level (long)float-53.0wtOsLevel
OB Level (short)float53.0wtObLevel
Use RSI ReclaimboolfalseuseRSI
RSI Lengthint14rsiLen
OS Level (long)float35.0rsiOsLevel
OB Level (short)float65.0rsiObLevel
Use Supertrend FlipbooltrueuseST
ATR Lengthint10stAtrLen
Factorfloat3.0stFactor
Use Candle Sweep (liquidity)booltrueuseSweep
Swing Lookback (bars)int10sweepLen
Use MA Reclaim + MACDbooltrueuseEMA
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

114 stars

This indicator calculates key support and resistance levels by analyzing the first and second momentum derivatives of a Double Exponential Moving Average (DEMA). When the second derivative (momentum of momentum) crosses zero, it identifies local turning points

Indicator v4 Support & resistanceOscillators & momentum
Updated 1 year ago · None stated GitHub repository of Support and Resistance Levels

Moon Dev Lunar Sniper

by moondevonyt
92 stars

Moon Dev Lunar Sniper is a multi-indicator overlay that scores confluence across fast and slow EMAs, RSI, MACD, DMI, and BB width to fire signals only when score meets the minimum threshold. It plots buy and sell signals on the price chart, highlights cosmic b

Indicator v6 TrendOscillators & momentum Repaints
Updated 8 weeks ago · Custom licence GitHub repository of Moon Dev Lunar Sniper
14 stars

Self-Aware Trend System calculates volatility-adaptive ATR trailing bands using a Trend Quality Index engine that evaluates market efficiency, momentum persistence, and structural pivots. It plots adaptive trend bands and character-flip trend signals directly

Indicator v6 TrendVolatility & bands
Updated 2 weeks ago · MIT GitHub repository of Self-Aware Trend System [WillyAlgoTrader]

SPY 0DTE Scalper - 15min

by chris-c-thomas
14 stars

This intraday overlay establishes directional bias and high-conviction swing setups for SPY on the 15-minute timeframe. It computes a weighted confluence score combining an EMA ribbon, a 50 EMA anchor, session VWAP bands, RSI divergence, ADX, TTM Squeeze, cand

Indicator v6 TrendOscillators & momentum
Updated 6 months ago · MIT GitHub repository of SPY 0DTE Scalper - 15min

Market Trend

by azamiftikhar1000
12 stars

Market Trend is an overlay indicator that identifies market direction and key price levels. It calculates a Relative Strength Index with Bollinger Bands to construct a 20-step color gradient reflecting momentum shifts. The script incorporates exponential movin

Indicator v4 TrendOscillators & momentum
Updated 3 years ago · None stated GitHub repository of Market Trend

BTE Acc AO SMIO

by f-cksociety
11 stars

This script provides an indicator-based backtesting engine framework that simulates trade entries, exits, and filtering directly on the price chart. It evaluates selected entry rules and multi-layered filters, including moving average trends, volume increases,

Indicator v5 TrendOscillators & momentum
Updated 4 years ago · MPL-2.0 GitHub repository of BTE Acc AO SMIO

More indicators

Trade Your Charts With a Regulated Broker

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

Compare brokers