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:
@@ -39,6 +39,7 @@ const scalarFactories = {
|
||||
KAMA: () => new wickra.KAMA(10, 2, 30),
|
||||
ALMA: () => new wickra.ALMA(9, 0.85, 6.0),
|
||||
McGinleyDynamic: () => new wickra.McGinleyDynamic(10),
|
||||
FRAMA: () => new wickra.FRAMA(16),
|
||||
SMMA: () => new wickra.SMMA(14),
|
||||
TRIMA: () => new wickra.TRIMA(20),
|
||||
ZLEMA: () => new wickra.ZLEMA(14),
|
||||
@@ -261,6 +262,11 @@ test('LinRegAngle of a unit-slope series is 45 degrees', () => {
|
||||
assert.ok(Math.abs(out[4] - 45) < 1e-9);
|
||||
});
|
||||
|
||||
test('FRAMA pure uptrend hugs the latest close', () => {
|
||||
const out = new wickra.FRAMA(4).batch([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
assert.ok(Math.abs(out[out.length - 1] - 8) < 0.05);
|
||||
});
|
||||
|
||||
test('McGinleyDynamic(3) seeds with SMA and recurses on the next price', () => {
|
||||
// Seed = SMA([10, 20, 30]) = 20. On 40: ratio = 2, divisor = 0.6*3*16 = 28.8.
|
||||
const out = new wickra.McGinleyDynamic(3).batch([10, 20, 30, 40]);
|
||||
|
||||
@@ -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, SMMA, TRIMA, ZLEMA, MOM, CMO, DPO, StdDev, UlcerIndex, VerticalHorizontalFilter, ZScore, MACD, BollingerBands, ATR, Stochastic, OBV, ADX, CCI, WilliamsR, MFI, PSAR, Keltner, Donchian, VWAP, RollingVWAP, AwesomeOscillator, Aroon, KAMA, ALMA, McGinleyDynamic, T3, TSI, PMO, ADL, VolumePriceTrend, ChaikinMoneyFlow, ChaikinOscillator, ForceIndex, EaseOfMovement, SuperTrend, ChandelierExit, ChandeKrollStop, AtrTrailingStop, TypicalPrice, MedianPrice, WeightedClose, LinearRegression, LinRegSlope, AcceleratorOscillator, BalanceOfPower, ChoppinessIndex, TrueRange, ChaikinVolatility, LinRegAngle, BollingerBandwidth, PercentB, NATR, HistoricalVolatility, AroonOscillator, Vortex, MassIndex, StochRSI, UltimateOscillator, PPO, Coppock, VWMA } = nativeBinding
|
||||
const { version, SMA, EMA, WMA, RSI, DEMA, TEMA, HMA, ROC, TRIX, SMMA, TRIMA, ZLEMA, MOM, CMO, DPO, StdDev, UlcerIndex, VerticalHorizontalFilter, ZScore, MACD, BollingerBands, ATR, Stochastic, OBV, ADX, CCI, WilliamsR, MFI, PSAR, Keltner, Donchian, VWAP, RollingVWAP, AwesomeOscillator, Aroon, KAMA, ALMA, McGinleyDynamic, FRAMA, T3, TSI, PMO, ADL, VolumePriceTrend, ChaikinMoneyFlow, ChaikinOscillator, ForceIndex, EaseOfMovement, SuperTrend, ChandelierExit, ChandeKrollStop, AtrTrailingStop, TypicalPrice, MedianPrice, WeightedClose, LinearRegression, LinRegSlope, AcceleratorOscillator, BalanceOfPower, ChoppinessIndex, TrueRange, ChaikinVolatility, LinRegAngle, BollingerBandwidth, PercentB, NATR, HistoricalVolatility, AroonOscillator, Vortex, MassIndex, StochRSI, UltimateOscillator, PPO, Coppock, VWMA } = nativeBinding
|
||||
|
||||
module.exports.version = version
|
||||
module.exports.SMA = SMA
|
||||
@@ -351,6 +351,7 @@ module.exports.Aroon = Aroon
|
||||
module.exports.KAMA = KAMA
|
||||
module.exports.ALMA = ALMA
|
||||
module.exports.McGinleyDynamic = McGinleyDynamic
|
||||
module.exports.FRAMA = FRAMA
|
||||
module.exports.T3 = T3
|
||||
module.exports.TSI = TSI
|
||||
module.exports.PMO = PMO
|
||||
|
||||
@@ -117,6 +117,7 @@ node_scalar_indicator!(
|
||||
);
|
||||
node_scalar_indicator!(ZScoreNode, "ZScore", wc::ZScore);
|
||||
node_scalar_indicator!(McGinleyDynamicNode, "McGinleyDynamic", wc::McGinleyDynamic);
|
||||
node_scalar_indicator!(FramaNode, "FRAMA", wc::Frama);
|
||||
|
||||
// ============================== MACD ==============================
|
||||
|
||||
|
||||
@@ -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,)),
|
||||
|
||||
@@ -81,6 +81,7 @@ wasm_scalar_indicator!(WasmZlema, "ZLEMA", wc::Zlema, period: usize);
|
||||
wasm_scalar_indicator!(WasmT3, "T3", wc::T3, period: usize, v: f64);
|
||||
wasm_scalar_indicator!(WasmAlma, "ALMA", wc::Alma, period: usize, offset: f64, sigma: f64);
|
||||
wasm_scalar_indicator!(WasmMcGinleyDynamic, "McGinleyDynamic", wc::McGinleyDynamic, period: usize);
|
||||
wasm_scalar_indicator!(WasmFrama, "FRAMA", wc::Frama, period: usize);
|
||||
wasm_scalar_indicator!(WasmMom, "MOM", wc::Mom, period: usize);
|
||||
wasm_scalar_indicator!(WasmCmo, "CMO", wc::Cmo, period: usize);
|
||||
wasm_scalar_indicator!(WasmTsi, "TSI", wc::Tsi, long: usize, short: usize);
|
||||
|
||||
Reference in New Issue
Block a user