Triangular Moving Average (TRIMA)
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. OtherwiseTrimaError::InvalidPeriod/PeriodTooSmall.- At least
periodvalid points after the first finite value; elseTrimaError::NotEnoughValidData. - All-NaN input →
TrimaError::AllValuesNaN; empty input →TrimaError::NoData. - Warmup: indices before
first + period − 1areNaN. Streaming returnsNoneuntil 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
Across sizes, Rust CPU runs about 2.02× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05
Related Indicators
Arnaud Legoux Moving Average
Moving average indicator
Compound Ratio Moving Average (CoRa Wave)
Moving average indicator
Centered Weighted Moving Average
Moving average indicator
Double Exponential Moving Average
Moving average indicator
Ehlers Distance Coefficient Filter
Moving average indicator
Ehlers Error-Correcting EMA (ECEMA)
Moving average indicator