Indicator · Pine Script v6 · On the chart

Jurik Adaptive Envelope Bands

by mihakralj

This overlay indicator plots Jurik Adaptive Envelope Bands around price.

View on GitHub

GitHub stars99on the repository
Pine versionv6current
Lines of code176
Settings3inputs
LicenceApache-2.0
Last update4 weeks ago27 Sep 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

176 lines of Pine Script v6 from lib/channels/jbands/Jbands.pine, published under Apache-2.0. Keep the author's credit and licence lines when you reuse it.

Jbands.pine
// Licensed under the Apache License, Version 2.0
// © mihakralj
//@version=6
indicator("Jurik Adaptive Envelope Bands", "JBANDS", overlay=true)

//@function Jurik Adaptive Envelope Bands - Upper/Lower bands from JMA's adaptive envelope tracking
//@param   source Series to calculate JBANDS from
//@param   period Number of bars used in the calculation (>= 1)
//@param   phase  Phase shift (-100 to 100). Negative = smoother, positive = more leading
//@returns Array of [middle (JMA), upper, lower]
jbands(series float source, simple int period, simple int phase = 0) =>
    // ---- Precomputed length/phase parameters (constant per series) ----
    simple float _PHASE      = phase < -100 ? 0.5 : phase > 100 ? 2.5 : (phase * 0.01) + 1.5
    simple float _LEN0       = period < 1.0000000002 ? 1e-10 : (period - 1.0) / 2.0
    simple float _LOG_PARAM  = math.max(math.log(math.sqrt(_LEN0)) / math.log(2.0) + 2.0, 0.0)
    simple float _SQRT_PARAM = math.sqrt(_LEN0) * _LOG_PARAM
    simple float _LEN_ADJ    = _LEN0 * 0.9
    simple float _LEN_DIV    = _LEN_ADJ / (_LEN_ADJ + 2.0)
    simple float _SQRT_DIV   = _SQRT_PARAM / (_SQRT_PARAM + 1.0)
    simple float _P_EXP      = math.max(_LOG_PARAM - 2.0, 0.5)

    // ---- Internal state (persists across bars) ----
    var float  upperBand   = na
    var float  lowerBand   = na
    var float  lastC0      = na
    var float  lastC8      = na
    var float  lastA8      = na
    var float  lastJma     = na
    var int    bars        = 0

    // 10-bar local deviation window
    var float  cycleDelta  = 0.0
    var int    volIndex    = 0
    var int    volCount    = 0
    var array<float> volWindow  = array.new_float(10, 0.0)

    // 128-bar volatility distribution
    var int    distIndex   = 0
    var int    distCount   = 0
    var array<float> distWindow = array.new_float(128, 0.0)
    var array<float> sorted     = array.new_float(0)

    float current_jma   = na
    float current_upper = na
    float current_lower = na

    if not na(source)
        bars += 1

        // ---- First bar: initialize anchors and filter state ----
        if bars == 1
            upperBand := source
            lowerBand := source
            lastC0    := source
            lastC8    := 0.0
            lastA8    := 0.0
            lastJma   := source
            current_jma   := source
            current_upper := source
            current_lower := source
        else
            // 1) Local deviation vs. UpperBand / LowerBand
            float diffA    = source - upperBand
            float diffB    = source - lowerBand
            float absA     = math.abs(diffA)
            float absB     = math.abs(diffB)
            float absValue = absA > absB ? absA : absB
            float dLocal   = absValue + 1e-10

            // 2) 10-bar SMA of local deviation -> highD
            float oldVol = array.get(volWindow, volIndex)
            cycleDelta  += dLocal - oldVol
            array.set(volWindow, volIndex, dLocal)
            volIndex += 1
            if volIndex >= 10
                volIndex := 0
            if volCount < 10
                volCount += 1
            float highD = volCount > 0 ? cycleDelta / (volCount < 10 ? volCount : 10) : dLocal

            // 3) 128-bar volatility distribution + trimmed mean
            array.set(distWindow, distIndex, highD)
            distIndex += 1
            if distIndex >= 128
                distIndex := 0
            if distCount < 128
                distCount += 1

            float dRef = highD
            if distCount >= 16
                int count = distCount
                array.clear(sorted)
                for i = 0 to count - 1
                    int idx = distIndex - 1 - i
                    if idx < 0
                        idx += 128
                    array.push(sorted, array.get(distWindow, idx))
                array.sort(sorted)

                int idxLo = 0
                int idxHi = 0
                if count >= 128
                    idxLo := 32
                    idxHi := 96
                else
                    int slice = int(math.max(5.0, math.round(count * 0.5)))
                    int drop  = (count - slice) / 2
                    idxLo := drop
                    idxHi := drop + slice - 1

                if idxLo < 0
                    idxLo := 0
                if idxHi >= count
                    idxHi := count - 1

                float sum = 0.0
                for i = idxLo to idxHi
                    sum += array.get(sorted, i)
                dRef := sum / float(idxHi - idxLo + 1)

            if dRef <= 0.0
                dRef := dLocal

            // 4) Jurik dynamic exponent
            float ratio = absValue / dRef
            if ratio < 0.0
                ratio := 0.0
            float d = math.pow(ratio, _P_EXP)
            d := math.min(math.max(d, 1.0), _LOG_PARAM)

            // 5) Update UpperBand / LowerBand via sqrtDivider ^ sqrt(d)
            float adapt = math.pow(_SQRT_DIV, math.sqrt(d))
            if source > upperBand
                upperBand := source
            else
                upperBand := source - (source - upperBand) * adapt
            if source < lowerBand
                lowerBand := source
            else
                lowerBand := source - (source - lowerBand) * adapt

            // 6) 2-pole IIR core (C0/C8/A8) with Jurik alpha for middle band (JMA)
            float prevJma = na(lastJma) ? source : lastJma
            float alpha  = math.pow(_LEN_DIV, d)
            float alpha2 = alpha * alpha
            float c0 = (1.0 - alpha) * source + alpha * lastC0
            float c8 = (source - c0) * (1.0 - _LEN_DIV) + _LEN_DIV * lastC8
            float a8 = (_PHASE * c8 + c0 - prevJma) * (alpha * -2.0 + alpha2 + 1.0) + alpha2 * lastA8
            float jmaVal = prevJma + a8

            lastC0  := c0
            lastC8  := c8
            lastA8  := a8
            lastJma := jmaVal

            current_jma   := jmaVal
            current_upper := upperBand
            current_lower := lowerBand

    [current_jma, current_upper, current_lower]

// ---------- Main loop ----------

// Inputs
i_period = input.int(10, "Period", minval=1, tooltip="Number of bars used in the calculation")
i_phase  = input.int(0, "Phase", tooltip="Phase shift (-100 to 100). Negative = smoother, positive = more leading", minval=-100, maxval=100, step=10)
i_source = input.source(close, "Source")

// Calculation
[jma, upper, lower] = jbands(i_source, i_period, i_phase)

// Plot
p_upper = plot(upper, "Upper Band", color=color.new(color.red, 50), linewidth=1)
p_lower = plot(lower, "Lower Band", color=color.new(color.green, 50), linewidth=1)
plot(jma, "Middle (JMA)", color=color.yellow, linewidth=2)
fill(p_upper, p_lower, color=color.new(color.blue, 90), title="Band Fill")

Overview

Jurik Adaptive Envelope Bands calculates a Jurik moving average as the middle line and builds upper and lower bands from that filter’s adaptive envelope. A ten-bar window tracks local deviation from the bands, and a 128-bar volatility distribution feeds a trimmed mean that widens or tightens the envelope. Period sets the calculation length, phase shifts the filter from smoother to more leading, and source selects the input series. It draws on the price chart.

Details

Published by

mihakralj on GitHub

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

Moving averages, Volatility & bands

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

algorithmic-tradingcsharpdotnetpinescriptpythonquantitative-financesimdta-libtechnical-analysistechnical-indicators

Settings

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

SettingTypeDefaultVariable
Periodint10i_period
Phaseint0i_phase
Sourcesourceclosei_source
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

Hampel Filter

by everget
948 stars

The Hampel Filter indicator identifies and filters out price outliers to smooth price data directly on the main chart. It calculates the rolling median and Median Absolute Deviation (MAD) of the chosen source series over a specified length. When a data point e

Indicator v4 Moving averagesVolatility & bands
Updated 10 days ago · GPL-3.0 GitHub repository of Hampel Filter

This overlay indicator plots a Supertrend from an ATR length and factor, four exponential moving averages, and a selectable VWAP or TWAP taken from a chosen timeframe on the price chart. It also shows MACD and stochastic with adjustable lengths, smoothing, ver

Indicator v5 Oscillators & momentumMoving averages
Updated 1 year ago · MPL-2.0 GitHub repository of Momentum Setup: Vijay Thakare Option Buying Scalping Setup

This indicator plots trade entry and exit signals on the price chart by combining Bollinger Bands with optional filter conditions. It calculates standard Bollinger Bands along with RSI, Aroon, and moving average filters. Reversal signals trigger when price bre

Indicator v4 Volatility & bandsOscillators & momentum
Updated 2 months ago · None stated GitHub repository of Bollinger Bands Winner IND [Alorse]
318 stars

This indicator plots four customizable moving averages directly on the main price chart using default lengths of 3, 8, 21, and 50 periods. It fills the space between adjacent averages in green or maroon depending on trend alignment. Additionally, it computes B

Indicator v5 Moving averagesTrend
Updated 2 months ago · MIT GitHub repository of Multiple Moving Averages

NVDA Trend Ribbon

by longsurf-ai
277 stars

This indicator plots three exponential moving averages and ATR volatility envelopes on the price chart. It compares the fast and slow EMAs to shade a trend ribbon and marks confirmed upward and downward crossovers with triangle labels. Settings change the thre

Indicator v6 TrendMoving averages
Updated yesterday · MIT GitHub repository of NVDA Trend Ribbon

Algo AVT

by ricardosantos79
261 stars

This indicator calculates an outlier-filtered moving average based on statistical filtering concepts. It stores close prices in a rolling array over a user-defined length, calculates the mean and standard deviation, and excludes values falling within a specifi

Indicator v4 Moving averagesVolatility & bands
Updated 4 years ago · None stated GitHub repository of Algo AVT

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