Pretty Good Oscillator
length = 14 Overview
Pretty Good Oscillator compares a chosen source series to its simple moving average and scales that distance by the current Average True Range. The result behaves like a standardized distance-from-mean measure, which makes it easier to compare momentum stretches across markets that trade at very different volatility levels.
In VectorTA the candle path defaults to close as the source, while the slice path lets you pass separate high, low, close, and source arrays. The streaming implementation updates SMA and ATR together, so it is suitable for real-time feeds where each new bar should immediately produce a fresh normalized reading once warmup is met.
Defaults: `length = 14`.
Implementation Examples
Use the candle path for close-based PGO or provide your own source slice alongside OHLC arrays.
use vector_ta::indicators::pretty_good_oscillator::{
pretty_good_oscillator,
PrettyGoodOscillatorInput,
PrettyGoodOscillatorParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};
let direct = pretty_good_oscillator(&PrettyGoodOscillatorInput::from_slices(
&high,
&low,
&close,
&close,
PrettyGoodOscillatorParams { length: Some(14) },
))?;
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let from_candles = pretty_good_oscillator(&PrettyGoodOscillatorInput::with_default_candles(&candles))?;
println!("latest = {:?}", direct.values.last());
println!("candle latest = {:?}", from_candles.values.last());API Reference
Input Methods▼
PrettyGoodOscillatorInput::from_candles(&Candles, "close", PrettyGoodOscillatorParams)
-> PrettyGoodOscillatorInput
PrettyGoodOscillatorInput::from_slices(&[f64], &[f64], &[f64], &[f64], PrettyGoodOscillatorParams)
-> PrettyGoodOscillatorInput
PrettyGoodOscillatorInput::with_default_candles(&Candles)
-> PrettyGoodOscillatorInputParameters Structure▼
pub struct PrettyGoodOscillatorParams {
pub length: Option<usize>, // default 14
}Output Structure▼
pub struct PrettyGoodOscillatorOutput {
pub values: Vec<f64>,
}Validation, Warmup & Bars▼
- High, low, close, and source arrays must all have the same length and the input must not be empty.
lengthmust be greater than0and cannot exceed the available data length.- A valid bar requires finite
high,low,close, andsourcevalues plushigh >= low. - The direct path warms up through
first_valid + length - 1; earlier values are initialized toNaN. - The stream returns
Noneon invalid bars instead of advancing with broken ATR or SMA state. - Batch mode validates the length sweep and rejects non-batch kernels.
Builder, Streaming & Batch APIs▼
PrettyGoodOscillatorBuilder::new()
.length(usize)
.kernel(Kernel)
.apply(&Candles)
.apply_slices(&[f64], &[f64], &[f64], &[f64])
.into_stream()
PrettyGoodOscillatorStream::try_new(params)
stream.update(high, low, close, source) -> Option<f64>
PrettyGoodOscillatorBatchBuilder::new()
.length_range(start, end, step)
.length_static(usize)
.kernel(Kernel)
.apply_candles(&Candles)
.apply_slices(&[f64], &[f64], &[f64], &[f64])Python Bindings
Python exposes a scalar function, a stream class, and a batch helper. The scalar path accepts four NumPy arrays, the stream updates one bar at a time, and the batch helper returns a matrix plus the resolved length grid.
from vector_ta import (
pretty_good_oscillator,
pretty_good_oscillator_batch,
PrettyGoodOscillatorStream,
)
values = pretty_good_oscillator(high, low, close, close, length=14)
stream = PrettyGoodOscillatorStream(length=14)
point = stream.update(high[-1], low[-1], close[-1], close[-1])
batch = pretty_good_oscillator_batch(
high,
low,
close,
close,
length_range=(10, 20, 5),
)
print(batch["values"].shape)
print(batch["lengths"])JavaScript/WASM Bindings
The WASM layer exposes a scalar function returning one series, a batch helper returning flattened batch output, and low-level allocation and into-buffer functions for caller-managed memory.
import init, {
pretty_good_oscillator_js,
pretty_good_oscillator_batch_js,
pretty_good_oscillator_alloc,
pretty_good_oscillator_free,
pretty_good_oscillator_into,
pretty_good_oscillator_into_host,
pretty_good_oscillator_batch_into,
} from "vector-ta-wasm";
await init();
const single = pretty_good_oscillator_js(high, low, close, close, 14);
console.log(single);
const batch = pretty_good_oscillator_batch_js(high, low, close, close, {
length_range: [10, 20, 5],
});
const ptr = pretty_good_oscillator_alloc(close.length);
pretty_good_oscillator_free(ptr, close.length);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)