Polynomial Regression Bands (PRB)

Parameters: 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 vectorta::indicators::prb::{prb, PrbInput, PrbParams};
use vectorta::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. If smooth_data, require smooth_period \u2265 2. regression_period \u2265 1 and must not exceed input length.
  • Let first be the index of the first finite input. Warmup index is first + regression_period - 1 + equ_from. Indices before warmup are NaN.
  • Streaming: PrbStream::update returns None until the warmup fills; then returns (reg, upper, lower).
Error Handling
use vectorta::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 vectorta 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 vectorta 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 vectorta 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 bindings for PRB are in development. The API will follow the same patterns used by other CUDA-enabled indicators.

# Coming soon: CUDA-accelerated PRB helpers
# from vectorta import prb_cuda_batch, prb_cuda_many_series_one_param
# ...

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);

Performance Analysis

Comparison:
View:
Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05

Related Indicators