Optimized Trend Tracker Oscillator (OTTO)

Parameters: ott_period = 2 | ott_percent = 0.6 | fast_vidya_length = 10 | slow_vidya_length = 25 | correcting_constant = 100000 | ma_type = VAR

Overview

The Optimized Trend Tracker Oscillator (OTTO) combines Variable Index Dynamic Average (VIDYA) smoothing with OTT band logic to create a momentum-adaptive trend following system. The indicator first constructs LOTT, a base ratio derived from three VIDYA calculations with different period lengths that respond to market velocity through a CMO(9) modulated alpha. This adaptive base then feeds into an OTT algorithm that applies percentage offsets and moving average smoothing to generate HOTT, the final optimized tracking band. The dual output provides both the raw momentum-adjusted signal (LOTT) and the trend-filtered result (HOTT), allowing traders to assess both underlying adaptive behavior and smoothed directional bias. When HOTT rises while maintaining separation from price, it confirms strong trending conditions with momentum support. The default configuration uses a correcting constant to stabilize the VIDYA ratio during low volatility periods, ensuring reliable signals across varying market states.

SIMD: disabled for OTTO; kernel selection is accepted but computed via scalar path.

Implementation Examples

Compute HOTT and LOTT with defaults or custom parameters:

use vectorta::indicators::otto::{otto, OttoInput, OttoParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// From price slice
let prices = vec![100.0, 101.2, 99.8, 102.5, 101.7];
let params = OttoParams { ott_period: Some(2), ott_percent: Some(0.6), fast_vidya_length: Some(10), slow_vidya_length: Some(25), correcting_constant: Some(100_000.0), ma_type: Some("VAR".to_string()) };
let input = OttoInput::from_slice(&prices, params);
let out = otto(&input)?; // out.hott, out.lott

// From candles with defaults (source = "close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = OttoInput::with_default_candles(&candles);
let out = otto(&input)?;

// Access
println!("HOTT last: {}", out.hott.last().unwrap());
println!("LOTT last: {}", out.lott.last().unwrap());

API Reference

Input Methods
// From slice
OttoInput::from_slice(&[f64], OttoParams) -> OttoInput

// From candles with custom source
OttoInput::from_candles(&Candles, &str, OttoParams) -> OttoInput

// With defaults (source = "close")
OttoInput::with_default_candles(&Candles) -> OttoInput
Parameters Structure
#[derive(Debug, Clone, PartialEq)]
pub struct OttoParams {
    pub ott_period: Option<usize>,        // default: 2 (min 1)
    pub ott_percent: Option<f64>,         // default: 0.6 (%)
    pub fast_vidya_length: Option<usize>, // default: 10 (min 1)
    pub slow_vidya_length: Option<usize>, // default: 25 (min 1)
    pub correcting_constant: Option<f64>, // default: 100000.0
    pub ma_type: Option<String>,          // default: "VAR"
}

impl Default for OttoParams { /* defaults as above */ }
Output Structure
pub struct OttoOutput {
    pub hott: Vec<f64>, // OTT-tracked band
    pub lott: Vec<f64>, // Adaptive base ratio
}
Validation, Warmup & NaNs
  • ott_period > 0; error InvalidPeriod otherwise or if it exceeds data length.
  • fast_vidya_length > 0, slow_vidya_length > 0; composite VIDYA periods slow/2, slow, slow×fast must be non-zero.
  • Data requirement: needed = max(slow×fast, 10); error NotEnoughValidData { needed, valid } if insufficient after first finite input.
  • ma_type must be one of VAR, SMA, EMA, WMA, WWMA, DEMA, TMA, ZLEMA, TSF, HULL; else InvalidMaType.
  • Integrated path (ma_type = "VAR") yields from index 0 (VIDYA seeded per implementation). Other MAs produce NaN until their own warmup completes.
  • Streaming warmup: emits None until buffer length ≥ slow×fast + 10.
Error Handling
use vectorta::indicators::otto::{otto, OttoError};

match otto(&input) {
    Ok(out) => use_results(out.hott, out.lott),
    Err(OttoError::EmptyInputData) => eprintln!("input is empty"),
    Err(OttoError::AllValuesNaN) => eprintln!("all values are NaN"),
    Err(OttoError::InvalidPeriod { period, data_len }) => eprintln!("invalid period {} for len {}", period, data_len),
    Err(OttoError::NotEnoughValidData { needed, valid }) => eprintln!("need {}, have {} valid", needed, valid),
    Err(OttoError::InvalidMaType { ma_type }) => eprintln!("bad MA: {}", ma_type),
    Err(OttoError::CmoError(e)) | Err(OttoError::MaError(e)) => eprintln!("inner error: {}", e),
} 

Python Bindings

Basic Usage
import numpy as np
from vectorta import otto

prices = np.array([100.0, 101.2, 99.8, 102.5, 101.7])

# Returns two arrays: HOTT, LOTT
hott, lott = otto(
    prices,
    ott_period=2,
    ott_percent=0.6,
    fast_vidya_length=10,
    slow_vidya_length=25,
    correcting_constant=100000.0,
    ma_type="VAR",
    kernel="auto"  # optional
)
print(hott[-1], lott[-1])
Streaming Real-time Updates
from vectorta import OttoStreamPy

stream = OttoStreamPy(ott_period=2, ott_percent=0.6, fast_vidya_length=10, slow_vidya_length=25, correcting_constant=100000.0, ma_type="VAR")
for price in feed:
    hott, lott = stream.update(price)
    if hott is not None:
        trade(hott, lott)
Batch Parameter Optimization
import numpy as np
from vectorta import otto_batch

prices = np.array([...], dtype=float)
res = otto_batch(
    prices,
    ott_period_range=(2, 4, 1),
    ott_percent_range=(0.5, 0.7, 0.1),
    fast_vidya_range=(10, 12, 1),
    slow_vidya_range=(20, 22, 1),
    correcting_constant_range=(100000.0, 100000.0, 0.0),
    ma_types=["VAR", "EMA"],
    kernel="auto"
)

# res is a dict with 'hott', 'lott' shaped [rows, len], and parameter arrays
print(res['hott'].shape, res['lott'].shape)
CUDA Acceleration

CUDA support for OTTO is coming soon.

JavaScript/WASM Bindings

Basic Usage
import { otto_js } from 'vectorta-wasm';

const prices = new Float64Array([100, 101.2, 99.8, 102.5, 101.7]);
const js = await otto_js(prices, 2, 0.6, 10, 25, 100000.0, 'VAR');
// js = { values: Float64Array([...]), rows: 2, cols: prices.length }

const values = js.values as Float64Array;
const hott = values.slice(0, js.cols);
const lott = values.slice(js.cols, 2*js.cols);
Memory-Efficient Operations
import { otto_alloc, otto_free, otto_into, memory } from 'vectorta-wasm';

const prices = new Float64Array([/* ... */]);
const n = prices.length;
const inPtr = otto_alloc(n);
const hottPtr = otto_alloc(n);
const lottPtr = otto_alloc(n);
new Float64Array(memory.buffer, inPtr, n).set(prices);

// in_ptr, hott_ptr, lott_ptr, len, ott_period, ott_percent, fast, slow, constant, ma_type
otto_into(inPtr, hottPtr, lottPtr, n, 2, 0.6, 10, 25, 100000.0, 'VAR');

const hott = new Float64Array(memory.buffer, hottPtr, n).slice();
const lott = new Float64Array(memory.buffer, lottPtr, n).slice();
otto_free(inPtr, n); otto_free(hottPtr, n); otto_free(lottPtr, n);
Batch Processing
import { otto_batch } from 'vectorta-wasm'; // exported as otto_batch_unified_js

const cfg = {
  ott_period: [2, 4, 1],
  ott_percent: [0.5, 0.7, 0.1],
  fast_vidya: [10, 12, 1],
  slow_vidya: [20, 22, 1],
  correcting_constant: [100000.0, 100000.0, 0.0],
  ma_types: ['VAR', 'EMA']
};
const res = await otto_batch(prices, cfg);
// res = { values: Float64Array, combos: OttoParams[], rows: combos*2, cols, rows_per_combo: 2 }

// For combo i: rows i*2 (HOTT), i*2+1 (LOTT)
const rowsPerCombo = res.rows_per_combo;
const N = res.cols;
const values = res.values as Float64Array;
function rowSlice(row) { return values.slice(row*N, (row+1)*N); }
const hott0 = rowSlice(0); // first combo HOTT
const lott0 = rowSlice(1); // first combo LOTT

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators