Ultimate Oscillator (ULTOSC)

Parameters: short_period = 7 | medium_period = 14 | long_period = 28

Overview

The Ultimate Oscillator combines buying pressure measurements across three different timeframes to reduce false signals inherent in single-period momentum indicators. Developed by Larry Williams, it calculates buying pressure relative to true range for short, medium, and long periods, then blends these values using a 4:2:1 weighting scheme that emphasizes recent activity while maintaining longer term context. This multi-timeframe approach helps the oscillator avoid the whipsaws and premature reversals that plague simpler momentum tools, providing more reliable overbought and oversold readings. Values range from 0 to 100, with readings above 70 typically indicating overbought conditions and below 30 suggesting oversold levels, though divergences between the oscillator and price often provide the most actionable signals. Traders appreciate how the indicator synthesizes different time perspectives into a single coherent momentum picture, making it easier to identify high probability reversal zones. Default parameters use periods of 7, 14, and 28 bars, representing fast, intermediate, and slow momentum components that together capture a comprehensive view of market strength.

Implementation Examples

Get started with ULTOSC in a few lines:

use vectorta::indicators::ultosc::{ultosc, UltOscInput, UltOscParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using with H/L/C slices
let high = vec![/* ... */];
let low = vec![/* ... */];
let close = vec![/* ... */];
let params = UltOscParams { timeperiod1: Some(7), timeperiod2: Some(14), timeperiod3: Some(28) };
let input = UltOscInput::from_slices(&high, &low, &close, params);
let result = ultosc(&input)?;

// Using with Candles (defaults: 7/14/28; sources "high","low","close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = UltOscInput::with_default_candles(&candles);
let result = ultosc(&input)?;

// Access the values
for v in result.values { println!("ULTOSC: {}", v); }

API Reference

Input Methods
// From H/L/C slices
UltOscInput::from_slices(&[f64], &[f64], &[f64], UltOscParams) -> UltOscInput

// From candles with custom sources
UltOscInput::from_candles(&Candles, &str, &str, &str, UltOscParams) -> UltOscInput

// From candles with defaults (high/low/close, 7/14/28)
UltOscInput::with_default_candles(&Candles) -> UltOscInput
Parameters Structure
pub struct UltOscParams {
    pub timeperiod1: Option<usize>, // Default: 7
    pub timeperiod2: Option<usize>, // Default: 14
    pub timeperiod3: Option<usize>, // Default: 28
}
Output Structure
pub struct UltOscOutput {
    pub values: Vec<f64>, // ULTOSC values (range 0..=100)
}
Validation, Warmup & NaNs
  • timeperiod1/2/3 > 0 and must not exceed input length; otherwise UltOscError::InvalidPeriods.
  • Finds the first index where both row i-1 and i are finite across H/L/C; earlier indices are ignored.
  • Warmup ends at first_valid + max(timeperiod1, timeperiod2, timeperiod3) - 1; prior outputs are NaN.
  • If not enough valid data after warmup start, returns UltOscError::NotEnoughValidData { needed, valid }.
  • If no consecutive finite rows exist, returns UltOscError::AllValuesNaN.
  • Streaming: update() returns None until warmup completes or when any input is NaN.
Error Handling
use vectorta::indicators::ultosc::{ultosc, UltOscError};

match ultosc(&input) {
    Ok(output) => process(output.values),
    Err(UltOscError::EmptyData) =>
        eprintln!("Empty data provided"),
    Err(UltOscError::InvalidPeriods { p1, p2, p3, data_len }) =>
        eprintln!("Invalid periods: p1={}, p2={}, p3={}, len={}", p1, p2, p3, data_len),
    Err(UltOscError::NotEnoughValidData { needed, valid }) =>
        eprintln!("Need {} valid points after start, got {}", needed, valid),
    Err(UltOscError::AllValuesNaN) =>
        eprintln!("All values are NaN (or preceding row is NaN)"),
    Err(e) => eprintln!("ULTOSC error: {}", e),
}

Python Bindings

Basic Usage

Calculate ULTOSC with NumPy arrays (defaults 7/14/28):

import numpy as np
from vectorta import ultosc

high = np.array([...], dtype=float)
low = np.array([...], dtype=float)
close = np.array([...], dtype=float)

# Defaults 7/14/28
values = ultosc(high, low, close)

# Custom params and kernel selection
values = ultosc(high, low, close, timeperiod1=7, timeperiod2=14, timeperiod3=28, kernel="auto")

print(values)  # NumPy array in range [0, 100]
Streaming Real-time Updates

O(1) updates with UltOscStream:

from vectorta import UltOscStream

stream = UltOscStream(timeperiod1=7, timeperiod2=14, timeperiod3=28)

for h, l, c in hlc_feed:
    v = stream.update(h, l, c)
    if v is not None:
        print("ULTOSC:", v)
Batch Parameter Optimization

Sweep three parameter ranges:

import numpy as np
from vectorta import ultosc_batch

high = np.array([...], dtype=float)
low = np.array([...], dtype=float)
close = np.array([...], dtype=float)

res = ultosc_batch(
    high,
    low,
    close,
    timeperiod1_range=(5, 9, 2),
    timeperiod2_range=(12, 16, 2),
    timeperiod3_range=(26, 30, 2),
    kernel="auto"
)

print(res['values'].shape)  # (rows, len)
print(res['timeperiod1'])
print(res['timeperiod2'])
print(res['timeperiod3'])
CUDA Acceleration

CUDA support for Ultimate Oscillator is coming soon. The API will follow existing CUDA-enabled patterns.

# Coming soon: CUDA-accelerated Ultimate Oscillator operations
# Similar shape and semantics to other CUDA batch APIs in this library.

JavaScript/WASM Bindings

Basic Usage

Calculate ULTOSC in JavaScript/TypeScript:

import { ultosc_js } from 'vectorta-wasm';

const high = new Float64Array([/* ... */]);
const low = new Float64Array([/* ... */]);
const close = new Float64Array([/* ... */]);

// timeperiod1, timeperiod2, timeperiod3
const values = ultosc_js(high, low, close, 7, 14, 28);
console.log('ULTOSC values:', values);
Memory-Efficient Operations

Use zero-copy operations for large datasets:

import { ultosc_alloc, ultosc_free, ultosc_into, memory } from 'vectorta-wasm';

const len = close.length;
const hPtr = ultosc_alloc(len);
const lPtr = ultosc_alloc(len);
const cPtr = ultosc_alloc(len);
const oPtr = ultosc_alloc(len);

// Copy H/L/C into WASM memory
new Float64Array(memory.buffer, hPtr, len).set(high);
new Float64Array(memory.buffer, lPtr, len).set(low);
new Float64Array(memory.buffer, cPtr, len).set(close);

// Compute directly into pre-allocated output
ultosc_into(hPtr, lPtr, cPtr, oPtr, len, 7, 14, 28);
const out = new Float64Array(memory.buffer, oPtr, len).slice();

// Free allocations
ultosc_free(hPtr, len); ultosc_free(lPtr, len);
ultosc_free(cPtr, len); ultosc_free(oPtr, len);
console.log('ULTOSC:', out);
Batch Processing

Test multiple parameter combinations:

import { ultosc_batch } from 'vectorta-wasm';

const cfg = {
  timeperiod1_range: [5, 9, 2],
  timeperiod2_range: [12, 16, 2],
  timeperiod3_range: [26, 30, 2],
};

const res = ultosc_batch(high, low, close, cfg);
// res: { values: Float64Array, combos: Array<{timeperiod1?: number, timeperiod2?: number, timeperiod3?: number}>, rows: number, cols: number }

// Extract one row by params
const target = { timeperiod1: 7, timeperiod2: 14, timeperiod3: 28 };
const idx = res.combos.findIndex(c => c.timeperiod1 === target.timeperiod1 && c.timeperiod2 === target.timeperiod2 && c.timeperiod3 === target.timeperiod3);
if (idx >= 0) {
  const start = idx * res.cols;
  const row = res.values.slice(start, start + res.cols);
  console.log('Selected ULTOSC row:', row);
}

Performance Analysis

Comparison:
View:

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

Loading chart...

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

Related Indicators