Polynomial Regression Bands (PRB)
smooth_period = 10 | regression_period = 100 | polynomial_order = 2 | regression_offset = 0 | ndev = 2 | equ_from = 0 Overview
Polynomial Regression Bands fit a polynomial curve to price data over a rolling window and construct volatility adjusted envelopes around the regression line, creating a dynamic channel that adapts to both trend direction and magnitude. The indicator optionally applies a two pole Super Smoother Filter to remove high frequency noise before performing least squares polynomial regression, then evaluates the fitted polynomial at a specified point within the window to generate the central line. Upper and lower bands are calculated by adding and subtracting a multiple of the rolling standard deviation from the regression value, creating boundaries that expand during volatile periods and contract when price action stabilizes. Higher polynomial orders capture more complex curve shapes like parabolic moves or S curves, while the regression offset parameter allows forecasting by evaluating the polynomial beyond the current bar. Traders use the regression line as a dynamic trend baseline and the bands as overbought oversold zones, with price touching the upper band in an uptrend signaling potential profit taking opportunities and breaks below the lower band suggesting possible reversals. The pre smoothing option helps on noisy intraday data where raw price fluctuations would destabilize the regression fit, while the configurable period and deviation multiplier allow optimization for different market conditions and trading styles.
Defaults (chart UI): smooth_period=10, regression_period=100, polynomial_order=2, regression_offset=0, ndev=2.0, equ_from=0 (SSF pre-smoothing is always enabled).
Implementation Examples
Compute PRB from a slice or candles:
use vector_ta::indicators::prb::{prb, PrbInput, PrbParams};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};
// From a numeric slice
let data = vec![100.0, 102.0, 101.0, 103.5, 104.2, 105.0];
let params = PrbParams { // Defaults: smooth=true, smooth_period=10, regression_period=100, order=2, offset=0, ndev=2.0, equ_from=0
smooth_data: Some(true),
smooth_period: Some(10),
regression_period: Some(100),
polynomial_order: Some(2),
regression_offset: Some(0),
ndev: Some(2.0),
equ_from: Some(0),
};
let input = PrbInput::from_slice(&data, params);
let out = prb(&input)?; // PrbOutput { values, upper_band, lower_band }
// From Candles (defaults, source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = PrbInput::with_default_candles(&candles);
let out = prb(&input)?;
// Access results
println!("reg: {} | upper: {} | lower: {}", out.values[200], out.upper_band[200], out.lower_band[200]); API Reference
Input Methods ▼
// From price slice
PrbInput::from_slice(&[f64], PrbParams) -> PrbInput
// From candles with custom source
PrbInput::from_candles(&Candles, &str, PrbParams) -> PrbInput
// From candles with defaults (close prices)
PrbInput::with_default_candles(&Candles) -> PrbInput Parameters Structure ▼
pub struct PrbParams {
pub smooth_data: Option<bool>, // Default: true
pub smooth_period: Option<usize>, // Default: 10 (min 2 if smoothing)
pub regression_period: Option<usize>, // Default: 100 (min 1)
pub polynomial_order: Option<usize>, // Default: 2 (min 1)
pub regression_offset: Option<i32>, // Default: 0
pub ndev: Option<f64>, // Default: 2.0
pub equ_from: Option<usize>, // Default: 0
} Output Structure ▼
pub struct PrbOutput {
pub values: Vec<f64>, // Regression line
pub upper_band: Vec<f64>, // Regression + ndev * stdev
pub lower_band: Vec<f64>, // Regression - ndev * stdev
} Validation, Warmup & NaNs ▼
- Errors:
EmptyInputData,AllValuesNaN,InvalidPeriod,NotEnoughValidData,InvalidOrder,InvalidSmoothPeriod,SingularMatrix. polynomial_order \u2265 1. Ifsmooth_data, requiresmooth_period \u2265 2.regression_period \u2265 1and must not exceed input length.- Let
firstbe the index of the first finite input. Warmup index isfirst + regression_period - 1 + equ_from. Indices before warmup areNaN. - Streaming:
PrbStream::updatereturnsNoneuntil the warmup fills; then returns(reg, upper, lower).
Error Handling ▼
use vector_ta::indicators::prb::{prb, PrbInput, PrbParams, PrbError};
let input = PrbInput::from_slice(&data, PrbParams::default());
match prb(&input) {
Ok(out) => { /* use out.values / out.upper_band / out.lower_band */ }
Err(PrbError::EmptyInputData) => eprintln!("no data"),
Err(PrbError::AllValuesNaN) => eprintln!("all NaN"),
Err(PrbError::InvalidPeriod { period, data_len }) => { /* fix period */ }
Err(PrbError::NotEnoughValidData { needed, valid }) => { /* wait for more data */ }
Err(PrbError::InvalidOrder { .. }) => { /* set order >= 1 */ }
Err(PrbError::InvalidSmoothPeriod { .. }) => { /* set smooth_period >= 2 when smoothing */ }
Err(PrbError::SingularMatrix) => { /* adjust order/period to avoid degenerate design */ }
} Python Bindings
Basic Usage ▼
import numpy as np
from vector_ta import prb
prices = np.array([100.0, 102.0, 101.0, 103.5, 104.2], dtype=np.float64)
reg, upper, lower = prb(
prices,
smooth_data=True,
smooth_period=10,
regression_period=100,
polynomial_order=2,
regression_offset=0,
ndev=2.0,
kernel="auto" # optional
)
print(reg[-1], upper[-1], lower[-1]) Streaming Real-time Updates ▼
from vector_ta import PrbStreamPy
stream = PrbStreamPy(
smooth_data=True,
smooth_period=10,
regression_period=100,
polynomial_order=2,
regression_offset=0,
ndev=2.0,
)
for px in feed:
out = stream.update(px)
if out is not None:
reg, upper, lower = out
process(reg, upper, lower) Batch Parameter Optimization ▼
import numpy as np
from vector_ta import prb_batch
prices = np.array([...], dtype=np.float64)
res = prb_batch(
prices,
smooth_data=True,
smooth_period_start=8, smooth_period_end=14, smooth_period_step=2,
regression_period_start=50, regression_period_end=120, regression_period_step=10,
polynomial_order_start=1, polynomial_order_end=3, polynomial_order_step=1,
regression_offset_start=0, regression_offset_end=2, regression_offset_step=1,
kernel="auto"
)
values = res["values"] # shape: (rows, len(prices))
upper = res["upper"]
lower = res["lower"]
sp = res["smooth_periods"]
rp = res["regression_periods"]
po = res["polynomial_orders"]
ro = res["regression_offsets"] CUDA Acceleration ▼
CUDA helpers are available when the Python package is built with CUDA support. Inputs must be float32; outputs are device arrays (DLPack / __cuda_array_interface__ compatible).
import numpy as np
from vector_ta import prb_cuda_batch_dev, prb_cuda_many_series_one_param_dev
# One series (float32)
data_f32 = np.asarray(load_data(), dtype=np.float32)
dev = prb_cuda_batch_dev(
data_f32=data_f32,
smooth_data=False,
smooth_period_range=(5, 30, 5),
regression_period_range=(5, 30, 5),
polynomial_order_range=(2, 20, 2),
regression_offset_range=14,
device_id=0,
)
# Many series (time-major)
prices_tm_f32 = np.asarray(load_prices_time_major_matrix(), dtype=np.float32)
rows, cols = prices_tm_f32.shape
prices_tm_f32 = prices_tm_f32.ravel()
dev_tm = prb_cuda_many_series_one_param_dev(
prices_tm_f32=prices_tm_f32,
cols=cols,
rows=rows,
smooth_data=False,
smooth_period=14,
regression_period=14,
polynomial_order=14,
regression_offset=14,
ndev=1.0,
device_id=0,
) JavaScript/WASM Bindings
Basic Usage ▼
Compute PRB in JS/TS and unpack the three rows:
import { prb as prb_js } from 'vectorta-wasm';
const data = new Float64Array([100, 102, 101, 103.5, 104.2]);
const res = prb_js(data, true, 10, 100, 2, 0, 2.0); // PrbJsResult
// res.values is flattened [main..., upper..., lower...] with rows=3
const flat = res.values;
const len = res.cols; // = data.length
const main = flat.slice(0, len);
const upper = flat.slice(len, 2*len);
const lower = flat.slice(2*len, 3*len); Memory-Efficient Operations ▼
Use zero-copy pointers for large arrays:
import { prb_alloc, prb_free, prb_into, memory } from 'vectorta-wasm';
const data = new Float64Array([/* ... */]);
const n = data.length;
const inPtr = prb_alloc(n);
const outMainPtr = prb_alloc(n);
const outUpPtr = prb_alloc(n);
const outLoPtr = prb_alloc(n);
new Float64Array(memory.buffer, inPtr, n).set(data);
// in_ptr, out_main, out_upper, out_lower, len, smooth_data, smooth_period, regression_period, order, offset, ndev
prb_into(inPtr, outMainPtr, outUpPtr, outLoPtr, n, true, 10, 100, 2, 0, 2.0);
const main = new Float64Array(memory.buffer, outMainPtr, n).slice();
const upper = new Float64Array(memory.buffer, outUpPtr, n).slice();
const lower = new Float64Array(memory.buffer, outLoPtr, n).slice();
prb_free(inPtr, n);
prb_free(outMainPtr, n);
prb_free(outUpPtr, n);
prb_free(outLoPtr, n); Batch Processing ▼
import { prb_batch_js, prb_batch_unified } from 'vectorta-wasm';
const data = new Float64Array([/* prices */]);
// Explicit range arguments
const flat = prb_batch_js(
data,
true,
8, 14, 2,
50, 120, 10,
1, 3, 1,
0, 2, 1
);
// Or unified config object
const cfg = { smooth_period: [8, 14, 2], regression_period: [50, 120, 10], polynomial_order: [1, 3, 1], regression_offset: [0, 2, 1] };
const unified = prb_batch_unified(data, cfg, true); CUDA Bindings (Rust)
use vector_ta::cuda::CudaPrb;
use vector_ta::indicators::prb::PrbBatchRange;
let cuda = CudaPrb::new(0)?;
let data_f32: [f32] = /* ... */;
let sweep = PrbBatchRange::default();
let smooth_data: bool = /* ... */;
let out = cuda.prb_batch_dev(&data_f32, &sweep, smooth_data)?;
let _ = out; Performance Analysis
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-02-28