Optimized Trend Tracker Oscillator (OTTO)
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; errorInvalidPeriodotherwise or if it exceeds data length.fast_vidya_length > 0,slow_vidya_length > 0; composite VIDYA periodsslow/2,slow,slow×fastmust be non-zero.- Data requirement:
needed = max(slow×fast, 10); errorNotEnoughValidData { needed, valid }if insufficient after first finite input. ma_typemust be one ofVAR, SMA, EMA, WMA, WWMA, DEMA, TMA, ZLEMA, TSF, HULL; elseInvalidMaType.- Integrated path (
ma_type = "VAR") yields from index 0 (VIDYA seeded per implementation). Other MAs produceNaNuntil their own warmup completes. - Streaming warmup: emits
Noneuntil 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
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05