TRIX (Triple Exponential Average Oscillator)

Parameters: period = 18

Overview

TRIX filters market noise through triple exponential smoothing applied to logarithmic price data, then measures the rate of change of the resulting curve. The indicator begins by taking the natural logarithm of prices to normalize percentage moves across different price levels. Three successive exponential moving averages smooth this log series, with each layer using the same period parameter. Finally, TRIX calculates the one-period percentage change of the triple-smoothed output and scales it by 10000 for readability. This aggressive smoothing removes virtually all short term noise while preserving longer term momentum trends. The oscillator crosses its zero centerline as momentum shifts from positive to negative or vice versa, providing clean trend change signals. Additionally, traders watch for divergences between TRIX and price, as these often precede reversals. The default 18-period setting suits intermediate term trend analysis, though shorter periods increase sensitivity to momentum shifts at the cost of more false signals.

Default: period=18; a single period is used for all smoothing stages.

Implementation Examples

Get started with TRIX in just a few lines:

use vectorta::indicators::trix::{trix, TrixInput, TrixParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// From a price slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let params = TrixParams { period: Some(18) }; // Default period is 18
let input = TrixInput::from_slice(&prices, params);
let result = trix(&input)?;

// From Candles with defaults (source="close", period=18)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = TrixInput::with_default_candles(&candles);
let result = trix(&input)?;

// Access the TRIX values (scaled ×10000)
for value in result.values {
    println!("TRIX: {}", value);
}

API Reference

Input Methods
// From price slice
TrixInput::from_slice(&[f64], TrixParams) -> TrixInput

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

// From candles with defaults (source="close", period=18)
TrixInput::with_default_candles(&Candles) -> TrixInput
Parameters Structure
pub struct TrixParams {
    pub period: Option<usize>, // Default: 18
}
Output Structure
pub struct TrixOutput {
    pub values: Vec<f64>, // TRIX values: (EMA3_t - EMA3_{t-1}) * 10000
}
Validation, Warmup & NaNs
  • period > 0 and period ≤ data.len(); otherwise TrixError::InvalidPeriod.
  • First non-NaN index is detected; if none: TrixError::AllValuesNaN.
  • Requires at least needed = 3*(period−1)+2 valid points after the first finite value; else TrixError::NotEnoughValidData.
  • Warmup prefix length warmup_end = first + 3*(period−1) + 1; indices before this are NaN. First finite output at warmup_end.
  • Computation uses ln(price) internally and a single α = 2/(n+1) for all three EMA stages.
Error Handling
use vectorta::indicators::trix::{trix, TrixError};

match trix(&input) {
    Ok(output) => process_results(output.values),
    Err(TrixError::EmptyData) => eprintln!("Empty data provided"),
    Err(TrixError::InvalidPeriod { period, data_len }) => {
        eprintln!("Invalid period: period={}, len={}", period, data_len)
    }
    Err(TrixError::NotEnoughValidData { needed, valid }) => {
        eprintln!("Need {} valid points, only {}", needed, valid)
    }
    Err(TrixError::AllValuesNaN) => eprintln!("All values are NaN"),
}

Python Bindings

Basic Usage

Calculate TRIX using NumPy arrays (period is required; kernel optional):

import numpy as np
from vectorta import trix

# Prepare price data as NumPy array
prices = np.array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5])

# Calculate TRIX
values = trix(prices, period=18)            # kernel defaults to 'auto'
values = trix(prices, period=18, kernel="avx2")

print(f"TRIX values: {values}")
Streaming Real-time Updates

Process real-time price updates efficiently:

from vectorta import TrixStream

# Initialize streaming TRIX
stream = TrixStream(period=18)

for price in price_feed:
    trix_value = stream.update(price)
    if trix_value is not None:
        print(f"Current TRIX: {trix_value}")
Batch Parameter Optimization

Test multiple period values for optimization:

import numpy as np
from vectorta import trix_batch

prices = np.array([...])

# (start, end, step)
results = trix_batch(prices, period_range=(10, 30, 5), kernel="auto")

print(results.keys())            # 'values', 'periods'
print(results['values'].shape)   # (num_periods, len(prices))
print(results['periods'])        # [10, 15, 20, 25, 30]
CUDA Acceleration

CUDA support for TRIX is currently under development. The API will follow the same pattern as other CUDA-enabled indicators.

# Coming soon: CUDA-accelerated TRIX calculations
# Example will mirror other CUDA-enabled indicators in this project.

JavaScript/WASM Bindings

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

// Float64Array input; period is a single value
const prices = new Float64Array([100, 102, 101.5, 103, 105, 104.5]);
const values = trix_js(prices, 18);
console.log('TRIX values:', values);
Memory-Efficient Operations

Use zero-copy operations for better performance with large datasets:

import { trix_alloc, trix_free, trix_into, memory } from 'vectorta-wasm';

const prices = new Float64Array([/* your data */]);
const length = prices.length;

// Allocate WASM memory for input and output
const inPtr = trix_alloc(length);
const outPtr = trix_alloc(length);

// Copy input data into WASM memory
new Float64Array(memory.buffer, inPtr, length).set(prices);

// Compute TRIX directly into allocated memory
// Args: in_ptr, out_ptr, len, period
trix_into(inPtr, outPtr, length, 18);

// Read results from WASM memory (slice() to copy out)
const trixValues = new Float64Array(memory.buffer, outPtr, length).slice();

// Free allocated memory when done
trix_free(inPtr, length);
trix_free(outPtr, length);

console.log('TRIX values:', trixValues);
Batch Processing

Test multiple periods efficiently:

import { trix_batch, trix_batch_into, trix_alloc, trix_free, memory } from 'vectorta-wasm';

// Your price data
const prices = new Float64Array([/* historical prices */]);

// Calculate all combinations via high-level API
const { values, periods, rows, cols } = trix_batch(prices, { period_range: [10, 30, 5] });

// Reshape values (flat) into matrix rows × cols
const matrix = [] as Float64Array[];
for (let r = 0; r < rows; r++) {
  const start = r * cols;
  matrix.push(values.slice(start, start + cols));
}

// Or write directly into a preallocated buffer
const outPtr = trix_alloc(periods.length * prices.length);
const numCombos = trix_batch_into(
  prices, outPtr, prices.length,
  10, 30, 5
);
const flat = new Float64Array(memory.buffer, outPtr, numCombos * prices.length);
trix_free(outPtr, numCombos * prices.length);

Performance Analysis

Comparison:
View:

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

Loading chart...

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

CUDA note

In our benchmark workload, the Rust CPU implementation is faster than CUDA for this indicator. Prefer the Rust/CPU path unless your workload differs.

Related Indicators