Indicator · Pine Script v5 · Own pane

WaveTrend 3D

by nersent· code credited to jdehorty

WaveTrend 3D is a redesigned oscillator that implements the classic WaveTrend algorithm with kernel-based regression for trend reversal confirmation and multi-timeframe support.

View on GitHub

GitHub stars29on the repository
Pine versionv5runs as is
Lines of code522
Settings40inputs
LicenceCustom licencefile: MPL-2.0
Last update11 months ago24 Nov 2025

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

522 lines of Pine Script v5 from suite/jdehorty/wavetrend_3d.pine, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

wavetrend_3d.pine
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © jdehorty
// @version=5

indicator("WaveTrend 3D", max_lines_count=500, explicit_plot_zorder=true, timeframe="")

import jdehorty/KernelFunctions/2 as kernels

// ==================
// ==== Overview ====
// ==================

// WaveTrend 3D (WT3D) is a novel implementation of the famous WaveTrend (WT) indicator and has been completely redesigned from the ground up to address some 
// of the inherent shortcomings associated with the traditional WT algorithm, including:
// (1) unbounded extremes
// (2) susceptibility to whipsaw
// (3) lack of insight into other timeframes

// Furthermore, WT3D expands upon the original functionality of WT by providing: 
// (1) first-class support for multi-timeframe (MTF) analysis
// (2) kernel-based regression for trend reversal confirmation
// (3) various options for signal smoothing and transformation
// (4) a unique mode for visualizing an input series as a symmetrical, three-dimensional waveform useful for pattern identification and cycle-related analysis

// Fundamental Assumptions:
// (1) There exists a probability density function that describes the relative likelihood for a price to visit a given value.
// (2) The probability density function for price is a function of time.
// (3) The probability density function can approximate a Gaussian distribution (shown below).

//                                                                            ___ 
//                                  .::~!:..                                   |                
//                                :ΞΞΞΞ~!ΞΞΞ!.                                 |                
//                              .ΞJΞΞΞΞ~!ΞΞΞ?J^                                |                
//                             :J?ΞΞΞΞΞ~!ΞΞΞΞΞJ^                               |                
//                            :J?ΞΞΞΞΞΞ~!ΞΞΞΞΞΞ??.                             |                
//                           :JΞΞΞΞΞΞΞΞ~!ΞΞΞΞΞΞΞ?J^                            |                
//                          :JΞΞΞΞΞΞΞΞΞ~!ΞΞΞΞΞΞΞΞ?J^                       [ PRICE ]                
//                        .:~ΞΞΞΞΞΞΞΞΞΞ~!ΞΞΞΞΞΞΞΞΞ!!~                          |                
//                       :?~^ΞΞΞΞΞΞΞΞΞΞ~!ΞΞΞΞΞΞΞΞΞ!^Ξ!                         |                
//                      ~:^^^ΞΞΞΞΞΞΞΞΞΞ~!ΞΞΞΞΞΞΞΞΞ!^^!Ξ.                       |                
//                    .Ξ!^^^^ΞΞΞΞΞΞΞΞΞΞ~!ΞΞΞΞΞΞΞΞΞ!^^^~Ξ~                      |                
//                  .~Ξ~^^^^^ΞΞΞΞΞΞΞΞΞΞ~!ΞΞΞΞΞΞΞΞΞ!^^^^^!Ξ:                    |                
//                .~Ξ~^^^^^^^ΞΞΞΞΞΞΞΞΞΞ~!ΞΞΞΞΞΞΞΞΞ!^^^^^^~!!^.                 |                
//       ....::^^!~~^^^^^^^^^ΞΞΞΞΞΞΞΞΞΞ~!ΞΞΞΞΞΞΞΞΞ!^^^^^^^^^~!^^::......       |               
// ..:::^^^^^^^::::::::::::::ΞΞΞΞΞΞΞΞΞΞ~!ΞΞΞΞΞΞΞΞΞ!::::::::::::^^^^^^^^:::..   |                
//
// -------------------------------- [ TIME ] -------------------------------|     

// How to use this indicator:
// - The basic usage of WT3D is similar to how one would use the traditional WT indicator.
// - Divergences can be spotted by finding "trigger waves", which are small waves that immediately follow a larger wave. These can also be thought of as Lower-Highs and Higher-Lows in the oscillator.
// - Instead of the SMA-cross in the original WT, the primary mechanism for identifying potential pivots are the crossovers of the fast/normal speed oscillators, denoted by the small red/green circles.
// - The larger red/green circles represent points where there could be a potential trigger wave for a Divergence. Settings related to Divergence detection can be configured in the "Divergence" section.
// - For overbought/oversold conditions, the 0.5 and -0.5 levels are convenient since the normal-speed oscillator will only exceed this level ~25% of the time.
// - For less experienced users, focusing on the three oscillators is recommended since they give critical information from multiple timeframes that can help to identify trends and spot potential divergences.
// - For more experienced users, this indicator also has many other valuable features, such as Center of Gravity (CoG) smoothing, Kernel Estimate Crossovers, a mirrored mode for cycle analysis, and more. 
// - Note: Additional resources for learning/using the more advanced features of this indicator are a work in progress, but in the meantime, I am happy to answer any questions.

// ================
// ==== Inputs ====
// ================

// Signal Settings
src = input.source(close, title="Source", group="Signal Settings", inline='00')
useMirror = input.bool(false, "Use Mirror", group="Signal Settings", inline='00', tooltip="Displays the input series as a symmetrical, three-dimensional waveform useful for pattern identification and cycle-related analysis.")
useEma = input.bool(false, "Use EMA", group="Signal Settings", inline='ema')
emaLength = input.int(3, minval=1, title="Length", tooltip="The number of bars used to calculate the EMA smoothing.", group="Signal Settings", inline='ema')
useCog = input.bool(false, "Use CoG", tooltip="Use the center of gravity of the price distribution as the signal.", group="Signal Settings", inline="smoothing")
cogLength = input.int(6, minval=1, title="Length", tooltip="Add CoG smoothing to the signal", group="Signal Settings", inline="smoothing")
oscillatorLookback = input.int(20, "Lookback", minval=2, tooltip="The number of bars to use for signal smoothing. This lookback is scaled so that multiple frequencies can be examined concurrently.", group="Signal Settings", inline="osc")
quadraticMeanLength = input.int(50, "Quadratic Mean", minval=2, tooltip="The Quadratic Mean is the square root of the average of the squares of the values. It is used in the normalization of the price's rate of change.", group="Signal Settings", inline="osc")
src := useEma ? ta.ema(src, emaLength) : src 
src := useCog ? ta.cog(src, cogLength) : src
speedToEmphasize = input.string('Slow', 'Speed to Emphasize', options=['Slow', 'Normal', 'Fast', 'None'], tooltip='Length to emphasize. This is like a timeframe within a timeframe.', inline="emphasis", group="Signal Settings")
emphasisWidth = input.int(2, "Width", tooltip="Width of the emphasized line.", inline="emphasis", group="Signal Settings")
useKernelMA = input.bool(false, "Display Kernel Moving Average", group="Signal Settings", tooltip="Display the Kernel Moving Average of the signal. This is a smoothed version of the signal that is more robust to noise.", inline="kernel")
useKernelEmphasis = input.bool(false, "Display Kernel Signal", group="Signal Settings", tooltip="Display the Kernel Estimator for the emphasized line. This is a smoothed version of the emphasized line that is more robust to noise.", inline="kernel")

// Oscillator Settings
offset = input.int(0, "Oscillator Separation Distance", group="Oscillators", tooltip="Separates the signal from the source by the specified number of bars. Useful for examining an oscillator in isolation and directly comparing to other timeframes.", inline="toggleOsc")
showOsc = input.bool(true, "Show Oscillator Lines", group="Oscillators", inline="toggleOsc")
showOsc := showOsc
f_length = input.float(0.75, "Fast Length:", step=0.05, tooltip="Length scale factor for the fast oscillator.", inline="fast", group="Oscillators")
f_smoothing = input.float(0.45, "Smoothing:", step=0.05, tooltip="Smoothing scale factor for the fast oscillator.", inline="fast", group="Oscillators")
n_length = input.float(1.0, "Normal Length:", step=0.05, tooltip="Length scale factor for the normal oscillator.", inline="normal", group="Oscillators")
n_smoothing = input.float(1.0, "Smoothing:", step=0.05, tooltip="Smoothing scale factor for the normal frequency.", inline="normal", group="Oscillators")
s_length = input.float(1.75, "Slow Length:", step=0.05, tooltip="Length scale factor for the slow oscillator.", inline="slow", group="Oscillators")
s_smoothing = input.float(2.5, "Smoothing:", step=0.05, tooltip="Smoothing scale factor for the slow frequency.", inline="slow", group="Oscillators")

// Divergence Detection
divThreshold = input.int(30, "Divergence Distance", minval=1, tooltip="The amount of bars for the divergence to be considered significant.", group="Divergence Detection", inline="divergence")
sizePercent = input.int(40, "Percent Size", tooltip="How big the current wave should be relative to the previous wave. A smaller waves immediately following a larger wave is often a trigger wave for a divergence.", group="Divergence Detection", inline="divergence")

// Overbought/Oversold Zones (Reversal Zones)
showObOs = input.bool(false, "Show OB/OS Zones", tooltip="Show the overbought/oversold zones for the normal-speed oscillator. These zones are useful for identifying potential reversal points since price will only exceed the ±0.5 level ~25% of the time.", group="Overbought/Oversold Zones", inline="zones")
invertObOsColors = input.bool(false, "Invert Colors", tooltip="Changes the colors of the overbought/oversold regions to be the inverse.", group="Overbought/Oversold Zones", inline="zones")
ob1 = input.float(0.5, "Overbought Primary", minval=0, maxval=1, step=0.05, group="Overbought/Oversold Zones", inline="ob")
ob2 = input.float(0.75, "Overbought Secondary", minval=0, maxval=1, step=0.05, group="Overbought/Oversold Zones", inline="ob")
os1 = input.float(-0.5, "Oversold Primary", minval=-1, maxval=0, step=0.05, group="Overbought/Oversold Zones", inline="os")
os2 = input.float(-0.75, "Oversold Secondary", minval=-1, maxval=0, step=0.05, group="Overbought/Oversold Zones", inline="os")

// Transparencies and Gradients
areaBackgroundTrans = input.float(128., "Background Area Transparency Factor", minval=0., step=1, tooltip="Transparency factor for the background area.", group="Transparencies and Gradients")
areaForegroundTrans = input.float(64., "Foreground Area Transparency Factor", minval=0., step=1, tooltip="Transparency factor for the foreground area.", group="Transparencies and Gradients")
lineBackgroundTrans = input.float(2.6, "Background Line Transparency Factor", minval=0., step=1, tooltip="Transparency factor for the background line.", group="Transparencies and Gradients")
lineForegroundTrans = input.float(2., "Foreground Line Transparency Factor", minval=0., step=1, tooltip="Transparency factor for the foreground line.", group="Transparencies and Gradients")
customTransparency = input.int(30, 'Custom Transparency', minval=0, maxval=100, step=5, tooltip="Transparency of the custom colors.", group="Transparencies and Gradients")
maxStepsForGradient = input.int(8, 'Total Gradient Steps', minval=2, maxval=256, tooltip='The maximum amount of steps supported for a gradient calculation is 256.', group="Transparencies and Gradients")

// The defaults are colors that Google uses for its Data Science libraries (e.g. TensorFlow). They are considered to be colorblind-safe. 
var color fastBullishColor = input.color(color.new(#009988, 30), 'Fast Bullish Color', group="Colors", inline="fast")
var color normalBullishColor = input.color(color.new(#009988, 60), 'Normal Bullish Color', group="Colors", inline="normal")
var color slowBullishColor = input.color(color.new(#009988, 70), 'Slow Bullish Color', group="Colors", inline="slow")
var color fastBearishColor = input.color(color.new(#CC3311, 30), 'Fast Bearish Color', group="Colors", inline="fast")
var color normalBearishColor = input.color(color.new(#CC3311, 60), 'Normal Bearish Color', group="Colors", inline="normal")
var color slowBearishColor = input.color(color.new(#CC3311, 70), 'Slow Bearish Color', group="Colors", inline="slow")
var color c_bullish = input.color(#009988, "Bullish Divergence Signals", group="Colors", inline="divergence")
var color c_bearish = input.color(#CC3311, "Bearish Divergence Signals", group="Colors", inline="divergence")

lineBackgroundTrans := lineBackgroundTrans * customTransparency
areaBackgroundTrans := areaBackgroundTrans * customTransparency
lineForegroundTrans := lineForegroundTrans * customTransparency
areaForegroundTrans := areaForegroundTrans * customTransparency

areaFastTrans = areaBackgroundTrans
lineFastTrans = lineBackgroundTrans
areaNormalTrans = areaBackgroundTrans
lineNormalTrans = lineBackgroundTrans
areaSlowTrans = areaForegroundTrans
lineSlowTrans = lineForegroundTrans

switch speedToEmphasize
    "Slow" =>
        areaFastTrans := areaBackgroundTrans
        lineFastTrans := lineBackgroundTrans
        areaNormalTrans := areaBackgroundTrans
        lineNormalTrans := lineBackgroundTrans
        areaSlowTrans := areaForegroundTrans
        lineSlowTrans := lineForegroundTrans
    "Normal" =>
        areaFastTrans := areaBackgroundTrans
        lineFastTrans := lineBackgroundTrans
        areaNormalTrans := areaForegroundTrans
        lineNormalTrans := lineForegroundTrans
        areaSlowTrans := areaBackgroundTrans
        lineSlowTrans := lineBackgroundTrans
    "Fast" =>
        areaFastTrans := areaForegroundTrans
        lineFastTrans := lineForegroundTrans
        areaNormalTrans := areaBackgroundTrans
        lineNormalTrans := lineBackgroundTrans
        areaSlowTrans := areaBackgroundTrans
        lineSlowTrans := lineBackgroundTrans
    "None" =>
        areaFastTrans := areaBackgroundTrans
        lineFastTrans := lineBackgroundTrans
        areaNormalTrans := areaBackgroundTrans
        lineNormalTrans := lineBackgroundTrans
        areaSlowTrans := areaBackgroundTrans
        lineSlowTrans := lineBackgroundTrans

// =================================
// ==== Color Helper Functions =====
// =================================

getPlotColor(signal, bullColor, bearColor) =>
    signal >= 0.0 ? bullColor : bearColor

getAreaColor(signal, useMomentum, bullColor, bearColor) =>
    if useMomentum
        ta.rising(signal, 1) ? bullColor : bearColor
    else
        signal >= 0.0 ? bullColor : bearColor

getColorGradientFromSteps(_source, _center, _steps, weakColor, strongColor) =>
    var float _qtyAdvDec = 0.
    var float _maxSteps = math.max(1, _steps)
    bool _xUp = ta.crossover(_source, _center)
    bool _xDn = ta.crossunder(_source, _center)
    float _chg = ta.change(_source)
    bool _up = _chg > 0
    bool _dn = _chg < 0
    bool _srcBull = _source > _center
    bool _srcBear = _source < _center
    _qtyAdvDec := _srcBull ? _xUp ? 1 : _up ? math.min(_maxSteps, _qtyAdvDec + 1) : _dn ? math.max(1, _qtyAdvDec - 1) : _qtyAdvDec : _srcBear ? _xDn ? 1 : _dn ? math.min(_maxSteps, _qtyAdvDec + 1) : _up ? math.max(1, _qtyAdvDec - 1) : _qtyAdvDec : _qtyAdvDec      
    color colorGradient = color.from_gradient(_qtyAdvDec, 1, _maxSteps, weakColor, strongColor)
    colorGradient

getColorGradientFromSource(series, _min, _max, weakColor, strongColor) =>
    var float baseLineSeries = _min + (_max - _min) / 2
    color colorGradient = series >= baseLineSeries ? color.from_gradient(value=series, bottom_value=baseLineSeries, top_value=_max, bottom_color=weakColor, top_color=strongColor) : color.from_gradient(series, _min, baseLineSeries, strongColor, weakColor)
    colorGradient

// ================================
// ==== Main Helper Functions =====
// ================================

normalizeDeriv(_src, _quadraticMeanLength) =>
    float derivative = _src - _src[2]
    quadraticMean = math.sqrt(nz(math.sum(math.pow(derivative, 2), _quadraticMeanLength) / _quadraticMeanLength))
    derivative/quadraticMean

tanh(series float _src) =>
    -1 + 2/(1 + math.exp(-2*_src))

dualPoleFilter(float _src, float _lookback) =>
    float _omega = -99 * math.pi / (70 * _lookback)
    float _alpha = math.exp(_omega)
    float _beta = -math.pow(_alpha, 2)
    float _gamma = math.cos(_omega) * 2 * _alpha
    float _delta = 1 - _gamma - _beta
    float _slidingAvg = 0.5 * (_src + nz(_src[1], _src))
    float _filter = na
    _filter := (_delta*_slidingAvg) + _gamma*nz(_filter[1]) + _beta*nz(_filter[2])
    _filter

getOscillator(float src, float smoothingFrequency, int quadraticMeanLength) =>   
    nDeriv = normalizeDeriv(src, quadraticMeanLength)
    hyperbolicTangent = tanh(nDeriv)
    result = dualPoleFilter(hyperbolicTangent, smoothingFrequency)

// =================================
// ==== Oscillator Calculations ====
// =================================

// Fast Oscillator + Mirror
offsetFast = offset
f_lookback = f_smoothing * oscillatorLookback
signalFast = getOscillator(src, f_lookback, quadraticMeanLength)
seriesFast = f_length*signalFast+offsetFast
seriesFastMirror = useMirror ? -seriesFast + 2*offsetFast : na

// Normal Oscillator + Mirror
offsetNormal = 0
n_lookback = n_smoothing * oscillatorLookback
signalNormal = getOscillator(src, n_lookback, quadraticMeanLength)
seriesNormal = n_length*signalNormal+offsetNormal
seriesNormalMirror = useMirror ? -seriesNormal + 2*offsetNormal : na

// Slow Oscillator + Mirror
offsetSlow = -offset
s_lookback = s_smoothing * oscillatorLookback
signalSlow = getOscillator(src, s_lookback, quadraticMeanLength)
seriesSlow = s_length*signalSlow+offsetSlow
seriesSlowMirror = useMirror ? -seriesSlow + 2*offsetSlow : na

// =====================================
// ==== Color Gradient Calculations ====
// =====================================

// Fast Color Gradients (Areas and Lines)
// fastBaseColor = getPlotColor(signalFast, fastBullishColor, fastBearishColor)
// fastBaseColorInverse = getPlotColor(signalFast, fastBearishColor, fastBullishColor)
// fastAreaGradientFromSource = getColorGradientFromSource(seriesFast, -1.+offsetFast, 1+offsetFast, color.new(fastBaseColor, areaFastTrans), fastBaseColor)
// fastAreaGradientFromSteps = getColorGradientFromSteps(seriesFast, offsetFast, maxStepsForGradient, color.new(fastBaseColor, areaFastTrans), fastBaseColor)
// fastLineGradientFromSource = getColorGradientFromSource(seriesFast, -1+offsetFast, 1+offsetFast, color.new(fastBaseColor, lineFastTrans), fastBaseColor)
// fastLineGradientFromSteps = getColorGradientFromSteps(seriesFast, offsetFast, maxStepsForGradient, color.new(fastBaseColor, lineFastTrans), fastBaseColor)
// fastAreaGradientFromSourceInverse = getColorGradientFromSource(seriesFast, -1.+offsetFast, 1+offsetFast, color.new(fastBaseColorInverse, areaFastTrans), fastBaseColorInverse)
// fastAreaGradientFromStepsInverse = getColorGradientFromSteps(seriesFast, offsetFast, maxStepsForGradient, color.new(fastBaseColorInverse, areaFastTrans), fastBaseColorInverse)

// // Normal Color Gradients (Areas and Lines)
// normalBaseColor = getPlotColor(signalNormal, normalBullishColor, normalBearishColor)
// normalBaseColorInverse = getPlotColor(signalNormal, normalBearishColor, normalBullishColor)
// normalAreaGradientFromSource = getColorGradientFromSource(seriesNormal, -1.+offsetNormal, 1.+offsetNormal, color.new(normalBaseColor, areaNormalTrans), normalBaseColor)
// normalAreaGradientFromSteps = getColorGradientFromSteps(seriesNormal, offsetNormal, maxStepsForGradient, color.new(normalBaseColor, areaNormalTrans), normalBaseColor)
// normalLineGradientFromSource = getColorGradientFromSource(seriesNormal, -1+offsetNormal, 1+offsetNormal, color.new(normalBaseColor, lineNormalTrans), normalBaseColor)
// normalLineGradientFromSteps = getColorGradientFromSteps(seriesNormal, offsetNormal, maxStepsForGradient, color.new(normalBaseColor, lineNormalTrans), normalBaseColor)
// normalAreaGradientFromSourceInverse = getColorGradientFromSource(seriesNormal, -1.+offsetNormal, 1.+offsetNormal, color.new(normalBaseColorInverse, areaNormalTrans), normalBaseColorInverse)
// normalAreaGradientFromStepsInverse = getColorGradientFromSteps(seriesNormal, offsetNormal, maxStepsForGradient, color.new(normalBaseColorInverse, areaNormalTrans), normalBaseColorInverse)

// // Slow Color Gradients (Areas and Lines)
// slowBaseColor = getPlotColor(signalSlow, slowBullishColor, slowBearishColor)
// slowBaseColorInverse = getPlotColor(signalSlow, slowBearishColor, slowBullishColor)
// slowAreaGradientFromSource = getColorGradientFromSource(seriesSlow, -1.75+offsetSlow, 1.75+offsetSlow, color.new(slowBaseColor, areaSlowTrans), slowBaseColor)
// slowAreaGradientFromSteps = getColorGradientFromSteps(seriesSlow, offsetSlow, maxStepsForGradient, color.new(slowBaseColor, areaSlowTrans), slowBaseColor)
// slowLineGradientFromSource = getColorGradientFromSource(seriesSlow, -1.75+offsetSlow, 1.75+offsetSlow, color.new(slowBaseColor, lineSlowTrans), slowBaseColor)
// slowLineGradientFromSteps = getColorGradientFromSteps(seriesSlow, offsetSlow, maxStepsForGradient, color.new(slowBaseColor, lineSlowTrans), slowBaseColor)
// slowAreaGradientFromSourceInverse = getColorGradientFromSource(seriesSlow, -1.75+offsetSlow, 1.75+offsetSlow, color.new(slowBaseColorInverse, areaSlowTrans), slowBaseColorInverse)
// slowAreaGradientFromStepsInverse = getColorGradientFromSteps(seriesSlow, offsetSlow, maxStepsForGradient, color.new(slowBaseColorInverse, areaSlowTrans), slowBaseColorInverse)

// =========================================
// ==== Plot Parameters and Logic Gates ====
// =========================================

// Speed Booleans
isSlow = speedToEmphasize == "Slow"
isNormal = speedToEmphasize == "Normal"
isFast = speedToEmphasize == "Fast"

// Series Colors
// seriesSlowColor = showOsc or isSlow ? color.new(slowLineGradientFromSource, lineSlowTrans) : na
// seriesNormalColor = showOsc or isNormal ? color.new(normalLineGradientFromSource, lineNormalTrans) : na
// seriesFastColor = showOsc or isFast ? color.new(fastLineGradientFromSource, lineFastTrans) : na
// seriesSlowMirrorColor = useMirror ? seriesSlowColor : na
// seriesNormalMirrorColor = useMirror ? seriesNormalColor : na
// seriesFastMirrorColor = useMirror ? seriesFastColor : na

// Series Line Widths
// seriesSlowWidth = isSlow ? emphasisWidth : 1
// seriesNormalWidth = isNormal ? emphasisWidth : 1
// seriesFastWidth = isFast ? emphasisWidth : 1
// seriesSlowMirrorWidth = useMirror ? seriesSlowWidth : na
// seriesNormalMirrorWidth = useMirror ? seriesNormalWidth : na
// seriesFastMirrorWidth = useMirror ? seriesFastWidth : na

// Speed Related Switches
seriesEmphasis = switch
    isFast => seriesFast
    isNormal => seriesNormal
    isSlow => seriesSlow
    => na

// colorLineEmphasis = switch
//     isFast => fastLineGradientFromSource
//     isNormal => normalLineGradientFromSource
//     isSlow => slowLineGradientFromSource
//     => na

// colorAreaEmphasis = switch
//     isFast => fastAreaGradientFromSource
//     isNormal => normalAreaGradientFromSource
//     isSlow => slowAreaGradientFromSource
//     => na

// TA__crossunder(series float source1, series float source2) =>
//     if bar_index == 5
//         log.info("[TA__crossunder] bar_index=" + str.tostring(bar_index) + ", source1[0]=" + str.tostring(source1[0]) + ", source2[0]=" + str.tostring(source2[0]) + ", source1[1]=" + str.tostring(source1[1]) + ", source2[1]=" + str.tostring(source2[1]) + ", xd=" + str.tostring(bool(float(na) >= 0.0)))
//     if na(source1) or na(source2)
//         na
//     else
//         source1 < source2 and (source1[1] >= source2[1] or na(source1[1]) or na(source2[1]))

// Crossover Signals
var bool bearishCross = na
bearishCross := ta.crossunder(seriesFast, seriesNormal) and seriesNormal > 0 

var bool bullishCross = na
bullishCross := ta.crossover(seriesFast, seriesNormal) and seriesNormal < 0

var bool slowBearishMedianCross = na
slowBearishMedianCross := ta.crossunder(seriesSlow, 0)

var bool slowBullishMedianCross = na
slowBullishMedianCross := ta.crossover(seriesSlow, 0)

var bool normalBearishMedianCross = na
normalBearishMedianCross := ta.crossunder(seriesNormal, 0)
// normalBearishMedianCross = TA__crossunder(seriesNormal, 0)

var bool normalBullishMedianCross = na
normalBullishMedianCross := ta.crossover(seriesNormal, 0)

var bool fastBearishMedianCross = na
fastBearishMedianCross := ta.crossunder(seriesFast, 0)

var bool fastBullishMedianCross = na
fastBullishMedianCross := ta.crossover(seriesFast, 0)

// if bar_index == 5
//     log.info("bar_index=" + str.tostring(bar_index) + ", normalBearishMedianCross=" + str.tostring(normalBearishMedianCross))

// Last Crossover Values
lastBearishCrossValue = ta.valuewhen(condition=bearishCross, source=seriesNormal, occurrence=1)
lastBullishCrossValue = ta.valuewhen(condition=bullishCross , source=seriesNormal, occurrence=1)

// Trigger Wave Size Comparison
triggerWaveFactor = sizePercent/100
isSmallerBearishCross = bearishCross and seriesNormal < lastBearishCrossValue * triggerWaveFactor
isSmallerBullishCross = bullishCross and seriesNormal > lastBullishCrossValue * triggerWaveFactor

// ===========================
// ==== Kernel Estimators ====
// ===========================

// The following kernel estimators are based on the Gaussian Kernel.

// They are used for:
//     (1) Confirming directional changes in the slow oscillator (i.e. a type of trend filter)
//     (2) Visualizing directional changes as a dynamic ribbon (i.e. an additional oscillator that can crossover with the user specified oscillator of interest)
//     (3) Visualizing transient directional changes while in the midst of a larger uptrend or downtrend (i.e. via color changes on the ribbon)

// Gaussian Kernel with a lookback of 6 bars, starting on bar 6 of the chart (medium fit)
var float yhat0 = na
yhat0 := kernels.gaussian(seriesEmphasis, 6, 6) 

// Gaussian Kernel with a lookback of 3 bars, starting on bar 2 of the chart (tight fit)
var float yhat1 = na
yhat1 := kernels.gaussian(seriesEmphasis, 3, 2) 

// Trend Assessment based on the relative position of the medium fit kernel to the slow oscillator
var bool isBearishKernelTrend = na
isBearishKernelTrend := yhat0 < seriesSlow

var bool isBullishKernelTrend = na
isBullishKernelTrend := yhat0 > seriesSlow

// Plots of the Kernel Estimators
// p = plot(seriesEmphasis, title="Series Emphasis", color=color.new(color.white, 100))
// p0 = plot(useKernelMA ? yhat0 : na, "Kernel Estimate for Trend", color=colorLineEmphasis)
// p1 = plot(useKernelEmphasis ? yhat1 : na, "Kernel Estimate for Emphasis", color=colorLineEmphasis)

// By assigning the color of a faster gradient, we can create a dynamic ribbon that changes color even amid a more significant trend. Since this is essentially a projection 
// of the rate of change of a lower frequency component to a higher frequency component, this can be seen as analogous to "Principal Component Analysis" (PCA), an unsupervised 
// machine learning technique used to reduce the dimensionality of a dataset by projecting multi-dimensional data onto a single component. In this scenario, we are essentially 
// reducing the dimensions from 3 to 2, allowing the user to focus exclusively on the ribbon, while the background oscillators are used to confirm the color changes of the ribbon.

// Fills for the Kernel Ribbon Colors
// fill(p, p0, color=fastLineGradientFromSource)
// fill(p, p1, color=fastLineGradientFromSource)

// Divergence Signals
var bool isBearishDivZone = na
isBearishDivZone := ta.barssince(bearishCross[1]) < divThreshold

var bool isBullishDivZone = na
isBullishDivZone := ta.barssince(bullishCross[1]) < divThreshold

// Crossover Detection
var bool isBearishTriggerWave = na
isBearishTriggerWave := isSmallerBearishCross and isBearishDivZone and isBearishKernelTrend

var bool isBullishTriggerWave = na
isBullishTriggerWave := isSmallerBullishCross and isBullishDivZone and isBullishKernelTrend

// =======================
// ==== Plots & Fills ====
// =======================

// Overbought/Oversold Zones
// obPlot1 = plot(ob1, "Overbought Primary", color=na)
// obPlot2 = plot(ob2, "Overbought Secondary", color=na)
// osPlot1 = plot(os1, "Oversold  Primary", color=na)
// osPlot2 = plot(os2, "Oversold Secondary", color=na)
// fill(obPlot1, obPlot2, offset == 0 and showObOs ? invertObOsColors ? normalAreaGradientFromStepsInverse : normalAreaGradientFromSteps : na)
// fill(osPlot1, osPlot2, offset == 0 and showObOs ? invertObOsColors ? normalAreaGradientFromStepsInverse : normalAreaGradientFromSteps : na) 

// Slow Plots with Fills
// slowOscPlot = plot(seriesSlow, "Slow Oscillator", color=seriesSlowColor, linewidth=seriesSlowWidth)
// slowOscPlotMirror = plot(seriesSlowMirror, "Slow Oscillator Mirror", color=seriesSlowMirrorColor, linewidth=seriesSlowMirrorWidth)
// baseLineSlow = plot(offsetSlow, "Baseline Slow", slowLineGradientFromSteps, style=plot.style_line, linewidth=1)
// fill(baseLineSlow, slowOscPlot, slowAreaGradientFromSource)
// fill(baseLineSlow, slowOscPlotMirror, slowAreaGradientFromSource)

// Normal Plots with Fills
// normalOscPlot = plot(seriesNormal, "Normal Oscillator", color=seriesNormalColor, linewidth=seriesNormalWidth)
// normalOscPlotMirror = plot(seriesNormalMirror, "Normal Oscillator Mirror", color=seriesNormalMirrorColor, linewidth=seriesNormalMirrorWidth)
// baseLineNormal = plot(offsetNormal, "Baseline Normal", normalLineGradientFromSteps, style=plot.style_line, linewidth=1)
// fill(baseLineNormal, normalOscPlot, normalAreaGradientFromSource)
// fill(baseLineNormal, normalOscPlotMirror, normalAreaGradientFromSource)

// Fast Plots with Fills
// fastOscPlot = plot(seriesFast, "Fast Oscillator", color=seriesFastColor, linewidth=seriesFastWidth)
// fastOscPlotMirror = plot(seriesFastMirror, "Fast Oscillator Mirror", color=seriesFastMirrorColor, linewidth=seriesFastMirrorWidth)
// baseLineFast = plot(offsetFast, "Baseline Fast", color=fastLineGradientFromSteps, style=plot.style_line, linewidth=1)
// fill(baseLineFast, fastOscPlot, fastAreaGradientFromSource)
// fill(baseLineFast, fastOscPlotMirror, fastAreaGradientFromSource)

// Signal Plots
// plot(bearishCross ? useMirror ? 0 : seriesNormal : na, title="Bearish Cross", style=plot.style_circles, linewidth=1, color=c_bearish, offset=-1)
// plot(isBearishTriggerWave ? useMirror ? 0 : seriesNormal : na, title="Bearish Trigger Cross", style=plot.style_circles, linewidth=3, color=c_bearish, offset=-1)
// plot(bullishCross ? useMirror ? 0 : seriesNormal : na, title="Bullish Cross", style=plot.style_circles, linewidth=1, color=c_bullish, offset=-1)
// plot(isBullishTriggerWave ? useMirror ? 0 : seriesNormal : na, title="Bullish Trigger Cross", style=plot.style_circles, linewidth=3, color=c_bullish, offset=-1) 

// ================
// ==== Alerts ====
// ================

// alertcondition(bearishCross, title='Bearish Cross', message='WT3D: {{ticker}} ({{interval}}) Bearish Cross ▼ [{{close}}]')
// alertcondition(bullishCross, title='Bullish Cross', message='WT3D: {{ticker}} ({{interval}}) Bullish Cross ▲ [{{close}}]')
// alertcondition(isBearishTriggerWave, title='Bearish Divergence', message='WT3D: {{ticker}} ({{interval}}) Bearish Divergence ▼ [{{close}}]')
// alertcondition(isBullishTriggerWave, title='Bullish Divergence', message='WT3D: {{ticker}} ({{interval}}) Bullish Divergence ▲ [{{close}}]')
// alertcondition(slowBearishMedianCross, title='Slow Bearish Median Cross', message='WT3D: {{ticker}} ({{interval}}) Slow Bearish Median Cross ▼ [{{close}}]')
// alertcondition(slowBullishMedianCross, title='Slow Bullish Median Cross', message='WT3D: {{ticker}} ({{interval}}) Slow Bullish Median Cross ▲ [{{close}}]')
// alertcondition(normalBearishMedianCross, title='Normal Bearish Median Cross', message='WT3D: {{ticker}} ({{interval}}) Normal Bearish Median Cross ▼ [{{close}}]')
// alertcondition(normalBullishMedianCross, title='Normal Bullish Median Cross', message='WT3D: {{ticker}} ({{interval}}) Normal Bullish Median Cross ▲ [{{close}}]')
// alertcondition(fastBearishMedianCross, title='Fast Bearish Median Cross', message='WT3D: {{ticker}} ({{interval}}) Fast Bearish Median Cross ▼ [{{close}}]')
// alertcondition(fastBullishMedianCross, title='Fast Bullish Median Cross', message='WT3D: {{ticker}} ({{interval}}) Fast Bullish Median Cross ▲ [{{close}}]')

// =====================
// ==== Backtesting ====
// =====================

var int condition = na
condition := switch 
    bearishCross => 1
    bullishCross => 2
    isBearishTriggerWave => 3
    isBullishTriggerWave => 4
    slowBearishMedianCross => 5
    slowBullishMedianCross => 6
    normalBearishMedianCross => 7
    normalBullishMedianCross => 8
    fastBearishMedianCross => 9
    fastBullishMedianCross => 10

// plot(condition, "Alert Stream", display=display.none) 

// EXPORTS =========================
// var bool is_bearish_kernel_trend = na
// is_bearish_kernel_trend := isBearishKernelTrend
// var bool is_bullish_kernel_trend = na
// is_bullish_kernel_trend := isBullishKernelTrend
// var bool bearish_cross = na
// bearish_cross := bearishCross
// var bool bullish_cross = na
// bullish_cross := bullishCross
// var bool bearish_trigger_wave = na
// bearish_trigger_wave := isBearishTriggerWave
// var bool bullish_trigger_wave = na
// bullish_trigger_wave := isBullishTriggerWave
// var bool slow_bearish_median_cross = na
// slow_bearish_median_cross := slowBearishMedianCross
// var bool slow_bullish_median_cross = na
// slow_bullish_median_cross := slowBullishMedianCross
// var bool normal_bearish_median_cross = na
// normal_bearish_median_cross := normalBearishMedianCross
// var bool normal_bullish_median_cross = na
// normal_bullish_median_cross := normalBullishMedianCross
// var bool fast_bearish_median_cross = na
// fast_bearish_median_cross := fastBearishMedianCross
// var bool fast_bullish_median_cross = na
// fast_bullish_median_cross := fastBullishMedianCross

Overview

WaveTrend 3D calculates a smoothed oscillator line and displays a symmetrical three-dimensional waveform for cycle analysis. The indicator plots signals in a separate pane and highlights overbought oversold zones along with divergence patterns via trigger waves. Main settings adjust smoothing lengths kernel emphasis and divergence thresholds. It draws on the price chart with optional kernel moving averages.

Details

Published by

nersent on GitHub

Code credit

jdehorty, as written in the script's header

Type

Indicator, Pine Script v5

Draws

Separate pane below the chart

Category

Oscillators & momentum, Divergence, Support & resistance

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Topics

backtestindicatorjavascriptpandas-tapinescriptpythontata-libtradingviewvectorized

Settings

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

SettingTypeDefaultVariable
Sourcesourceclosesrc
Use MirrorboolfalseuseMirror
Use EMAboolfalseuseEma
Lengthint3emaLength
Use CoGboolfalseuseCog
Lengthint6cogLength
Lookbackint20oscillatorLookback
Quadratic Meanint50quadraticMeanLength
Speed to EmphasizestringSlowspeedToEmphasize
Widthint2emphasisWidth
Display Kernel Moving AverageboolfalseuseKernelMA
Display Kernel SignalboolfalseuseKernelEmphasis
Show all 40 settings
SettingTypeDefaultVariable
Oscillator Separation Distanceint0offset
Show Oscillator LinesbooltrueshowOsc
Fast Length:float0.75f_length
Smoothing:float0.45f_smoothing
Normal Length:float1.0n_length
Smoothing:float1.0n_smoothing
Slow Length:float1.75s_length
Smoothing:float2.5s_smoothing
Divergence Distanceint30divThreshold
Percent Sizeint40sizePercent
Show OB/OS ZonesboolfalseshowObOs
Invert ColorsboolfalseinvertObOsColors
Overbought Primaryfloat0.5ob1
Overbought Secondaryfloat0.75ob2
Oversold Primaryfloat-0.5os1
Oversold Secondaryfloat-0.75os2
Background Area Transparency Factorfloat128.areaBackgroundTrans
Foreground Area Transparency Factorfloat64.areaForegroundTrans
Background Line Transparency Factorfloat2.6lineBackgroundTrans
Foreground Line Transparency Factorfloat2.lineForegroundTrans
Custom Transparencyint30customTransparency
Total Gradient Stepsint8maxStepsForGradient
Fast Bullish Colorcolorcolor.new(#009988, 30)fastBullishColor
Normal Bullish Colorcolorcolor.new(#009988, 60)normalBullishColor
Slow Bullish Colorcolorcolor.new(#009988, 70)slowBullishColor
Fast Bearish Colorcolorcolor.new(#CC3311, 30)fastBearishColor
Normal Bearish Colorcolorcolor.new(#CC3311, 60)normalBearishColor
Slow Bearish Colorcolorcolor.new(#CC3311, 70)slowBearishColor
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

Benso RSI

by benso87
24 stars

This indicator calculates a Relative Strength Index using Wilder or Cutler calculation methods, accompanied by an optional smoothing moving average and a signal line. Plotted in a separate pane, it features defined bull and bear control and critical threshold

Indicator v5 Oscillators & momentumDivergence
Updated 4 years ago · MPL-2.0 GitHub repository of Benso RSI

The indicator scores whether a counter-move against the dominant structure is maturing and whether price has started turning back. It plots a composite score in a separate pane from weighted context, correction maturity, and exhaustion, scaled by trigger and a

Indicator v6 TrendOscillators & momentum
Updated yesterday · MIT GitHub repository of Directional Probability Engine v2 [WavesUnchained]

QFA Pro Elite Refined

by JasonTeixeira
9 stars

This indicator plots a smoothed fear index in a separate pane. It measures how far the current low sits below the highest close over a lookback, scales that reading by an ATR percentile, and smooths it with a zero-lag EMA. A volatility regime raises or lowers

Indicator v6 Oscillators & momentumVolatility & bands
Updated 12 days ago · Custom licence GitHub repository of QFA Pro Elite Refined
23 stars

This indicator collection calculates and plots previous daily high and low price levels on the chart to identify potential supply and demand zones. It includes real-time intraday high and low calculations, RSI divergence detection across a customizable period

Indicator v4 Support & resistanceDivergence
Updated 3 years ago · None stated GitHub repository of Daily High and Low Strategy

This indicator implements a full Chanlun pipeline on the price chart. It merges contained bars, marks top and bottom fractals, and connects alternating fractals into strokes. Overlapping strokes form pivot boxes, and MACD area ratios flag trend or consolidatio

Indicator v6 Support & resistanceDivergence
Updated 2 months ago · GPL-3.0 GitHub repository of 缠论 - 分型与笔 [Chanlun Core]

This indicator calculates momentum setups based on the Relative Strength Index (RSI) and plots in a separate pane. It offers multiple scanning modes such as Continuous Break, Continuous Retracement, Flip Break, Flip Retracement, and both regular and hidden div

Indicator v6 Oscillators & momentumDivergence Repaints
Updated 1 year ago · MPL-2.0 GitHub repository of Momentum Setup - RSI Directional Momentum Indicator

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