Projection Oscillator
length = 14 | smooth_length = 4 Overview
Projection Oscillator fits rolling regression-style slopes to recent highs and lows, projects those channel edges across the active lookback window, and then expresses the current source value as a percentage of that projected range. That raw position is smoothed into the final PBO line, and a second weighted smoothing pass produces the companion signal line.
The result is useful when you want to know whether price is clustering near the top or bottom of a projected channel rather than simply above or below a moving average. VectorTA supports candle-based close input, explicit high-low-source slices, streaming updates, and parameter sweeps across both the projection window and smoothing.
Defaults: `length = 14` and `smooth_length = 4`.
Implementation Examples
Run Projection Oscillator from candles or from explicit high, low, and source slices.
use vector_ta::indicators::projection_oscillator::{
projection_oscillator,
ProjectionOscillatorInput,
ProjectionOscillatorParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};
let direct = projection_oscillator(&ProjectionOscillatorInput::from_slices(
&high,
&low,
&close,
ProjectionOscillatorParams {
length: Some(14),
smooth_length: Some(4),
},
))?;
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let from_candles = projection_oscillator(&ProjectionOscillatorInput::with_default_candles(&candles))?;
println!("pbo = {:?}", direct.pbo.last());
println!("signal = {:?}", from_candles.signal.last());API Reference
Input Methods▼
ProjectionOscillatorInput::from_candles(&Candles, "close", ProjectionOscillatorParams)
-> ProjectionOscillatorInput
ProjectionOscillatorInput::from_slices(&[f64], &[f64], &[f64], ProjectionOscillatorParams)
-> ProjectionOscillatorInput
ProjectionOscillatorInput::with_default_candles(&Candles)
-> ProjectionOscillatorInputParameters Structure▼
pub struct ProjectionOscillatorParams {
pub length: Option<usize>, // default 14
pub smooth_length: Option<usize>, // default 4
}Output Structure▼
pub struct ProjectionOscillatorOutput {
pub pbo: Vec<f64>,
pub signal: Vec<f64>,
}Validation & Warmup▼
- High, low, and source arrays must be non-empty, equal in length, and contain enough valid triples for the selected lookback.
lengthandsmooth_lengthmust both be greater than0.- Invalid stream triples reset the rolling projection state and return
None. - The PBO and signal warmups are exposed separately through
get_pbo_warmup_period()andget_signal_warmup_period(). - Batch mode validates both numeric sweep axes and rejects non-batch kernels.
Builder, Streaming & Batch APIs▼
ProjectionOscillatorBuilder::new()
.length(usize)
.smooth_length(usize)
.kernel(Kernel)
.apply(&Candles)
.apply_slices(&[f64], &[f64], &[f64])
.into_stream()
ProjectionOscillatorStream::try_new(params)
stream.update(high, low, source) -> Option<(f64, f64)>
stream.reset()
stream.get_pbo_warmup_period() -> usize
stream.get_signal_warmup_period() -> usize
ProjectionOscillatorBatchBuilder::new()
.length_range((start, end, step))
.smooth_length_range((start, end, step))
.kernel(Kernel)
.apply_slices(&[f64], &[f64], &[f64])
.apply(&Candles)Python Bindings
Python exposes a scalar function returning two NumPy arrays, a stream class returning the latest PBO and signal pair, and a batch helper returning reshaped matrices plus the resolved parameter axes.
from vector_ta import (
projection_oscillator,
projection_oscillator_batch,
ProjectionOscillatorStream,
)
pbo, signal = projection_oscillator(high, low, close, length=14, smooth_length=4)
stream = ProjectionOscillatorStream(length=14, smooth_length=4)
point = stream.update(high[-1], low[-1], close[-1])
batch = projection_oscillator_batch(
high,
low,
close,
length_range=(10, 20, 5),
smooth_length_range=(3, 5, 1),
)
print(pbo[-1], signal[-1])
print(batch["rows"], batch["cols"])JavaScript/WASM Bindings
The WASM layer exposes scalar and batch object-returning helpers plus allocation and into-buffer APIs that write both output series into caller-managed memory.
import init, {
projection_oscillator_js,
projection_oscillator_batch_js,
projection_oscillator_alloc,
projection_oscillator_free,
projection_oscillator_into,
projection_oscillator_batch_into,
} from "vector-ta-wasm";
await init();
const single = projection_oscillator_js(high, low, close, 14, 4);
const batch = projection_oscillator_batch_js(high, low, close, {
length_range: [10, 20, 5],
smooth_length_range: [3, 5, 1],
});
console.log(single.pbo, single.signal);CUDA Bindings (Rust)
Additional details for the CUDA bindings can be found inside the VectorTA repository.
Performance Analysis
Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)