From 3287146f440d8051876888c62c1d4dc66d2e589b Mon Sep 17 00:00:00 2001 From: kingchenc Date: Sun, 24 May 2026 12:26:08 +0200 Subject: [PATCH] feat(mcginley): add McGinley Dynamic moving average John McGinley's self-adjusting moving average with the recurrence MD + (price - MD) / (0.6 * period * (price / MD)^4). Speeds up when price falls below the indicator and damps when price runs above the indicator. Seeded with the simple average of the first period inputs. Reference: McGinley, Technical Analysis of Stocks & Commodities, 1990. Touchpoints: - crates/wickra-core: mcginley_dynamic.rs + mod.rs + lib.rs re-export - bindings/python: PyMcGinleyDynamic + __init__.py + test_new_indicators + test_known_values reference - bindings/node: McGinleyDynamicNode (scalar macro) + index.d.ts/index.js + indicators.test.js factory + reference value - bindings/wasm: wasm_scalar_indicator! macro - fuzz: indicator_update target covers McGinleyDynamic(10) - crates/wickra/benches: bench_scalar entry - README + CHANGELOG: Moving Averages row + Unreleased entry --- CHANGELOG.md | 5 + README.md | 4 +- bindings/node/__tests__/indicators.test.js | 10 + bindings/node/index.js | 3 +- bindings/node/src/lib.rs | 1 + bindings/python/python/wickra/__init__.py | 2 + bindings/python/src/lib.rs | 53 +++++ bindings/python/tests/test_known_values.py | 17 ++ bindings/python/tests/test_new_indicators.py | 1 + bindings/wasm/src/lib.rs | 1 + .../src/indicators/mcginley_dynamic.rs | 224 ++++++++++++++++++ crates/wickra-core/src/indicators/mod.rs | 2 + crates/wickra-core/src/lib.rs | 6 +- crates/wickra/benches/indicators.rs | 7 +- fuzz/fuzz_targets/indicator_update.rs | 5 +- 15 files changed, 331 insertions(+), 10 deletions(-) create mode 100644 crates/wickra-core/src/indicators/mcginley_dynamic.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 899017bf..22ed5a4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `[0, 1]`) and kernel width (`sigma > 0`). Community-standard defaults `(period = 9, offset = 0.85, sigma = 6.0)` available via `Alma::classic()`. Exposed in all four bindings (Rust, Python, Node, WASM). +- **Family 01 — Moving Averages.** `McGinleyDynamic`: John McGinley's + self-adjusting MA. Single parameter `period`; the recurrence + `MD + (price - MD) / (0.6 * period * (price / MD)^4)` speeds up when price + falls below the indicator and damps when price runs above. Seeded with the + simple average of the first `period` inputs. Exposed in all four bindings. ## [0.2.7] - 2026-05-24 diff --git a/README.md b/README.md index d0d7a4fb..275ce97e 100644 --- a/README.md +++ b/README.md @@ -109,13 +109,13 @@ python -m benchmarks.compare_libraries ## Indicators -72 streaming-first indicators across eight families. Every one passes the +73 streaming-first indicators across eight families. Every one passes the `batch == streaming` equivalence test, reference-value tests, and reset semantics tests. | Family | Indicators | |--------|-----------| -| Moving Averages | SMA, EMA, WMA, DEMA, TEMA, HMA, KAMA, SMMA, TRIMA, ZLEMA, T3, VWMA, ALMA | +| Moving Averages | SMA, EMA, WMA, DEMA, TEMA, HMA, KAMA, SMMA, TRIMA, ZLEMA, T3, VWMA, ALMA, McGinley Dynamic | | Momentum Oscillators | RSI (Wilder), Stochastic, CCI, ROC, Williams %R, MFI, Awesome Oscillator, MOM, CMO, TSI, PMO, StochRSI, Ultimate Oscillator | | Trend & Directional | MACD, ADX (+DI/-DI), Aroon, TRIX, Aroon Oscillator, Vortex, Mass Index, Choppiness Index, Vertical Horizontal Filter | | Price Oscillators | PPO, DPO, Coppock, Accelerator Oscillator, Balance of Power | diff --git a/bindings/node/__tests__/indicators.test.js b/bindings/node/__tests__/indicators.test.js index fd3ca2d4..82fa1f40 100644 --- a/bindings/node/__tests__/indicators.test.js +++ b/bindings/node/__tests__/indicators.test.js @@ -38,6 +38,7 @@ const scalarFactories = { TRIX: () => new wickra.TRIX(9), KAMA: () => new wickra.KAMA(10, 2, 30), ALMA: () => new wickra.ALMA(9, 0.85, 6.0), + McGinleyDynamic: () => new wickra.McGinleyDynamic(10), SMMA: () => new wickra.SMMA(14), TRIMA: () => new wickra.TRIMA(20), ZLEMA: () => new wickra.ZLEMA(14), @@ -260,6 +261,15 @@ test('LinRegAngle of a unit-slope series is 45 degrees', () => { assert.ok(Math.abs(out[4] - 45) < 1e-9); }); +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]); + assert.ok(Number.isNaN(out[0]) && Number.isNaN(out[1])); + assert.ok(Math.abs(out[2] - 20) < 1e-12); + const expected = 20 + 20 / (0.6 * 3 * 16); + assert.ok(Math.abs(out[3] - expected) < 1e-12); +}); + test('ALMA(3, 0.85, 6) reference value on [10, 20, 30]', () => { // m = 0.85 * 2 = 1.7; s = 3 / 6 = 0.5; 2*s^2 = 0.5. const out = new wickra.ALMA(3, 0.85, 6).batch([10, 20, 30]); diff --git a/bindings/node/index.js b/bindings/node/index.js index b5c9e9d9..3617a0ca 100644 --- a/bindings/node/index.js +++ b/bindings/node/index.js @@ -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, 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, 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 @@ -350,6 +350,7 @@ module.exports.AwesomeOscillator = AwesomeOscillator module.exports.Aroon = Aroon module.exports.KAMA = KAMA module.exports.ALMA = ALMA +module.exports.McGinleyDynamic = McGinleyDynamic module.exports.T3 = T3 module.exports.TSI = TSI module.exports.PMO = PMO diff --git a/bindings/node/src/lib.rs b/bindings/node/src/lib.rs index d3802ac5..4c39206b 100644 --- a/bindings/node/src/lib.rs +++ b/bindings/node/src/lib.rs @@ -116,6 +116,7 @@ node_scalar_indicator!( wc::VerticalHorizontalFilter ); node_scalar_indicator!(ZScoreNode, "ZScore", wc::ZScore); +node_scalar_indicator!(McGinleyDynamicNode, "McGinleyDynamic", wc::McGinleyDynamic); // ============================== MACD ============================== diff --git a/bindings/python/python/wickra/__init__.py b/bindings/python/python/wickra/__init__.py index 5b7d5f2c..bee22843 100644 --- a/bindings/python/python/wickra/__init__.py +++ b/bindings/python/python/wickra/__init__.py @@ -39,6 +39,7 @@ from ._wickra import ( T3, VWMA, ALMA, + McGinleyDynamic, # Momentum RSI, MACD, @@ -121,6 +122,7 @@ __all__ = [ "T3", "VWMA", "ALMA", + "McGinleyDynamic", # Momentum "RSI", "MACD", diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index b821871e..e0da9a2c 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -812,6 +812,58 @@ impl PyKama { } } +// ============================== McGinley Dynamic ============================== + +#[pyclass( + name = "McGinleyDynamic", + module = "wickra._wickra", + skip_from_py_object +)] +#[derive(Clone)] +struct PyMcGinleyDynamic { + inner: wc::McGinleyDynamic, +} + +#[pymethods] +impl PyMcGinleyDynamic { + #[new] + #[pyo3(signature = (period=10))] + fn new(period: usize) -> PyResult { + Ok(Self { + inner: wc::McGinleyDynamic::new(period).map_err(map_err)?, + }) + } + fn update(&mut self, value: f64) -> Option { + self.inner.update(value) + } + fn batch<'py>( + &mut self, + py: Python<'py>, + prices: PyReadonlyArray1<'py, f64>, + ) -> PyResult>> { + let s = prices + .as_slice() + .map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?; + Ok(flatten(self.inner.batch(s)).into_pyarray(py)) + } + #[getter] + fn period(&self) -> usize { + self.inner.period() + } + 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!("McGinleyDynamic(period={})", self.inner.period()) + } +} + // ============================== ALMA ============================== #[pyclass(name = "ALMA", module = "wickra._wickra", skip_from_py_object)] @@ -4556,6 +4608,7 @@ fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/bindings/python/tests/test_known_values.py b/bindings/python/tests/test_known_values.py index 5e5bfe19..5946f386 100644 --- a/bindings/python/tests/test_known_values.py +++ b/bindings/python/tests/test_known_values.py @@ -88,6 +88,23 @@ def test_alma_reference_value_period_3(): assert out[2] > 20.0 +def test_mcginley_dynamic_constant_series_yields_the_constant(): + # ratio = 1, so the recurrence collapses to MD + 0 / divisor = MD. + out = ta.McGinleyDynamic(5).batch(np.full(30, 42.0, dtype=np.float64)) + assert np.all(np.isnan(out[:4])) + np.testing.assert_allclose(out[4:], 42.0, atol=1e-12) + + +def test_mcginley_dynamic_reference_value(): + # Period 3, seed = SMA([10, 20, 30]) = 20.0. Next price 40.0: + # ratio = 2; divisor = 0.6 * 3 * 16 = 28.8; next = 20 + 20/28.8. + out = ta.McGinleyDynamic(3).batch(np.array([10.0, 20.0, 30.0, 40.0])) + assert math.isnan(out[0]) and math.isnan(out[1]) + assert math.isclose(out[2], 20.0, abs_tol=1e-12) + expected = 20.0 + 20.0 / (0.6 * 3.0 * 16.0) + assert math.isclose(out[3], expected, abs_tol=1e-12) + + 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. diff --git a/bindings/python/tests/test_new_indicators.py b/bindings/python/tests/test_new_indicators.py index 747ec64c..2804a2b1 100644 --- a/bindings/python/tests/test_new_indicators.py +++ b/bindings/python/tests/test_new_indicators.py @@ -45,6 +45,7 @@ SCALAR = [ (ta.TRIMA, (20,)), (ta.ZLEMA, (14,)), (ta.ALMA, (9, 0.85, 6.0)), + (ta.McGinleyDynamic, (10,)), (ta.T3, (5, 0.7)), (ta.MOM, (10,)), (ta.CMO, (14,)), diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 88965eae..03d5c8b1 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -80,6 +80,7 @@ wasm_scalar_indicator!(WasmTrima, "TRIMA", wc::Trima, period: usize); 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!(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); diff --git a/crates/wickra-core/src/indicators/mcginley_dynamic.rs b/crates/wickra-core/src/indicators/mcginley_dynamic.rs new file mode 100644 index 00000000..80ea56aa --- /dev/null +++ b/crates/wickra-core/src/indicators/mcginley_dynamic.rs @@ -0,0 +1,224 @@ +//! `McGinley` Dynamic — self-adjusting moving average. + +use std::collections::VecDeque; + +use crate::error::{Error, Result}; +use crate::traits::Indicator; + +/// John `McGinley`'s "Dynamic" — a self-adjusting moving average that speeds up +/// in downtrends and slows down in uptrends to track price more closely than +/// a fixed-period MA. +/// +/// The recurrence is +/// +/// ```text +/// MD_t = MD_{t-1} + (price_t - MD_{t-1}) / (K * period * (price_t / MD_{t-1})^4) +/// ``` +/// +/// where `K = 0.6` is `McGinley`'s original constant. The fourth-power ratio +/// term shrinks the divisor when price falls below the indicator (faster +/// catch-up) and inflates it when price runs above (more smoothing). The +/// indicator is seeded with the simple average of the first `period` inputs. +/// +/// Reference: John R. `McGinley` Jr., *Technical Analysis of Stocks & +/// Commodities*, 1990. +/// +/// # Example +/// +/// ``` +/// use wickra_core::{Indicator, McGinleyDynamic}; +/// +/// let mut md = McGinleyDynamic::new(10).unwrap(); +/// let mut last = None; +/// for i in 0..40 { +/// last = md.update(100.0 + f64::from(i)); +/// } +/// assert!(last.is_some()); +/// ``` +#[derive(Debug, Clone)] +pub struct McGinleyDynamic { + period: usize, + seed: VecDeque, + seed_sum: f64, + current: Option, +} + +/// `McGinley`'s original constant `K` in the recurrence denominator. +const K: f64 = 0.6; + +impl McGinleyDynamic { + /// # Errors + /// Returns [`Error::PeriodZero`] if `period == 0`. + pub fn new(period: usize) -> Result { + if period == 0 { + return Err(Error::PeriodZero); + } + Ok(Self { + period, + seed: VecDeque::with_capacity(period), + seed_sum: 0.0, + current: None, + }) + } + + /// Configured period. + pub const fn period(&self) -> usize { + self.period + } + + /// Current value if available. + pub const fn value(&self) -> Option { + self.current + } +} + +impl Indicator for McGinleyDynamic { + type Input = f64; + type Output = f64; + + fn update(&mut self, input: f64) -> Option { + if !input.is_finite() { + return self.current; + } + if let Some(prev) = self.current { + // The recurrence divides by `(price / prev)^4`; if either side is + // zero or negative the formula blows up, so we hold the previous + // value as a defensive fallback against degenerate price series. + if prev <= 0.0 || input <= 0.0 { + return self.current; + } + let ratio = input / prev; + let divisor = K * (self.period as f64) * ratio.powi(4); + let next = prev + (input - prev) / divisor; + self.current = Some(next); + } else { + self.seed.push_back(input); + self.seed_sum += input; + if self.seed.len() == self.period { + self.current = Some(self.seed_sum / self.period as f64); + } + } + self.current + } + + fn reset(&mut self) { + self.seed.clear(); + self.seed_sum = 0.0; + self.current = None; + } + + fn warmup_period(&self) -> usize { + self.period + } + + fn is_ready(&self) -> bool { + self.current.is_some() + } + + fn name(&self) -> &'static str { + "McGinleyDynamic" + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::BatchExt; + use approx::assert_relative_eq; + + #[test] + fn rejects_zero_period() { + assert!(matches!(McGinleyDynamic::new(0), Err(Error::PeriodZero))); + } + + #[test] + fn accessors_and_metadata() { + let mut md = McGinleyDynamic::new(10).unwrap(); + assert_eq!(md.period(), 10); + assert_eq!(md.warmup_period(), 10); + assert_eq!(md.name(), "McGinleyDynamic"); + assert_eq!(md.value(), None); + for i in 1..=10 { + md.update(f64::from(i)); + } + assert!(md.value().is_some()); + } + + #[test] + fn constant_series_yields_the_constant() { + // ratio = 1, so the recurrence collapses to MD + 0 / divisor = MD. + let mut md = McGinleyDynamic::new(5).unwrap(); + let out = md.batch(&[42.0_f64; 30]); + for v in out.iter().skip(4).flatten() { + assert_relative_eq!(*v, 42.0, epsilon = 1e-12); + } + } + + #[test] + fn warmup_emits_first_value_at_period() { + let mut md = McGinleyDynamic::new(3).unwrap(); + // Seed = SMA([10, 20, 30]) = 20.0. + assert_eq!(md.update(10.0), None); + assert_eq!(md.update(20.0), None); + assert_eq!(md.update(30.0), Some(20.0)); + } + + #[test] + fn reference_value_recurrence() { + // Period 3, seed = SMA([10, 20, 30]) = 20.0. Then on price = 40.0: + // ratio = 40 / 20 = 2 + // divisor = 0.6 * 3 * 2^4 = 0.6 * 3 * 16 = 28.8 + // next = 20 + (40 - 20) / 28.8 = 20.694444... + let mut md = McGinleyDynamic::new(3).unwrap(); + md.batch(&[10.0_f64, 20.0, 30.0]); + let v = md.update(40.0).unwrap(); + let expected = 20.0 + 20.0 / (0.6 * 3.0 * 16.0); + assert_relative_eq!(v, expected, epsilon = 1e-12); + } + + #[test] + fn batch_equals_streaming() { + let prices: Vec = (1..=80) + .map(|i| 100.0 + (f64::from(i) * 0.2).sin() * 5.0) + .collect(); + let mut a = McGinleyDynamic::new(10).unwrap(); + let mut b = McGinleyDynamic::new(10).unwrap(); + assert_eq!( + a.batch(&prices), + prices.iter().map(|p| b.update(*p)).collect::>() + ); + } + + #[test] + fn reset_clears_state() { + let mut md = McGinleyDynamic::new(5).unwrap(); + md.batch(&(1..=30).map(f64::from).collect::>()); + assert!(md.is_ready()); + md.reset(); + assert!(!md.is_ready()); + assert_eq!(md.update(1.0), None); + } + + #[test] + fn ignores_non_finite_input() { + let mut md = McGinleyDynamic::new(3).unwrap(); + md.batch(&[10.0_f64, 20.0, 30.0]); + let before = md.value().unwrap(); + assert_eq!(md.update(f64::NAN), Some(before)); + assert_eq!(md.update(f64::INFINITY), Some(before)); + } + + #[test] + fn holds_value_when_input_is_non_positive() { + // Defensive: a zero or negative price would make the (price/prev)^4 + // divisor zero or otherwise blow up; the recurrence holds steady. + let mut md = McGinleyDynamic::new(3).unwrap(); + md.batch(&[10.0_f64, 20.0, 30.0]); + let before = md.value().unwrap(); + assert_eq!(md.update(0.0), Some(before)); + assert_eq!(md.update(-5.0), Some(before)); + // Once a positive price arrives the recurrence resumes normally. + let after = md.update(40.0).unwrap(); + assert!(after > before); + } +} diff --git a/crates/wickra-core/src/indicators/mod.rs b/crates/wickra-core/src/indicators/mod.rs index 5d1ea2c6..ab41ca66 100644 --- a/crates/wickra-core/src/indicators/mod.rs +++ b/crates/wickra-core/src/indicators/mod.rs @@ -40,6 +40,7 @@ mod linreg_angle; mod linreg_slope; mod macd; mod mass_index; +mod mcginley_dynamic; mod median_price; mod mfi; mod mom; @@ -113,6 +114,7 @@ pub use linreg_angle::LinRegAngle; pub use linreg_slope::LinRegSlope; pub use macd::{MacdIndicator, MacdOutput}; pub use mass_index::MassIndex; +pub use mcginley_dynamic::McGinleyDynamic; pub use median_price::MedianPrice; pub use mfi::Mfi; pub use mom::Mom; diff --git a/crates/wickra-core/src/lib.rs b/crates/wickra-core/src/lib.rs index 4fb4fc6d..424cb759 100644 --- a/crates/wickra-core/src/lib.rs +++ b/crates/wickra-core/src/lib.rs @@ -50,9 +50,9 @@ pub use indicators::{ ChandeKrollStopOutput, ChandelierExit, ChandelierExitOutput, ChoppinessIndex, Cmo, Coppock, Dema, Donchian, DonchianOutput, Dpo, EaseOfMovement, Ema, ForceIndex, HistoricalVolatility, Hma, Kama, Keltner, KeltnerOutput, LinRegAngle, LinRegSlope, LinearRegression, MacdIndicator, - MacdOutput, MassIndex, MedianPrice, Mfi, Mom, Natr, Obv, PercentB, Pmo, Ppo, Psar, Roc, - RollingVwap, Rsi, Sma, Smma, StdDev, StochRsi, Stochastic, StochasticOutput, SuperTrend, - SuperTrendOutput, Tema, Trima, Trix, TrueRange, Tsi, TypicalPrice, UlcerIndex, + MacdOutput, MassIndex, McGinleyDynamic, MedianPrice, Mfi, Mom, Natr, Obv, PercentB, Pmo, Ppo, + Psar, Roc, RollingVwap, Rsi, Sma, Smma, StdDev, StochRsi, Stochastic, StochasticOutput, + SuperTrend, SuperTrendOutput, Tema, Trima, Trix, TrueRange, Tsi, TypicalPrice, UlcerIndex, UltimateOscillator, VerticalHorizontalFilter, VolumePriceTrend, Vortex, VortexOutput, Vwap, Vwma, WeightedClose, WilliamsR, Wma, ZScore, Zlema, T3, }; diff --git a/crates/wickra/benches/indicators.rs b/crates/wickra/benches/indicators.rs index 6e3b3852..1ed0439a 100644 --- a/crates/wickra/benches/indicators.rs +++ b/crates/wickra/benches/indicators.rs @@ -19,8 +19,8 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; use std::hint::black_box; use wickra::{ - Alma, Atr, BatchExt, BollingerBands, Candle, Ema, Indicator, MacdIndicator, Obv, Rsi, Sma, - Stochastic, Wma, + Alma, Atr, BatchExt, BollingerBands, Candle, Ema, Indicator, MacdIndicator, McGinleyDynamic, + Obv, Rsi, Sma, Stochastic, Wma, }; use wickra_data::csv::CandleReader; @@ -140,6 +140,9 @@ fn benches(c: &mut Criterion) { bench_scalar(c, "wma", &closes, || Wma::new(14).unwrap()); bench_scalar(c, "rsi", &closes, || Rsi::new(14).unwrap()); bench_scalar(c, "alma", &closes, || Alma::new(9, 0.85, 6.0).unwrap()); + bench_scalar(c, "mcginley_dynamic", &closes, || { + McGinleyDynamic::new(10).unwrap() + }); bench_macd(c, &closes); bench_bollinger(c, &closes); bench_candle_input(c, "atr", &candles, || Atr::new(14).unwrap()); diff --git a/fuzz/fuzz_targets/indicator_update.rs b/fuzz/fuzz_targets/indicator_update.rs index 887ea647..74b4e17f 100644 --- a/fuzz/fuzz_targets/indicator_update.rs +++ b/fuzz/fuzz_targets/indicator_update.rs @@ -16,8 +16,8 @@ use libfuzzer_sys::fuzz_target; use wickra_core::{ Alma, BatchExt, BollingerBands, Cmo, Coppock, Dema, Dpo, Ema, HistoricalVolatility, Hma, - Indicator, Kama, LinRegAngle, LinRegSlope, LinearRegression, MacdIndicator, Mom, Pmo, Ppo, Roc, - Rsi, Sma, Smma, StdDev, StochRsi, T3, Tema, Trima, Trix, Tsi, UlcerIndex, + Indicator, Kama, LinRegAngle, LinRegSlope, LinearRegression, MacdIndicator, McGinleyDynamic, + Mom, Pmo, Ppo, Roc, Rsi, Sma, Smma, StdDev, StochRsi, T3, Tema, Trima, Trix, Tsi, UlcerIndex, VerticalHorizontalFilter, Wma, ZScore, Zlema, }; @@ -54,6 +54,7 @@ fuzz_target!(|data: Vec| { drive(|| Zlema::new(14).unwrap(), &data); drive(|| Kama::new(10, 2, 30).unwrap(), &data); drive(|| Alma::new(9, 0.85, 6.0).unwrap(), &data); + drive(|| McGinleyDynamic::new(10).unwrap(), &data); drive(|| T3::new(14, 0.7).unwrap(), &data); drive(|| Mom::new(14).unwrap(), &data); drive(|| Cmo::new(14).unwrap(), &data);