Nadaraya-Watson Envelope
The Nadaraya-Watson Envelope uses kernel regression to draw a smooth line through price with two bands around it; Vlevel's version does not repaint by default.
What it is
Nadaraya-Watson regression is a statistical method for estimating the hidden trend in noisy data. For each point, nearby prices are averaged with more weight and distant ones with less (a Gaussian kernel). The result is a very smooth line, with upper and lower bands drawn from the average distance of price from it.
How it's calculated
- Weight of each past candle = exp(−distance² ÷ (2 × bandwidth²))
- Middle line = weighted average of prices with those weights
- Bands = middle line ± multiplier × mean absolute distance of price from the line
How traders read it
- Price leaving the upper or lower band and coming back inside is the indicator's common signal.
- The middle line's slope shows the overall direction.
- It works better in ranging, swinging markets than in powerful trends.
Settings
The bandwidth sets how smooth the line is and the multiplier how far the bands sit. In Vlevel the default mode doesn't repaint and uses only past candles; the classic look is turned on with the Repainting Smoothing option.
Where it misleads
- The classic version repaints: past values change as new candles arrive, so it looks perfect on the chart, but those signals didn't exist in real time.
- Backtest with repainting off (the default) before trusting its signals.
Its Pine Script on Vlevel
This is the script the chart runs. In the chart's indicators window you can open it, change it and save your own copy.
Nadaraya-Watson Envelope · Pine Script v6
//@version=6
indicator("Nadaraya-Watson Envelope", shorttitle = "NWE", overlay = true, max_lines_count = 500, max_labels_count = 500, max_bars_back = 500)
h = input.float(8.0, "Bandwidth", minval = 1, step = 0.5, tooltip = "How many bars the smoothing reaches: higher is smoother and slower.")
mult = input.float(3.0, "Envelope Multiplier", minval = 0, step = 0.1, tooltip = "The bands' distance, in multiples of the mean absolute distance between price and the middle line.")
src = input.source(close, "Source")
envLen = input.int(200, "Envelope Length", minval = 10, maxval = 1000, tooltip = "Bars the mean absolute distance is averaged over (without repainting).")
repaint = input.bool(false, "Repainting Smoothing", tooltip = "On: the classic look, smoothing with bars on both sides, redrawn on every bar over the recent window (the past changes; don't backtest on it). Off: each bar only uses itself and the bars before it, so nothing drawn changes later.")
win = input.int(250, "Repainting Window", minval = 20, maxval = 250, tooltip = "Bars redrawn when repainting is on.")
signals = input.bool(true, "Show Signals", tooltip = "A mark where price crosses back inside a band.")
showMid = input.bool(false, "Show Middle Line")
upCol = input.color(#ff4d6a, "Upper Band")
dnCol = input.color(#2ee6a6, "Lower Band")
midCol = input.color(color.new(#8a959c, 30), "Middle Line")
// ---- Without repainting: each bar's estimate uses only it and the bars before it, up to 4
// bandwidths back (the weight beyond is under 0.04% of the nearest one), so nothing drawn changes
// later. ta.kernel is Vlevel's built-in for the Gaussian-weighted average (a Pine loop doing the
// same sums is far slower).
nw = ta.kernel(src, h, math.min(500, int(math.ceil(h * 4))))
mae = ta.sma(math.abs(src - nw), envLen) * mult
upper = nw + mae
lower = nw - mae
plot(repaint ? na : upper, "Upper", color = upCol, width = 2)
plot(repaint ? na : lower, "Lower", color = dnCol, width = 2)
plot(repaint or not showMid ? na : nw, "Middle", color = midCol)
plotshape(signals and not repaint and ta.crossunder(src, upper), "Sell", style = shape.triangledown, location = location.abovebar, color = upCol, size = size.tiny)
plotshape(signals and not repaint and ta.crossover(src, lower), "Buy", style = shape.triangleup, location = location.belowbar, color = dnCol, size = size.tiny)
// ---- Repainting: on the last bar, every bar of the window smoothed with the bars on both sides of
// it (ta.kernel_window, index 0 = the last bar), and the envelope at the mean absolute distance.
ys = ta.kernel_window(src, h, win)
if repaint and barstate.islast and array.size(ys) > 1
n = array.size(ys) - 1
float sae = 0.0
for i = 0 to n
sae += math.abs(src[i] - array.get(ys, i))
m = sae / (n + 1) * mult
for i = 1 to n
y0 = array.get(ys, i)
y1 = array.get(ys, i - 1)
line.new(bar_index - i, y0 + m, bar_index - i + 1, y1 + m, color = upCol, width = 2)
line.new(bar_index - i, y0 - m, bar_index - i + 1, y1 - m, color = dnCol, width = 2)
if showMid
line.new(bar_index - i, y0, bar_index - i + 1, y1, color = midCol)
if signals and src[i] > y0 + m and src[i - 1] < y1 + m
label.new(bar_index - i + 1, src[i - 1], "▼", color = color.new(color.black, 100), textcolor = upCol, style = label.style_label_down, size = size.small)
if signals and src[i] < y0 - m and src[i - 1] > y1 - m
label.new(bar_index - i + 1, src[i - 1], "▲", color = color.new(color.black, 100), textcolor = dnCol, style = label.style_label_up, size = size.small)Indicators describe past prices; none predicts the future. For education, not investment advice.