TRIMA#

Description#

TRIMA (Triangular Moving Average) is a double-smoothed simple mean: an SMA of an SMA. The effective per-sample weights form a symmetric triangle (rising then falling), giving more weight to the centre of the window than the ends.

\[ \text{TRIMA}(x, n)[t] = \text{SMA}(\text{SMA}(x, n_\text{inner}), n_\text{outer})[t] \]

with TA-Lib's window split:

Total window n

n_inner

n_outer

odd

(n + 1) / 2

(n + 1) / 2

even

n/2 + 1

n/2

In both cases n_inner + n_outer - 1 == n, so the effective triangular weighting spans n samples.

Parameters#

  • window_size (int, positive). Total triangle width.

NaN handling: NaN values should be preprocessed.

Implementation Details#

Algorithm#

Pure composition of two chained detail::RollingMean instances. Both run with start_policy="expanding" so that the inner doesn't emit NaN (which would poison the outer's running sum permanently). TRIMA itself enforces strict warmup by counting samples and emitting NaN until n samples have been processed.

Complexity#

  • Time complexity: O(1) per step (two RollingMean updates).

  • Space complexity: O(window_size).

NaN handling#

Policy: ignore. A NaN in any input at index t causes the function to skip that step: output at t is NaN and internal state is unchanged. Subsequent finite samples are processed as if step t had not occurred.

Examples#

Usage example#

import numpy as np
from screamer import TRIMA, RollingMean

x = np.cumsum(np.random.randn(100))
n = 10

# Direct
ours = TRIMA(n)(x)

# Algorithmically equivalent composition (post-warmup, the test suite
# verifies bit-equality)
n_inner, n_outer = (n // 2 + 1, n // 2) if n % 2 == 0 else ((n + 1) // 2, (n + 1) // 2)
inner = RollingMean(n_inner, "expanding")(x)
outer = RollingMean(n_outer, "expanding")(inner)
np.testing.assert_allclose(ours[n - 1:], outer[n - 1:], atol=1e-12)

Reference#

Equivalent to TA-Lib's TRIMA. Validated in tests/test_moving_averages.py against the explicit two-RollingMean composition for several window sizes (both even and odd).