NLMS Volatility Trail-ATR Envelope for ThinkOrSwim

chewie76

Well-known member
VIP
VIP Enthusiast
The NLMS Volatility Trail is an adaptive trend-following overlay built on a Normalized Least Mean Squares (NLMS) filter — a machine-learning-derived signal processing algorithm that continuously learns from recent price action to produce a smoothed, predictive estimate of price. Unlike a simple moving average, the NLMS filter adjusts its internal weights bar-by-bar using gradient descent, making it more responsive to genuine trend changes while suppressing noise. Typically a flat Trail line indicates a choppy market while a moving Trail line is trending. The middle gray shaded areas are normal non-trending trading areas. Alerts, cloud fill, and paint bars are adjustable in the input settings.

The core Trail line is derived by wrapping that NLMS output in an ATR-based ratchet mechanism: the line can only move in the direction of the trend by at least one ATR step, preventing it from whipsawing on minor retracements. When the Trail reverses direction, it signals a trend change and recolors green (bullish) or red (bearish), with bar coloring and a price/trail fill zone reinforcing the bias visually.

Surrounding the Trail is a five-level ATR Envelope — bands at 2, 4, 6, 8, and 10 ATR multiples above and below — providing a dynamic map of how extended price is relative to its adaptive center. The inner bands (2–4 ATR) define normal trend extension; the middle bands (4–6 ATR) flag elevated momentum; the outer bands (8–10 ATR) mark statistically extreme moves where mean reversion risk is elevated. The asymmetric color scheme (warm colors above, cool colors below) makes overbought/oversold zones immediately readable at a glance.

Best used for: trend identification, trailing stop reference, identifying overextension, and framing re-entry zones after pullbacks to the inner ATR bands.

Conversion and adaptation inspired by this original code: https://www.tradingview.com/script/UUXdk60N-NLMS-Volatility-Trail-BackQuant/

Upper indicator and Watchlist Column code are below:

Here is a picture of SPY on the 1m chart.
1782575107000.png


CODE:
Code:
# NLMS Volatility Trail-ATR Envelope
# Converted and Adapted by Chewie 6/27/2026
# Original Concept: https://www.tradingview.com/script/UUXdk60N-NLMS-Volatility-Trail-BackQuant/

declare upper;
# ─── Inputs ───────────────────────────────────────────────────────────────────
input taps      = 72;
input mu        = 0.195;
input eps       = 0.6;
input periodAtr = 14;
input factorAtr = 1.7;
input lineW     = 3;
input showFill  = no;
input paintBar  = yes;
input alerts    = no;

DefineGlobalColor("Long",  CreateColor(0,   255, 0));
DefineGlobalColor("Short", CreateColor(255, 0,   0));

# ─── NLMS Filter (fold-based M-tap approximation) ─────────────────────────────
def src       = close;
def M = taps;

def power = fold i = 0 to M with p = 0 do
    p + Sqr(GetValue(src, i + 1));

def pred_raw = fold j = 0 to M with s = 0 do
    s + GetValue(src, j + 1);

def pred = pred_raw / M;
def err  = src - pred[1];
def k    = mu / (eps + power);
def raw  = pred + k * err * src[1];

# ─── ATR Band Trail ───────────────────────────────────────────────────────────

def atr            = ATR(periodAtr);
def trueRange      = atr * factorAtr;
def trueRangeUpper = raw + trueRange;
def trueRangeLower = raw - trueRange;

def nlmsAtr;
nlmsAtr = if IsNaN(nlmsAtr[1])           then raw
     else if trueRangeLower > nlmsAtr[1] then trueRangeLower
     else if trueRangeUpper < nlmsAtr[1] then trueRangeUpper
     else nlmsAtr[1];

# ─── Trend Direction ──────────────────────────────────────────────────────────

def nlmsAtrLong  = nlmsAtr > nlmsAtr[1] and nlmsAtr[1] <= nlmsAtr[2];
def nlmsAtrShort = nlmsAtr < nlmsAtr[1] and nlmsAtr[1] >= nlmsAtr[2];

def trendDir;
trendDir = if nlmsAtrLong  then  1
      else if nlmsAtrShort then -1
      else trendDir[1];

# ─── Trail Plot ───────────────────────────────────────────────────────────────

plot Trail = nlmsAtr;
Trail.SetLineWeight(lineW);
Trail.AssignValueColor(
    if trendDir ==  1 then GlobalColor("Long")
    else if trendDir == -1 then GlobalColor("Short")
    else Color.GRAY
);

# ─── Fill Anchor Plots ─────────────────────────

plot FillPriceBull = if showFill and close > nlmsAtr then close   else Double.NaN;
plot FillTrailBull = if showFill and close > nlmsAtr then nlmsAtr else Double.NaN;
plot FillTrailBear = if showFill and close < nlmsAtr then nlmsAtr else Double.NaN;
plot FillPriceBear = if showFill and close < nlmsAtr then close   else Double.NaN;

FillPriceBull.SetDefaultColor(Color.DARK_GREEN);
FillPriceBull.SetPaintingStrategy(PaintingStrategy.LINE);
FillPriceBull.SetLineWeight(1);
FillPriceBull.Hide();

FillTrailBull.SetDefaultColor(Color.DARK_GREEN);
FillTrailBull.Hide();

FillTrailBear.SetDefaultColor(Color.DARK_RED);
FillTrailBear.Hide();

FillPriceBear.SetDefaultColor(Color.DARK_RED);
FillPriceBear.Hide();

# Bull fill: price on top, trail on bottom
AddCloud(FillPriceBull, FillTrailBull, CreateColor(0, 180, 0), CreateColor(0, 40, 0));

# Bear fill: trail on top, price on bottom
AddCloud(FillTrailBear, FillPriceBear, CreateColor(180, 0, 0), CreateColor(40, 0, 0));

# ─── Bar Coloring ─────────────────────────────────────────────────────────────

AssignPriceColor(
    if !paintBar       then Color.CURRENT
    else if trendDir ==  1 then GlobalColor("Long")
    else if trendDir == -1 then GlobalColor("Short")
    else Color.GRAY
);

# ─── Alerts ───────────────────────────────────────────────────────────────────

Alert(alerts and nlmsAtrLong,  "NLMS ATR Trend turned Bullish", Alert.BAR, Sound.Ding);
Alert(alerts and nlmsAtrShort, "NLMS ATR Trend turned Bearish", Alert.BAR, Sound.Ding);

# ─── Trail ATR Bands ──────────────────────────────────────────────────────────

input bandAtrPeriod = 200;
input bandMult1     = 2.0;
input bandMult2     = 4.0;
input bandMult3     = 6.0;
input bandMult4     = 8.0;
input bandMult5     = 10.0;
input showBands     = yes;

def bATR = ATR(bandAtrPeriod);

plot Band1Up = if showBands then nlmsAtr + bATR * bandMult1 else Double.NaN;
plot Band1Dn = if showBands then nlmsAtr - bATR * bandMult1 else Double.NaN;
plot Band2Up = if showBands then nlmsAtr + bATR * bandMult2 else Double.NaN;
plot Band2Dn = if showBands then nlmsAtr - bATR * bandMult2 else Double.NaN;
plot Band3Up = if showBands then nlmsAtr + bATR * bandMult3 else Double.NaN;
plot Band3Dn = if showBands then nlmsAtr - bATR * bandMult3 else Double.NaN;
plot Band4Up = if showBands then nlmsAtr + bATR * bandMult4 else Double.NaN;
plot Band4Dn = if showBands then nlmsAtr - bATR * bandMult4 else Double.NaN;
plot Band5Up = if showBands then nlmsAtr + bATR * bandMult5 else Double.NaN;
plot Band5Dn = if showBands then nlmsAtr - bATR * bandMult5 else Double.NaN;

# 1 ATR —
Band1Up.SetDefaultColor(Color.gray);
Band1Up.SetLineWeight(1);
Band1Dn.SetDefaultColor(Color.gray);
Band1Dn.SetLineWeight(1);

# 2 ATR —
Band2Up.SetDefaultColor(Color.dark_orange);
Band2Up.SetLineWeight(1);
#Band2Up.SetStyle(Curve.SHORT_DASH);
Band2Dn.SetDefaultColor(Color.lime);
Band2Dn.SetLineWeight(1);

# 3 ATR —
Band3Up.SetDefaultColor(Color.red);
Band3Up.SetLineWeight(1);
Band3Dn.SetDefaultColor(Color.green);
Band3Dn.SetLineWeight(1);

# 4 ATR —
Band4Up.SetDefaultColor(Color.magenta);
Band4Up.SetLineWeight(1);
Band4Dn.SetDefaultColor(Color.cyan);
Band4Dn.SetLineWeight(1);

# 5 ATR —
Band5Up.SetDefaultColor(Color.white);
Band5Up.SetLineWeight(1);
Band5Dn.SetDefaultColor(Color.white);
Band5Dn.SetLineWeight(1);

# Zone fills between bands
AddCloud(Band3Up, Band4Up, color.dark_red, color.dark_red);
AddCloud(Band4Up, Band5Up, color.magenta, color.magenta);
AddCloud(Band5Dn, Band4Dn, color.cyan, color.cyan);
AddCloud(Band4Dn, Band3Dn, color.dark_green, color.dark_green);
AddCloud(Band1Dn, Band1Up, color.gray, color.gray);
AddCloud(Band2Dn, Band2Up, color.gray, color.gray);

WATCHLIST COLUMN: This indicates when lows or highs are in the 3 to 5 or -3 to -5 bands.

Code:
# NLMS Volatility Trail-ATR Envelope Watchlist
# Converted and Adapted by Chewie 6/27/2026
# Original Concept: https://www.tradingview.com/script/UUXdk60N-NLMS-Volatility-Trail-BackQuant/

declare upper;
# ─── Inputs ───────────────────────────────────────────────────────────────────
input taps      = 72;
input mu        = 0.195;
input eps       = 0.6;
input periodAtr = 14;
input factorAtr = 1.7;
input lineW     = 3;

# ─── NLMS Filter (fold-based M-tap approximation) ─────────────────────────────
def src       = close;
def M = taps;

def power = fold i = 0 to M with p = 0 do
    p + Sqr(GetValue(src, i + 1));

def pred_raw = fold j = 0 to M with s = 0 do
    s + GetValue(src, j + 1);

def pred = pred_raw / M;
def err  = src - pred[1];
def k    = mu / (eps + power);
def raw  = pred + k * err * src[1];

# ─── ATR Band Trail ───────────────────────────────────────────────────────────

def atr            = ATR(periodAtr);
def trueRange      = atr * factorAtr;
def trueRangeUpper = raw + trueRange;
def trueRangeLower = raw - trueRange;

def nlmsAtr;
nlmsAtr = if IsNaN(nlmsAtr[1])           then raw
     else if trueRangeLower > nlmsAtr[1] then trueRangeLower
     else if trueRangeUpper < nlmsAtr[1] then trueRangeUpper
     else nlmsAtr[1];

# ─── Trend Direction ──────────────────────────────────────────────────────────

def nlmsAtrLong  = nlmsAtr > nlmsAtr[1] and nlmsAtr[1] <= nlmsAtr[2];
def nlmsAtrShort = nlmsAtr < nlmsAtr[1] and nlmsAtr[1] >= nlmsAtr[2];

# ─── Trail Plot ───────────────────────────────────────────────────────────────

plot Trail = nlmsAtr;
Trail.SetLineWeight(lineW);


# ─── Trail ATR Bands ──────────────────────────────────────────────────────────

input bandAtrPeriod = 200;
input bandMult1     = 2.0;
input bandMult2     = 4.0;
input bandMult3     = 6.0;
input bandMult4     = 8.0;
input bandMult5     = 10.0;
input showBands     = yes;

def bATR = ATR(bandAtrPeriod);

plot Band1Up = if showBands then nlmsAtr + bATR * bandMult1 else Double.NaN;
plot Band1Dn = if showBands then nlmsAtr - bATR * bandMult1 else Double.NaN;
plot Band2Up = if showBands then nlmsAtr + bATR * bandMult2 else Double.NaN;
plot Band2Dn = if showBands then nlmsAtr - bATR * bandMult2 else Double.NaN;
plot Band3Up = if showBands then nlmsAtr + bATR * bandMult3 else Double.NaN;
plot Band3Dn = if showBands then nlmsAtr - bATR * bandMult3 else Double.NaN;
plot Band4Up = if showBands then nlmsAtr + bATR * bandMult4 else Double.NaN;
plot Band4Dn = if showBands then nlmsAtr - bATR * bandMult4 else Double.NaN;
plot Band5Up = if showBands then nlmsAtr + bATR * bandMult5 else Double.NaN;
plot Band5Dn = if showBands then nlmsAtr - bATR * bandMult5 else Double.NaN;

def UL = high > Band3Up and high < Band4Up;
def UL1 = high > Band4Up and high < Band5Up;
def UL2 = high > Band5Up;
def LL = low < Band3Dn and low > Band4Dn;
def LL1 = low < Band4Dn and low > Band5Dn;
def LL2 = low < Band5Dn;

AddLabel(yes,  if UL then "3" else if UL1 then "4" else if UL2 then "5" else if LL  then "-3" else if LL1 then "-4" else if LL2 then "-5" else " ",if UL2 or LL2 then COLOR.black else if LL or UL then color.yellow else if  UL1 or LL1 then color.blue else Color.BLACK);

AssignBACKGROUNDColor(if UL then color.dark_red else if UL1 then color.magenta else if UL2 then color.white else if LL then color.dark_green else if LL1 then color.cyan else if LL2 then color.white else color.black);
 
Last edited:

Join useThinkScript to post your question to a community of 21,000+ developers and traders.

Similar threads

Not the exact question you're looking for?

Start a new thread and receive assistance from our community.

87k+ Posts
888 Online
Create Post

Similar threads

Similar threads

The Market Trading Game Changer

Join 2,500+ subscribers inside the useThinkScript VIP Membership Club
  • Exclusive indicators
  • Proven strategies & setups
  • Private Discord community
  • ‘Buy The Dip’ signal alerts
  • Exclusive members-only content
  • Add-ons and resources
  • 1 full year of unlimited support

Frequently Asked Questions

What is useThinkScript?

useThinkScript is the #1 community of stock market investors using indicators and other tools to power their trading strategies. Traders of all skill levels use our forums to learn about scripting and indicators, help each other, and discover new ways to gain an edge in the markets.

How do I get started?

We get it. Our forum can be intimidating, if not overwhelming. With thousands of topics, tens of thousands of posts, our community has created an incredibly deep knowledge base for stock traders. No one can ever exhaust every resource provided on our site.

If you are new, or just looking for guidance, here are some helpful links to get you started.

What are the benefits of VIP Membership?
VIP members get exclusive access to these proven and tested premium indicators: Buy the Dip, Advanced Market Moves 2.0, Take Profit, and Volatility Trading Range. In addition, VIP members get access to over 50 VIP-only custom indicators, add-ons, and strategies, private VIP-only forums, private Discord channel to discuss trades and strategies in real-time, customer support, trade alerts, and much more. Learn all about VIP membership here.
How can I access the premium indicators?
To access the premium indicators, which are plug and play ready, sign up for VIP membership here.
Back
Top