Projection Oscillator

Parameters: 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)
    -> ProjectionOscillatorInput
Parameters 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.
  • length and smooth_length must both be greater than 0.
  • Invalid stream triples reset the rolling projection state and return None.
  • The PBO and signal warmups are exposed separately through get_pbo_warmup_period() and get_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

Comparison:
View:
Placeholder data (no recorded benchmarks for this indicator)

Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.

Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)

Related Indicators