TRIX (Triple Exponential Average Oscillator)
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 > 0andperiod ≤ data.len(); otherwiseTrixError::InvalidPeriod.- First non-NaN index is detected; if none:
TrixError::AllValuesNaN. - Requires at least
needed = 3*(period−1)+2valid points after the first finite value; elseTrixError::NotEnoughValidData. - Warmup prefix length
warmup_end = first + 3*(period−1) + 1; indices before this areNaN. First finite output atwarmup_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
Across sizes, Rust CPU runs about 1.95× slower than Tulip C in this benchmark.
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.