F1: wire SMMA and TRIMA through every binding and the wiki
Completes the F1 family (Simple & Weighted MAs). The Rust core for both SMMA (Wilder's RMA) and TRIMA (triangular MA) already landed; this adds the remaining Definition-of-Done steps: - Python: PySmma / PyTrima PyO3 classes + module registration + .pyi stubs. - Node: SmmaNode / TrimaNode via the scalar-indicator macro; index.d.ts and index.js updated for the two new classes. - WASM: WasmSmma / WasmTrima via the scalar-indicator macro. - Wiki: Indicator-Smma.md and Indicator-Trima.md (full pages) plus rows in Indicators-Overview.md and entries in Home.md. cargo fmt + clippy (core/wickra/data/wasm/node) clean; 208 core tests, 25 data tests and 31 doctests green.
This commit is contained in:
@@ -310,7 +310,7 @@ if (!nativeBinding) {
|
||||
throw new Error(`Failed to load native binding`)
|
||||
}
|
||||
|
||||
const { version, SMA, EMA, WMA, RSI, DEMA, TEMA, HMA, ROC, TRIX, MACD, BollingerBands, ATR, Stochastic, OBV, ADX, CCI, WilliamsR, MFI, PSAR, Keltner, Donchian, VWAP, AwesomeOscillator, Aroon, KAMA } = nativeBinding
|
||||
const { version, SMA, EMA, WMA, RSI, DEMA, TEMA, HMA, ROC, TRIX, SMMA, TRIMA, MACD, BollingerBands, ATR, Stochastic, OBV, ADX, CCI, WilliamsR, MFI, PSAR, Keltner, Donchian, VWAP, AwesomeOscillator, Aroon, KAMA } = nativeBinding
|
||||
|
||||
module.exports.version = version
|
||||
module.exports.SMA = SMA
|
||||
@@ -322,6 +322,8 @@ module.exports.TEMA = TEMA
|
||||
module.exports.HMA = HMA
|
||||
module.exports.ROC = ROC
|
||||
module.exports.TRIX = TRIX
|
||||
module.exports.SMMA = SMMA
|
||||
module.exports.TRIMA = TRIMA
|
||||
module.exports.MACD = MACD
|
||||
module.exports.BollingerBands = BollingerBands
|
||||
module.exports.ATR = ATR
|
||||
|
||||
@@ -102,6 +102,8 @@ node_scalar_indicator!(TemaNode, "TEMA", wc::Tema);
|
||||
node_scalar_indicator!(HmaNode, "HMA", wc::Hma);
|
||||
node_scalar_indicator!(RocNode, "ROC", wc::Roc);
|
||||
node_scalar_indicator!(TrixNode, "TRIX", wc::Trix);
|
||||
node_scalar_indicator!(SmmaNode, "SMMA", wc::Smma);
|
||||
node_scalar_indicator!(TrimaNode, "TRIMA", wc::Trima);
|
||||
|
||||
// ============================== MACD ==============================
|
||||
|
||||
|
||||
@@ -52,6 +52,30 @@ class WMA:
|
||||
@property
|
||||
def value(self) -> Optional[float]: ...
|
||||
|
||||
class SMMA:
|
||||
def __init__(self, period: int) -> None: ...
|
||||
def update(self, value: float) -> Optional[float]: ...
|
||||
def batch(self, prices: NDArray[np.float64]) -> NDArray[np.float64]: ...
|
||||
def reset(self) -> None: ...
|
||||
def is_ready(self) -> bool: ...
|
||||
def warmup_period(self) -> int: ...
|
||||
@property
|
||||
def period(self) -> int: ...
|
||||
@property
|
||||
def value(self) -> Optional[float]: ...
|
||||
|
||||
class TRIMA:
|
||||
def __init__(self, period: int) -> None: ...
|
||||
def update(self, value: float) -> Optional[float]: ...
|
||||
def batch(self, prices: NDArray[np.float64]) -> NDArray[np.float64]: ...
|
||||
def reset(self) -> None: ...
|
||||
def is_ready(self) -> bool: ...
|
||||
def warmup_period(self) -> int: ...
|
||||
@property
|
||||
def period(self) -> int: ...
|
||||
@property
|
||||
def value(self) -> Optional[float]: ...
|
||||
|
||||
class RSI:
|
||||
def __init__(self, period: int = 14) -> None: ...
|
||||
def update(self, value: float) -> Optional[float]: ...
|
||||
|
||||
@@ -1519,6 +1519,108 @@ impl PyAroon {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== SMMA ==============================
|
||||
|
||||
#[pyclass(name = "SMMA", module = "wickra._wickra")]
|
||||
#[derive(Clone)]
|
||||
struct PySmma {
|
||||
inner: wc::Smma,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PySmma {
|
||||
#[new]
|
||||
fn new(period: usize) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::Smma::new(period).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
fn update(&mut self, value: f64) -> Option<f64> {
|
||||
self.inner.update(value)
|
||||
}
|
||||
fn batch<'py>(
|
||||
&mut self,
|
||||
py: Python<'py>,
|
||||
prices: PyReadonlyArray1<'py, f64>,
|
||||
) -> PyResult<Bound<'py, PyArray1<f64>>> {
|
||||
let slice = prices
|
||||
.as_slice()
|
||||
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
|
||||
Ok(flatten(self.inner.batch(slice)).into_pyarray_bound(py))
|
||||
}
|
||||
#[getter]
|
||||
fn period(&self) -> usize {
|
||||
self.inner.period()
|
||||
}
|
||||
#[getter]
|
||||
fn value(&self) -> Option<f64> {
|
||||
self.inner.value()
|
||||
}
|
||||
fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
fn is_ready(&self) -> bool {
|
||||
self.inner.is_ready()
|
||||
}
|
||||
fn warmup_period(&self) -> usize {
|
||||
self.inner.warmup_period()
|
||||
}
|
||||
fn __repr__(&self) -> String {
|
||||
format!("SMMA(period={})", self.inner.period())
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== TRIMA ==============================
|
||||
|
||||
#[pyclass(name = "TRIMA", module = "wickra._wickra")]
|
||||
#[derive(Clone)]
|
||||
struct PyTrima {
|
||||
inner: wc::Trima,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyTrima {
|
||||
#[new]
|
||||
fn new(period: usize) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::Trima::new(period).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
fn update(&mut self, value: f64) -> Option<f64> {
|
||||
self.inner.update(value)
|
||||
}
|
||||
fn batch<'py>(
|
||||
&mut self,
|
||||
py: Python<'py>,
|
||||
prices: PyReadonlyArray1<'py, f64>,
|
||||
) -> PyResult<Bound<'py, PyArray1<f64>>> {
|
||||
let slice = prices
|
||||
.as_slice()
|
||||
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
|
||||
Ok(flatten(self.inner.batch(slice)).into_pyarray_bound(py))
|
||||
}
|
||||
#[getter]
|
||||
fn period(&self) -> usize {
|
||||
self.inner.period()
|
||||
}
|
||||
#[getter]
|
||||
fn value(&self) -> Option<f64> {
|
||||
self.inner.value()
|
||||
}
|
||||
fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
fn is_ready(&self) -> bool {
|
||||
self.inner.is_ready()
|
||||
}
|
||||
fn warmup_period(&self) -> usize {
|
||||
self.inner.warmup_period()
|
||||
}
|
||||
fn __repr__(&self) -> String {
|
||||
format!("TRIMA(period={})", self.inner.period())
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Module ==============================
|
||||
|
||||
#[pymodule]
|
||||
@@ -1549,5 +1651,7 @@ fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<PyVwap>()?;
|
||||
m.add_class::<PyAo>()?;
|
||||
m.add_class::<PyAroon>()?;
|
||||
m.add_class::<PySmma>()?;
|
||||
m.add_class::<PyTrima>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -75,6 +75,8 @@ wasm_scalar_indicator!(WasmTema, "TEMA", wc::Tema, period: usize);
|
||||
wasm_scalar_indicator!(WasmHma, "HMA", wc::Hma, period: usize);
|
||||
wasm_scalar_indicator!(WasmRoc, "ROC", wc::Roc, period: usize);
|
||||
wasm_scalar_indicator!(WasmTrix, "TRIX", wc::Trix, period: usize);
|
||||
wasm_scalar_indicator!(WasmSmma, "SMMA", wc::Smma, period: usize);
|
||||
wasm_scalar_indicator!(WasmTrima, "TRIMA", wc::Trima, period: usize);
|
||||
|
||||
// ---------- KAMA (three params) ----------
|
||||
|
||||
|
||||
@@ -79,6 +79,8 @@ Rust / Python / Node examples. They are grouped by family, mirroring the
|
||||
- [Indicator-Tema.md](indicators/trend/Indicator-Tema.md)
|
||||
- [Indicator-Hma.md](indicators/trend/Indicator-Hma.md)
|
||||
- [Indicator-Kama.md](indicators/trend/Indicator-Kama.md)
|
||||
- [Indicator-Smma.md](indicators/trend/Indicator-Smma.md)
|
||||
- [Indicator-Trima.md](indicators/trend/Indicator-Trima.md)
|
||||
|
||||
**Momentum** — measure the rate of price change rather than the level.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Indicators Overview
|
||||
|
||||
Wickra ships 25 indicators, organised in source under the four classical
|
||||
Wickra ships 27 indicators, organised in source under the four classical
|
||||
families — trend, momentum, volatility, volume — that map directly to the
|
||||
directory structure of `crates/wickra-core/src/indicators/`. The same family
|
||||
labels are used here, plus a second-level grouping that reflects how the
|
||||
@@ -35,6 +35,7 @@ benchmarks against fancier averages.
|
||||
|-----------|-----------|-------|--------|-------|----------|--------|-----------|
|
||||
| `Sma` | Equal-weighted rolling mean over `period` closes. | `f64` | `f64` | unbounded (price scale) | `period` (no default in core; Python defaults vary by binding) | `period` | [Indicator-Sma.md](indicators/trend/Indicator-Sma.md) |
|
||||
| `Wma` | Linear weights `1, 2, …, period` so the newest bar matters most. | `f64` | `f64` | unbounded (price scale) | `period` | `period` | [Indicator-Wma.md](indicators/trend/Indicator-Wma.md) |
|
||||
| `Trima` | A `period`-window SMA applied twice; triangular weights centred on the middle bar. | `f64` | `f64` | unbounded (price scale) | `period` | `period` | [Indicator-Trima.md](indicators/trend/Indicator-Trima.md) |
|
||||
|
||||
### Exponential family
|
||||
|
||||
@@ -46,6 +47,7 @@ you stack more EMAs, but so does responsiveness to noise.
|
||||
| `Ema` | EMA with `α = 2 / (period + 1)`, seeded from the SMA of the first `period` inputs. | `f64` | `f64` | unbounded (price scale) | `period` | `period` | [Indicator-Ema.md](indicators/trend/Indicator-Ema.md) |
|
||||
| `Dema` | Mulloy's `2·EMA − EMA(EMA)`; removes first-order EMA lag. | `f64` | `f64` | unbounded (price scale) | `period` | `2·period − 1` | [Indicator-Dema.md](indicators/trend/Indicator-Dema.md) |
|
||||
| `Tema` | Mulloy's `3·EMA − 3·EMA(EMA) + EMA(EMA(EMA))`; removes more lag than DEMA. | `f64` | `f64` | unbounded (price scale) | `period` | `3·period − 2` | [Indicator-Tema.md](indicators/trend/Indicator-Tema.md) |
|
||||
| `Smma` | Wilder's RMA: an SMA-seeded exponential average with the slow `1/period` factor. | `f64` | `f64` | unbounded (price scale) | `period` | `period` | [Indicator-Smma.md](indicators/trend/Indicator-Smma.md) |
|
||||
|
||||
`Trix` is also built from a triple-smoothed EMA, but it is a *momentum
|
||||
oscillator* — it emits the rate of change of that EMA, not a price-scale
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
# SMMA
|
||||
|
||||
> Smoothed Moving Average — Wilder's running moving average (RMA): an
|
||||
> SMA-seeded exponential average with a slow `1 / period` smoothing factor.
|
||||
|
||||
## Quick reference
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Family | Trend |
|
||||
| Sub-category | Exponential family |
|
||||
| Input type | `f64` (single close) |
|
||||
| Output type | `f64` |
|
||||
| Output range | unbounded; tracks the input price scale |
|
||||
| Default parameters | `period` is required (no default in either binding) |
|
||||
| Warmup period | `period` |
|
||||
| Interpretation | Heavily smoothed price level; the average underlying Wilder's RSI and ATR. |
|
||||
|
||||
## Formula
|
||||
|
||||
```
|
||||
SMMA_period = SMA(price_1 … price_period) (seed)
|
||||
SMMA_t = (SMMA_{t-1} * (period - 1) + price_t) / period (t > period)
|
||||
```
|
||||
|
||||
This is algebraically an exponential moving average with smoothing factor
|
||||
`alpha = 1 / period` — substantially slower than the `Ema` factor of
|
||||
`2 / (period + 1)` at the same `period`. The recurrence is O(1): each
|
||||
`update` touches only the previous value.
|
||||
|
||||
## Parameters
|
||||
|
||||
| Name | Type | Default | Valid range | Description |
|
||||
|----------|---------|---------|-------------|-------------|
|
||||
| `period` | `usize` | none | `>= 1` | Smoothing length. `period = 0` errors with `Error::PeriodZero`. `period = 1` is a pass-through. |
|
||||
|
||||
There is no Python `#[pyo3(signature = …)]` default for `SMMA`, so
|
||||
`wickra.SMMA(period)` requires the period explicitly.
|
||||
|
||||
## Inputs / Outputs
|
||||
|
||||
From `crates/wickra-core/src/indicators/smma.rs`:
|
||||
|
||||
```rust
|
||||
impl Indicator for Smma {
|
||||
type Input = f64;
|
||||
type Output = f64;
|
||||
// update(&mut self, input: f64) -> Option<f64>
|
||||
}
|
||||
```
|
||||
|
||||
A single `f64` close in, an `Option<f64>` out. Python maps this to
|
||||
`float | None` (streaming) or a `numpy.ndarray` with `NaN` warmup rows
|
||||
(batch); Node maps it to `number | null` / `Array<number>` with `NaN`
|
||||
warmup.
|
||||
|
||||
## Warmup
|
||||
|
||||
`Smma::new(period).warmup_period() == period`. The first `period - 1`
|
||||
inputs are buffered while the seed accumulates; the `period`-th `update()`
|
||||
emits the simple average of those inputs as `SMMA_period`. Every later
|
||||
input applies the `(prev·(n−1)+x)/n` recurrence.
|
||||
|
||||
## Edge cases
|
||||
|
||||
- **Constant series.** Feeding `[7.0; n]` returns `Some(7.0)` from input
|
||||
`period` onward — the recurrence is a fixed point for constants
|
||||
(`constant_series_yields_the_constant` pins this).
|
||||
- **NaN / infinity inputs.** The first line of `update` is
|
||||
`if !input.is_finite() { return self.current; }`. Non-finite inputs are
|
||||
**silently dropped** — they neither advance the seed nor perturb the
|
||||
recurrence, and the previous valid value (if any) is returned.
|
||||
- **Reset.** `smma.reset()` clears the seed buffer and the current value,
|
||||
restarting the warmup countdown.
|
||||
|
||||
## Examples
|
||||
|
||||
### Rust
|
||||
|
||||
```rust
|
||||
use wickra::{BatchExt, Indicator, Smma};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut smma = Smma::new(3)?;
|
||||
let out: Vec<Option<f64>> = smma.batch(&[1.0, 2.0, 3.0, 4.0, 5.0]);
|
||||
println!("{:?}", out);
|
||||
println!("warmup_period = {}", smma.warmup_period());
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
[None, None, Some(2.0), Some(2.6666666666666665), Some(3.4444444444444446)]
|
||||
warmup_period = 3
|
||||
```
|
||||
|
||||
The third input emits the seed `(1 + 2 + 3) / 3 = 2.0`; the fourth applies
|
||||
`(2.0·2 + 4) / 3 = 8/3`; the fifth `(8/3·2 + 5) / 3 = 31/9`. This matches
|
||||
the `warmup_then_recurrence` test in
|
||||
`crates/wickra-core/src/indicators/smma.rs`.
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
import wickra as ta
|
||||
|
||||
smma = ta.SMMA(3)
|
||||
print(smma.batch(np.array([1.0, 2.0, 3.0, 4.0, 5.0])))
|
||||
print("warmup_period =", smma.warmup_period())
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
[ nan nan 2. 2.6666667 3.4444444]
|
||||
warmup_period = 3
|
||||
```
|
||||
|
||||
### Node
|
||||
|
||||
```javascript
|
||||
const ta = require('wickra');
|
||||
const smma = new ta.SMMA(3);
|
||||
console.log(smma.batch([1, 2, 3, 4, 5]));
|
||||
console.log('warmupPeriod:', smma.warmupPeriod());
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
[ NaN, NaN, 2, 2.6666666666666665, 3.4444444444444446 ]
|
||||
warmupPeriod: 3
|
||||
```
|
||||
|
||||
## Interpretation
|
||||
|
||||
`Smma` is a very smooth, lag-heavy price level. Because its smoothing
|
||||
factor is `1 / period` rather than `2 / (period + 1)`, an `Smma(n)` is
|
||||
roughly as smooth as an `Ema(2n − 1)` — useful when you want maximum
|
||||
noise rejection from a single line. Its main role in this library,
|
||||
however, is structural: it is the exact smoothing kernel inside
|
||||
[`Rsi`](../momentum/Indicator-Rsi.md) and [`Atr`](../volatility/Indicator-Atr.md),
|
||||
so reaching for `Smma` directly lets you reproduce Wilder-style averages
|
||||
on any series.
|
||||
|
||||
## Common pitfalls
|
||||
|
||||
- **Confusing it with `Ema` at the same period.** `Smma(n)` and `Ema(n)`
|
||||
are *not* interchangeable — `Smma` lags far more. Match `Ema(2n − 1)`
|
||||
if you need comparable smoothness.
|
||||
- **Treating `period = 0` as "use a default".** `Smma::new(0)` returns
|
||||
`Err(Error::PeriodZero)` in Rust and a `ValueError` in Python; pass an
|
||||
explicit period.
|
||||
|
||||
## References
|
||||
|
||||
The smoothed moving average is J. Welles Wilder Jr.'s running average
|
||||
from *New Concepts in Technical Trading Systems* (1978); it is the
|
||||
averaging step in his RSI, ATR and ADX. The implementation here follows
|
||||
the standard SMA-seeded formulation, matching TA-Lib's `RMA`.
|
||||
|
||||
## See also
|
||||
|
||||
- [Indicator-Ema.md](Indicator-Ema.md) — faster exponential average.
|
||||
- [Indicator-Sma.md](Indicator-Sma.md) — the equal-weighted mean used as
|
||||
the SMMA seed.
|
||||
- [Indicator-Trima.md](Indicator-Trima.md) — the other F1 average.
|
||||
- [Indicators-Overview.md](../../Indicators-Overview.md) — the full taxonomy.
|
||||
@@ -0,0 +1,167 @@
|
||||
# TRIMA
|
||||
|
||||
> Triangular Moving Average — a simple moving average applied twice, which
|
||||
> triangular-weights the window so the middle bars carry the most weight.
|
||||
|
||||
## Quick reference
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Family | Trend |
|
||||
| Sub-category | Simple averages |
|
||||
| Input type | `f64` (single close) |
|
||||
| Output type | `f64` |
|
||||
| Output range | unbounded; tracks the input price scale |
|
||||
| Default parameters | `period` is required (no default in either binding) |
|
||||
| Warmup period | `period` |
|
||||
| Interpretation | Very smooth price level; the triangular weighting suppresses edge bars. |
|
||||
|
||||
## Formula
|
||||
|
||||
`TRIMA(n)` is `SMA` stacked on `SMA`. For period `n` the two lengths are:
|
||||
|
||||
```
|
||||
odd n: n1 = n2 = (n + 1) / 2
|
||||
even n: n1 = n / 2, n2 = n / 2 + 1
|
||||
TRIMA_t = SMA_{n2}( SMA_{n1}(price) )_t
|
||||
```
|
||||
|
||||
Composing two equal-weight means convolves two rectangular windows, which
|
||||
yields a triangular weight profile over the original `n` closes — the
|
||||
centre bar gets the largest weight, the two edges the smallest. Both
|
||||
stacked SMAs are O(1), so `update` is O(1) regardless of `period`.
|
||||
|
||||
## Parameters
|
||||
|
||||
| Name | Type | Default | Valid range | Description |
|
||||
|----------|---------|---------|-------------|-------------|
|
||||
| `period` | `usize` | none | `>= 1` | Window length. `period = 0` errors with `Error::PeriodZero`. `period = 1` and `period = 2` degenerate to short SMAs. |
|
||||
|
||||
There is no Python `#[pyo3(signature = …)]` default for `TRIMA`, so
|
||||
`wickra.TRIMA(period)` requires the period explicitly.
|
||||
|
||||
## Inputs / Outputs
|
||||
|
||||
From `crates/wickra-core/src/indicators/trima.rs`:
|
||||
|
||||
```rust
|
||||
impl Indicator for Trima {
|
||||
type Input = f64;
|
||||
type Output = f64;
|
||||
// update(&mut self, input: f64) -> Option<f64>
|
||||
}
|
||||
```
|
||||
|
||||
A single `f64` close in, an `Option<f64>` out. Python maps this to
|
||||
`float | None` / `numpy.ndarray` (NaN warmup); Node to `number | null` /
|
||||
`Array<number>` (NaN warmup).
|
||||
|
||||
## Warmup
|
||||
|
||||
`Trima::new(period).warmup_period() == period`. The inner SMA emits after
|
||||
`n1` inputs; the outer SMA then needs `n2 − 1` more, and `n1 + n2 − 1 = n`
|
||||
for both the odd and even splits. So the first non-`None` output lands on
|
||||
exactly the `period`-th `update()`.
|
||||
|
||||
## Edge cases
|
||||
|
||||
- **Constant series.** `[42.0; n]` returns `Some(42.0)` from input
|
||||
`period` onward — both SMAs are exact for constants
|
||||
(`constant_series_yields_the_constant` pins this).
|
||||
- **NaN / infinity inputs.** `update` returns `self.outer.value()` for a
|
||||
non-finite input *without* feeding either SMA, so the inner SMA's stale
|
||||
value is never double-counted into the outer SMA. State is left
|
||||
untouched.
|
||||
- **Reset.** `trima.reset()` resets both inner and outer SMAs, restarting
|
||||
the warmup countdown.
|
||||
|
||||
## Examples
|
||||
|
||||
### Rust
|
||||
|
||||
```rust
|
||||
use wickra::{BatchExt, Indicator, Trima};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut trima = Trima::new(5)?;
|
||||
let out: Vec<Option<f64>> = trima.batch(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]);
|
||||
println!("{:?}", out);
|
||||
println!("warmup_period = {}", trima.warmup_period());
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
[None, None, None, None, Some(3.0), Some(4.0), Some(5.0)]
|
||||
warmup_period = 5
|
||||
```
|
||||
|
||||
`TRIMA(5)` is `SMA(3)` of `SMA(3)`. `SMA(3)` of `1..=7` is
|
||||
`[_, _, 2, 3, 4, 5, 6]`; `SMA(3)` of that is `[_, _, _, _, 3, 4, 5]`. This
|
||||
matches the `odd_period_reference_values` test in
|
||||
`crates/wickra-core/src/indicators/trima.rs`.
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
import wickra as ta
|
||||
|
||||
trima = ta.TRIMA(5)
|
||||
print(trima.batch(np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0])))
|
||||
print("warmup_period =", trima.warmup_period())
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
[nan nan nan nan 3. 4. 5.]
|
||||
warmup_period = 5
|
||||
```
|
||||
|
||||
### Node
|
||||
|
||||
```javascript
|
||||
const ta = require('wickra');
|
||||
const trima = new ta.TRIMA(5);
|
||||
console.log(trima.batch([1, 2, 3, 4, 5, 6, 7]));
|
||||
console.log('warmupPeriod:', trima.warmupPeriod());
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
[ NaN, NaN, NaN, NaN, 3, 4, 5 ]
|
||||
warmupPeriod: 5
|
||||
```
|
||||
|
||||
## Interpretation
|
||||
|
||||
`Trima` is one of the smoothest single-line averages in the library: the
|
||||
triangular weight profile damps the most recent bar far more than a plain
|
||||
`Sma` does, so whipsaws are rare. The cost is lag — a `Trima(n)` lags
|
||||
roughly like an `Sma(n/2)` doubled. Use it as a slow trend filter where a
|
||||
clean, low-noise line matters more than fast reaction; prefer
|
||||
[`Ema`](Indicator-Ema.md) or [`Hma`](Indicator-Hma.md) when responsiveness
|
||||
matters.
|
||||
|
||||
## Common pitfalls
|
||||
|
||||
- **Expecting `Sma`-like lag.** Stacking two means roughly doubles the
|
||||
effective lag; size the period accordingly.
|
||||
- **Treating `period = 0` as "use a default".** `Trima::new(0)` returns
|
||||
`Err(Error::PeriodZero)` in Rust and a `ValueError` in Python.
|
||||
|
||||
## References
|
||||
|
||||
The triangular moving average is a standard double-smoothed SMA; the
|
||||
odd/even split used here (`n1`, `n2`) matches TA-Lib's `TRIMA`.
|
||||
|
||||
## See also
|
||||
|
||||
- [Indicator-Sma.md](Indicator-Sma.md) — the building block applied twice.
|
||||
- [Indicator-Wma.md](Indicator-Wma.md) — linear (not triangular) weights.
|
||||
- [Indicator-Smma.md](Indicator-Smma.md) — the other F1 average.
|
||||
- [Indicators-Overview.md](../../Indicators-Overview.md) — the full taxonomy.
|
||||
Reference in New Issue
Block a user