feat(frama): add Fractal Adaptive Moving Average
Ehlers' FRAMA adapts its smoothing constant to the fractal dimension of the recent window: tight tracking in trends, heavy smoothing in chop. Uses the close-only variant where max/min over each window half drive the dimension estimate. Period must be even (default 16). Reference: Ehlers, Fractal Adaptive Moving Average, 2005. Touchpoints: - crates/wickra-core: frama.rs + mod.rs + lib.rs re-export - bindings/python: PyFrama + __init__.py + test_new_indicators + test_known_values reference (constant series + uptrend tracking) - bindings/node: FramaNode (scalar macro) + index.d.ts/index.js + indicators.test.js factory + reference value - bindings/wasm: wasm_scalar_indicator! macro - fuzz: indicator_update target covers Frama(16) - crates/wickra/benches: bench_scalar entry - README + CHANGELOG: Moving Averages row + Unreleased entry
This commit is contained in:
@@ -40,6 +40,7 @@ from ._wickra import (
|
||||
VWMA,
|
||||
ALMA,
|
||||
McGinleyDynamic,
|
||||
FRAMA,
|
||||
# Momentum
|
||||
RSI,
|
||||
MACD,
|
||||
@@ -123,6 +124,7 @@ __all__ = [
|
||||
"VWMA",
|
||||
"ALMA",
|
||||
"McGinleyDynamic",
|
||||
"FRAMA",
|
||||
# Momentum
|
||||
"RSI",
|
||||
"MACD",
|
||||
|
||||
@@ -105,6 +105,20 @@ def test_mcginley_dynamic_reference_value():
|
||||
assert math.isclose(out[3], expected, abs_tol=1e-12)
|
||||
|
||||
|
||||
def test_frama_constant_series_yields_the_constant():
|
||||
# Flat input -> degenerate ranges -> alpha clamps to 0.01 and the EMA
|
||||
# recurrence holds the seed value.
|
||||
out = ta.FRAMA(4).batch(np.full(20, 42.0, dtype=np.float64))
|
||||
assert np.all(np.isnan(out[:3]))
|
||||
np.testing.assert_allclose(out[3:], 42.0, atol=1e-12)
|
||||
|
||||
|
||||
def test_frama_pure_uptrend_hugs_latest():
|
||||
# Monotonic uptrend -> alpha pushed toward 1.0, FRAMA tracks close.
|
||||
out = ta.FRAMA(4).batch(np.arange(1.0, 9.0, dtype=np.float64))
|
||||
assert math.isclose(out[-1], 8.0, abs_tol=0.05)
|
||||
|
||||
|
||||
def test_macd_constant_series_converges_to_zero():
|
||||
out = ta.MACD().batch(np.full(200, 100.0))
|
||||
# Last row's MACD and signal must be ~0.
|
||||
|
||||
@@ -46,6 +46,7 @@ SCALAR = [
|
||||
(ta.ZLEMA, (14,)),
|
||||
(ta.ALMA, (9, 0.85, 6.0)),
|
||||
(ta.McGinleyDynamic, (10,)),
|
||||
(ta.FRAMA, (16,)),
|
||||
(ta.T3, (5, 0.7)),
|
||||
(ta.MOM, (10,)),
|
||||
(ta.CMO, (14,)),
|
||||
|
||||
Reference in New Issue
Block a user