Nadaraya-Watson Envelope (NWE)
bandwidth = 8 | multiplier = 3 | lookback = 500 Overview
The Nadaraya Watson Envelope employs kernel regression with Gaussian weighting to create a smooth trend estimate surrounded by adaptive bands that expand and contract based on local price volatility. This non parametric approach assigns exponentially decreasing weights to historical data points based on their distance from the current position, with the bandwidth parameter controlling how quickly this influence decays. The indicator calculates upper and lower bands by measuring the mean absolute error over a rolling window, then multiplying by a user defined factor to create an envelope that contains most price action. Unlike traditional moving average envelopes, NWE adapts its band width to market conditions, tightening during stable periods and widening when volatility increases. Traders leverage this adaptive behavior to identify trend direction while the dynamic bands serve as support and resistance levels, with price breakouts beyond the envelope often signaling significant moves or potential reversals.
Implementation Examples
Compute NWE for prices or candles:
use vectorta::indicators::nadaraya_watson_envelope::{nadaraya_watson_envelope, NweInput, NweParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};
// From a price slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let params = NweParams { bandwidth: Some(8.0), multiplier: Some(3.0), lookback: Some(500) };
let input = NweInput::from_slice(&prices, params);
let out = nadaraya_watson_envelope(&input)?; // NweOutput { upper, lower }
// From candles with defaults (source = "close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = NweInput::with_default_candles(&candles);
let out = nadaraya_watson_envelope(&input)?;
// Access envelope values
for (u, l) in out.upper.iter().zip(out.lower.iter()) {
println!("upper: {u}, lower: {l}");
} API Reference
Input Methods ▼
// From price slice
NweInput::from_slice(&[f64], NweParams) -> NweInput
// From candles with custom source
NweInput::from_candles(&Candles, &str, NweParams) -> NweInput
// From candles with defaults (close, h=8.0, m=3.0, L=500)
NweInput::with_default_candles(&Candles) -> NweInput Parameters Structure ▼
pub struct NweParams {
pub bandwidth: Option<f64>, // Default: 8.0
pub multiplier: Option<f64>, // Default: 3.0
pub lookback: Option<usize>, // Default: 500
} Output Structure ▼
pub struct NweOutput {
pub upper: Vec<f64>, // Upper envelope (y + m * MAE)
pub lower: Vec<f64>, // Lower envelope (y - m * MAE)
} Validation, Warmup & NaNs ▼
bandwidth > 0and finite;multiplier ≥ 0and finite;lookback > 0.- Data must contain at least
lookbackvalid points after the first finite value, elseNweError::NotEnoughValidData. - Warmup: regression warmup ends at
warm_out = first_valid + lookback - 1; envelope starts after an additional499-sample MAE window (warm_total = warm_out + 498). - Indices before
warm_totalareNaN;NaNinputs propagate within the windows. - Streaming:
NweStream::updatereturnsNoneuntil both the regression window and MAE window are filled and free ofNaNs.
Error Handling ▼
use vectorta::indicators::nadaraya_watson_envelope::NweError;
match nadaraya_watson_envelope(&input) {
Ok(output) => handle(output.upper, output.lower),
Err(NweError::EmptyInputData) => eprintln!("Input data is empty"),
Err(NweError::AllValuesNaN) => eprintln!("All input values are NaN"),
Err(NweError::InvalidBandwidth { bandwidth }) => eprintln!("Invalid bandwidth: {}", bandwidth),
Err(NweError::InvalidMultiplier { multiplier }) => eprintln!("Invalid multiplier: {}", multiplier),
Err(NweError::InvalidLookback { lookback }) => eprintln!("Invalid lookback: {}", lookback),
Err(NweError::NotEnoughValidData { needed, valid }) => eprintln!("Need {} valid points, only {}", needed, valid),
Err(NweError::InvalidPeriod { .. }) => eprintln!("Invalid batch/kernel configuration"),
} Python Bindings
Basic Usage ▼
Calculate upper/lower envelopes using NumPy arrays (defaults: h=8.0, m=3.0, L=500):
import numpy as np
from vectorta import nadaraya_watson_envelope
prices = np.array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5])
# Defaults
upper, lower = nadaraya_watson_envelope(prices)
# Custom parameters; optional kernel string is accepted but not used by NWE
upper, lower = nadaraya_watson_envelope(prices, bandwidth=8.0, multiplier=3.0, lookback=500, kernel=None)
print("Upper:", upper)
print("Lower:", lower) Streaming Real-time Updates ▼
Use the NweStream class for O(1) updates:
from vectorta import NweStream
stream = NweStream(bandwidth=8.0, multiplier=3.0, lookback=500)
for price in price_feed:
result = stream.update(price) # returns (upper, lower) or None during warmup
if result is not None:
upper, lower = result
print(upper, lower) Batch Parameter Optimization ▼
Test multiple combinations and access results as 2D arrays:
import numpy as np
from vectorta import nadaraya_watson_envelope_batch
prices = np.array([...], dtype=np.float64)
out = nadaraya_watson_envelope_batch(
prices,
bandwidth_range=(4.0, 10.0, 2.0),
multiplier_range=(2.0, 4.0, 1.0),
lookback_range=(300, 600, 100),
kernel="auto"
)
upper = out["upper"] # shape: [rows, len(prices)]
lower = out["lower"] # shape: [rows, len(prices)]
bandwidths = out["bandwidths"]
multipliers = out["multipliers"]
lookbacks = out["lookbacks"] CUDA Acceleration ▼
CUDA support for NWE is currently under development. The API will follow the same pattern as other CUDA-enabled indicators.
JavaScript/WASM Bindings
Basic Usage ▼
Compute NWE and split flattened output into upper/lower:
import { nadaraya_watson_envelope_js } from 'vectorta-wasm';
const prices = new Float64Array([100, 102, 101.5, 103, 105, 104.5]);
const flat = nadaraya_watson_envelope_js(prices, 8.0, 3.0, 500);
const n = prices.length;
const upper = flat.slice(0, n);
const lower = flat.slice(n);
console.log('upper:', upper);
console.log('lower:', lower); Memory-Efficient Operations ▼
Use zero-copy into_flat and a single output buffer sized 2 × len:
import { nadaraya_watson_envelope_alloc, nadaraya_watson_envelope_free, nadaraya_watson_envelope_into_flat, memory } from 'vectorta-wasm';
const prices = new Float64Array([/* your data */]);
const n = prices.length;
// Allocate a single output buffer for [upper..., lower...]
const inPtr = nadaraya_watson_envelope_alloc(n);
const outPtr = nadaraya_watson_envelope_alloc(n); // capacity accounts for 2*n values
// Copy input data into WASM memory
new Float64Array(memory.buffer, inPtr, n).set(prices);
// Compute directly into the output buffer
nadaraya_watson_envelope_into_flat(inPtr, outPtr, n, 8.0, 3.0, 500);
// Read results and split
const flat = new Float64Array(memory.buffer, outPtr, 2 * n).slice();
const upper = flat.slice(0, n);
const lower = flat.slice(n);
// Free buffers
nadaraya_watson_envelope_free(inPtr, n);
nadaraya_watson_envelope_free(outPtr, n); Batch Processing ▼
Run multiple parameter combinations and reshape results:
import { nadaraya_watson_envelope_batch } from 'vectorta-wasm';
const prices = new Float64Array([/* historical prices */]);
const bw = new Float64Array([4.0, 10.0, 2.0]); // [start, end, step]
const mult = new Float64Array([2.0, 4.0, 1.0]); // [start, end, step]
const lb = new Uint32Array([300, 600, 100]); // [start, end, step]
const out = nadaraya_watson_envelope_batch(prices, bw, mult, lb);
// out.upper and out.lower are flattened row-major arrays (rows x len)
// out.bandwidths / out.multipliers / out.lookbacks list parameter combos per row
console.log(out.rows, out.cols);
console.log(out.bandwidths, out.multipliers, out.lookbacks); Performance Analysis
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05