Triangular Moving Average (TRIMA)

Parameters: period = 30

Overview

The Triangular Moving Average applies double smoothing by calculating a simple moving average and then taking another simple moving average of that result, creating a bell shaped weighting distribution that emphasizes central values. TRIMA effectively computes the average of averages, which mathematically produces weights that increase linearly toward the middle of the period and decrease linearly toward the edges, forming a triangular pattern. This double smoothing process generates significantly smoother curves than single pass moving averages, filtering out short term noise while introducing more lag in response to genuine trend changes. The triangular weight distribution makes TRIMA particularly resistant to sudden price spikes or drops, as extreme values receive minimal weight unless they persist across multiple bars. Traders use TRIMA when they prioritize smooth trend identification over quick responsiveness, finding it especially useful for longer term analysis where filtering noise matters more than catching every minor price swing.

Default parameter: period = 30.

Implementation Examples

Compute TRIMA from prices or candles:

use vectorta::indicators::moving_averages::trima::{trima, TrimaInput, TrimaParams};
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 = TrimaParams { period: Some(30) };
let input = TrimaInput::from_slice(&prices, params);
let output = trima(&input)?;

// From candles with defaults (period=30, source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = TrimaInput::with_default_candles(&candles);
let output = trima(&input)?;

for v in output.values { println!("TRIMA: {}", v); }

API Reference

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

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

// From candles with defaults (period=30, source="close")
TrimaInput::with_default_candles(&Candles) -> TrimaInput
Parameters Structure
pub struct TrimaParams {
    pub period: Option<usize>, // Default: 30
}
Output Structure
pub struct TrimaOutput {
    pub values: Vec<f64>, // TRIMA values
}
Validation, Warmup & NaNs
  • period > 3; period ≤ data_len. Otherwise TrimaError::InvalidPeriod/PeriodTooSmall.
  • At least period valid points after the first finite value; else TrimaError::NotEnoughValidData.
  • All-NaN input → TrimaError::AllValuesNaN; empty input → TrimaError::NoData.
  • Warmup: indices before first + period − 1 are NaN. Streaming returns None until warm.
Error Handling
use vectorta::indicators::moving_averages::trima::{trima, TrimaError};

match trima(&input) {
    Ok(out) => process(out.values),
    Err(TrimaError::NoData) => eprintln!("No data provided"),
    Err(TrimaError::AllValuesNaN) => eprintln!("All values are NaN"),
    Err(TrimaError::PeriodTooSmall { period }) =>
        eprintln!("Period too small: {}", period),
    Err(TrimaError::InvalidPeriod { period, data_len }) =>
        eprintln!("Invalid period {} for length {}", period, data_len),
    Err(TrimaError::NotEnoughValidData { needed, valid }) =>
        eprintln!("Need {} points after first finite, got {}", needed, valid),
}

Python Bindings

Basic Usage

Calculate TRIMA using NumPy arrays (default period is 30):

import numpy as np
from vectorta import trima

prices = np.array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5], dtype=np.float64)

# Default-like usage
result = trima(prices, period=30)

# Specify kernel explicitly
result = trima(prices, period=30, kernel="avx2")

print(result)  # NumPy array
Streaming Real-time Updates

Efficient streaming via TrimaStream:

from vectorta import TrimaStream

stream = TrimaStream(period=30)
for price in price_feed:
    value = stream.update(price)
    if value is not None:
        print("TRIMA:", value)
Batch Parameter Optimization

Run many periods at once:

import numpy as np
from vectorta import trima_batch

prices = np.array([...], dtype=np.float64)
out = trima_batch(prices, period_range=(10, 50, 5), kernel="auto")

values_2d = out["values"]   # shape: (num_periods, len(prices))
periods   = out["periods"]  # e.g., [10, 15, 20, ...]

print(values_2d.shape, periods)
CUDA Acceleration

CUDA helpers are available when the Python package is built with CUDA support:

# Requires vectorta built with CUDA
from vectorta import trima_cuda_batch_dev, trima_cuda_many_series_one_param_dev
import numpy as np

# 1) One series, many periods (device result)
prices = np.array([...], dtype=np.float64)
dev_arr = trima_cuda_batch_dev(prices, period_range=(10, 50, 5), device_id=0)

# 2) Many series (time-major), one period
data_tm_f32 = np.array([...], dtype=np.float32)  # shape [T, N]
dev_arr2 = trima_cuda_many_series_one_param_dev(data_tm_f32, period=30, device_id=0)

JavaScript/WASM Bindings

Basic Usage

Compute TRIMA in JS/TS:

import { trima_js } from 'vectorta-wasm';

const prices = new Float64Array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5]);
const trimaValues = await trima_js(prices, 30);
console.log('TRIMA:', trimaValues);
Memory-Efficient Operations

Zero-copy style with pre-allocated WASM buffers:

import { trima_alloc, trima_free, trima_into, memory } from 'vectorta-wasm';

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

const inPtr = trima_alloc(n);
const outPtr = trima_alloc(n);
new Float64Array(memory.buffer, inPtr, n).set(prices);

// Compute into the pre-allocated output buffer
await trima_into(inPtr, outPtr, n, 30);

const out = new Float64Array(memory.buffer, outPtr, n).slice();
trima_free(inPtr, n);
trima_free(outPtr, n);
Batch Processing

Sweep period ranges in a single call:

import { trima_batch_js, trima_batch_metadata_js } from 'vectorta-wasm';

const prices = new Float64Array([/* historical prices */]);
const start = 10, end = 50, step = 5;

const metadata = await trima_batch_metadata_js(start, end, step); // [period1, period2, ...]
const results = await trima_batch_js(prices, start, end, step);   // flat row-major array

const rows = metadata.length;
const cols = prices.length;
const matrix = [];
for (let i = 0; i < rows; i++) {
  const s = i * cols;
  matrix.push(results.slice(s, s + cols));
}

Performance Analysis

Comparison:
View:

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

Loading chart...

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

Related Indicators