Strategy · Pine Script v6 · Own pane

WaveTrend v4 [WavesUnchained] Strategy

by casoon

The WaveTrend v4 Strategy generates trading signals when the WaveTrend oscillator crosses at oversold or overbought levels.

View on GitHub

Repaints

GitHub stars9on the repository
Pine versionv6current
Lines of code610
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 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

610 lines of Pine Script v6 from archive/strategies/wavetrend/wavetrend_base_strategy.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

wavetrend_base_strategy.pine
//@version=6
// ============================================================================
// WaveTrend v4 [WavesUnchained]
// Version: 4.45
// Author: WavesUnchained
// Build: 2026-05-04
// ============================================================================
// 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 / 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)

// ─── Inputs ──────────────────────────────────────────────────────────────────
g_wt      = "WaveTrend"
g_srsi    = "Stochastic RSI"
g_risk    = "Risk"
g_be      = "Break-Even"
g_strat   = "Strategy"
g_pivot   = "Pivots"
g_filters = "Filters"

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)
osLevel      = input.float(-5, "OS Level (long entry)",  step=1,                group=g_wt)
obLevel      = input.float(5,  "OB Level (short entry)", step=1,                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)
tpMult  = input.float(3.0, "TP (ATR×)",  minval=0.1, step=0.1,             group=g_risk)

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)

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(false, "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.")
structMinR1H  = input.float(1.5,  "  Struct Exit Min Profit 1H (R×)", minval=0.0, step=0.1, group=g_strat)
structMinR4H  = input.float(2.0,  "  Struct Exit Min Profit 4H (R×)", minval=0.0, step=0.1, group=g_strat)
structMinR1D  = input.float(0.5,  "  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.")

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)
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)
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)
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)
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)

// ─── 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() == 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

// ─── 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 == 3600 or _tfSec == 14400 or _tfSec == 86400
_minAtrRank  = _tfSec == 3600 ? minAtrRank1H : _tfSec == 14400 ? minAtrRank4H : minAtrRank1D
_maxAtrRank  = _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 == 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 == 3600 ? sweepLookback1H : _tfSec == 14400 ? sweepLookback4H : sweepLookback1D
_sweepValidBars = _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

// ─── Signals ─────────────────────────────────────────────────────────────────
_wtLongRaw  = ta.crossover(wt1, wt2)  and wt1 <= osLevel
_wtShortRaw = ta.crossunder(wt1, wt2) and wt1 >= obLevel
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

varip int _lastExitBar = na

// Frozen at signal bar — used for SL/TP and exit log
var float _pendingLongAtr    = na
var float _pendingShortAtr   = na
var int   _pendingLowPivBars  = na
var int   _pendingHighPivBars = na

// Entry metadata kept for exit log
varip float _execRisk        = na
varip int   _execLowPivBars  = na
varip int   _execHighPivBars = na

// 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
    _execRisk        := _longRisk
    _execLowPivBars  := na(_pendingLowPivBars)  ? barsSinceLowPiv  : _pendingLowPivBars
    _execHighPivBars := na(_pendingHighPivBars) ? barsSinceHighPiv : _pendingHighPivBars
    _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
    _execRisk        := _shortRisk
    _execLowPivBars  := na(_pendingLowPivBars)  ? barsSinceLowPiv  : _pendingLowPivBars
    _execHighPivBars := na(_pendingHighPivBars) ? barsSinceHighPiv : _pendingHighPivBars
    _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
        if _i < strategy.closedtrades
            _epF   = strategy.closedtrades.entry_price(_i)
            _szF   = strategy.closedtrades.size(_i)
            _ebarF = strategy.closedtrades.entry_bar_index(_i)
            _dirF  = _szF > 0 ? "long" : "short"
            log.info(str.format('WT4 ENTRY FILL | tradeId={0} | dir={1} | bar={2} | entry={3} | tf={4} | source=closed',
                 _i + 1, _dirF, _ebarF, str.tostring(_epF, format.mintick), timeframe.period))
        else
            _idxOpen = _i - strategy.closedtrades
            _epF   = strategy.opentrades.entry_price(_idxOpen)
            _szF   = strategy.opentrades.size(_idxOpen)
            _ebarF = strategy.opentrades.entry_bar_index(_idxOpen)
            _dirF  = _szF > 0 ? "long" : "short"
            log.info(str.format('WT4 ENTRY FILL | tradeId={0} | dir={1} | bar={2} | entry={3} | tf={4} | source=open',
                 _i + 1, _dirF, _ebarF, str.tostring(_epF, format.mintick), timeframe.period))
    _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 ───────────────────────────────────────────────
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
    _longStop = _longBE ? strategy.position_avg_price : _longSL
    strategy.exit("Long Exit", "Long", stop=_longStop, limit=_longTP,
         comment_profit="tp", comment_loss=_longBE ? "be" : "sl")

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
    _shortStop = _shortBE ? strategy.position_avg_price : _shortSL
    strategy.exit("Short Exit", "Short", stop=_shortStop, limit=_shortTP,
         comment_profit="tp", comment_loss=_shortBE ? "be" : "sl")

// ─── Structural exit (unconfirmed pivot) ─────────────────────────────────────
_structMinR = _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(_execRisk) and _execRisk > 0 ? (close - strategy.position_avg_price) / _execRisk : 0.0
    _shortProfitR  = not na(_execRisk) and _execRisk > 0 ? (strategy.position_avg_price - close) / _execRisk : 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")

// ─── 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) =>
    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"
    _r

_blockLong  = f_blockReason(_wtLongRaw,  srsiLongOk,  _cooldownOk, _inSession, _inDateRange, _tfOk, _volOk, volExpansionGate, chopGate, pivotLongGate,  pivotDistLongGate,  sweepLongGate)
_blockShort = f_blockReason(_wtShortRaw, srsiShortOk, _cooldownOk, _inSession, _inDateRange, _tfOk, _volOk, volExpansionGate, chopGate, pivotShortGate, pivotDistShortGate, sweepShortGate)

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} | tf={12}',
         _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, '#.##'),
         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} | tf={12}',
         _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, '#.##'),
         timeframe.period))

_longEntry  = longSignal  and canLong  and strategy.position_size == 0 and not _exitLockThisBar and _cooldownOk and _canTrade and pivotLongGate  and pivotDistLongGate  and sweepLongGate
_shortEntry = shortSignal and canShort and strategy.position_size == 0 and not _exitLockThisBar and _cooldownOk and _canTrade and pivotShortGate and pivotDistShortGate and sweepShortGate

if _longEntry
    _pendingLongAtr    := atrVal
    _pendingLowPivBars  := barsSinceLowPiv
    _pendingHighPivBars := barsSinceHighPiv
    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} | tf={16}',
         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,
         timeframe.period))
    strategy.entry("Long", strategy.long)

if _shortEntry
    _pendingShortAtr    := atrVal
    _pendingLowPivBars  := barsSinceLowPiv
    _pendingHighPivBars := barsSinceHighPiv
    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} | tf={16}',
         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,
         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"
        _riskDenom = math.max(slMult * atrVal, 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,
             strategy.closedtrades.entry_bar_index(_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(_execLowPivBars),
             str.tostring(_execHighPivBars),
             _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, '#.#'))

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

Long positions enter on bullish crosses in oversold territory, while short positions enter on bearish crosses in overbought territory. It features configurable smoothing algorithms such as HMA and JMA, an optional Stochastic RSI gate, and post-exit cooldowns. Risk management relies on frozen ATR-based stop-loss and take-profit levels with optional break-even stops. Drawn in a separate pane, the strategy may repaint depending on structural pivot exit configurations.

Details

Published by

casoon on GitHub

Type

Strategy, Pine Script v6

Draws

Separate pane below the chart

Category

Oscillators & momentum, Trend, Volatility & bands

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
Channel Lengthint10chLen
Average Lengthint21avgLen
Signal Smoothingint4sigLen
OS Level (long entry)float-5osLevel
OB Level (short entry)float5obLevel
Smoothing 1HstringHMAsmooth1H
Smoothing 4HstringJMAsmooth4H
Smoothing 1DstringSMAsmooth1D
JMA Phaseint50jmaPhase
JMA Powerfloat2.0jmaPower
RSI Lengthint14srsiRsiLen
Stoch Lengthint14srsiStochLen
Show all 40 settings
SettingTypeDefaultVariable
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 (ATR×)float3.0tpMult
Enable Break-Even StopbooltrueenableBE
Trigger (R×)float1.0beTrigR
Show Entry Labels on ChartbooltrueshowSignals
Trade DirectionstringBothdirMode
Cooldown Bars After Exitint0cooldown
Structural Exit (unconfirmed pivot)boolfalseuseStructExit
Struct Exit Min Profit 1H (R×)float1.5structMinR1H
Struct Exit Min Profit 4H (R×)float2.0structMinR4H
Struct Exit Min Profit 1D (R×)float0.5structMinR1D
Pivot Lengthint5pivLen
Session FilterboolfalseuseSession
Sessionsession0000-2345sessionInput
Date RangeboolfalseuseDateRange
Fromtimetimestamp("01 Jan 2020 00:00 +0000")dateFrom
Totimetimestamp("31 Dec 2030 23:59 +0000")dateTo
Max DrawdownboolfalseuseMaxDD
Max Drawdown (%)float20.0maxDD
Max Intraday LossboolfalseuseMaxIntraDD
Max Intraday Loss (%)float5.0maxIntraDD
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

106 stars

This strategy captures strong bullish momentum by combining a 200-period SMA baseline, a custom Bollinger Band, the True Strength Index (TSI) smoothed with WMAs, and the ADX trend strength indicator. It initiates long positions when price is above the 200 SMA,

Strategy v4 TrendOscillators & momentum
Updated 2 years ago · GPL-3.0 GitHub repository of ADX_TSI_Bol Band Trend Chaser

EMA Ribbon MACD

by iamrichardD
57 stars

This strategy combines an EMA ribbon with MACD confirmation and plots in a separate pane. It enters when ribbon alignment, optional MACD zero or signal crosses, volume, smart-money, and multi-timeframe filters agree, then exits with percentage stop loss and ta

Strategy v6 TrendMoving averages
Updated 1 year ago · Apache-2.0 GitHub repository of EMA Ribbon MACD
6 stars

This strategy identifies high-volatility expansion bars by comparing the high-low range to a moving average plus standard deviation threshold. When range expansion occurs, it checks momentum over a specified lookback period. It opens a long position if the cur

Strategy v5 Volatility & bandsOscillators & momentum
Updated 1 year ago · None stated GitHub repository of TradersPost High-Low Range Strategy

Alpy Kazan

by avatar-lavventura
2 stars

This strategy tracks market direction using an ATR-based trailing envelope similar to Supertrend, combined with Bollinger Bands, Keltner Channels, and momentum filters. Long and short entry signals trigger on trend direction shifts within a specified backtest

Strategy v4 TrendVolatility & bands
Updated 7 months ago · None stated GitHub repository of Alpy Kazan

Alt Coin BB SMA Strategy

by samliu0206
1 stars

Alt Coin BB SMA Strategy calculates custom MACD delta histogram values, Bollinger Bands, and middle band slopes to generate trading signals in a separate pane. It triggers long entry orders when the price crosses above the Bollinger middle band, provided the M

Strategy v4 Oscillators & momentumVolatility & bands
Updated 4 years ago · None stated GitHub repository of Alt Coin BB SMA Strategy

Sentiment 2

by ggamer5555
1 stars

Sentiment 2 is a trend-following strategy that calculates a normalized composite market sentiment score ranging between 0 and 1. It combines exponential and Hull moving average slopes, high/low midpoints, Supertrend distances, and Parabolic SAR metrics. Plotte

Strategy v6 TrendMoving averages
Updated 5 months ago · None stated GitHub repository of Sentiment 2

More strategys

Trade Your Charts With a Regulated Broker

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

Compare brokers