using System.Runtime.CompilerServices; namespace QuanTAlib; /// /// DMX: Enhanced Directional Movement Index using JMA smoothing /// An improvement over the traditional DMI indicator that uses Jurik Moving Average (JMA) /// for smoothing. This enhancement provides better noise reduction while maintaining /// responsiveness to significant price movements. /// /// /// The DMX calculation process: /// 1. Calculate DMI using the standard Dmi class /// 2. Apply JMA smoothing to the +DI and -DI values /// /// Key improvements over DMI: /// - Uses JMA's adaptive volatility-based smoothing /// - Better noise reduction in the directional movement signals /// - Maintains responsiveness to significant price movements /// - Reduced lag through JMA's phase-shifting /// /// Formula: /// DMI calculation as per standard DMI /// DMX +DI = JMA(DMI +DI) /// DMX -DI = JMA(DMI -DI) /// /// Sources: /// Original DMI by J. Welles Wilder Jr. - "New Concepts in Technical Trading Systems" (1978) /// Enhanced with JMA smoothing by Mark Jurik /// [SkipLocalsInit] public sealed class Dmx : AbstractBarBase { private readonly Dmi _dmi; private readonly Jma _smoothedPlusDi; private readonly Jma _smoothedMinusDi; private double _plusDi, _minusDi; private const int DefaultDmiPeriod = 14; private const int DefaultJmaPeriod = 7; private const int DefaultPhase = 100; private const double DefaultFactor = 0.25; /// /// Gets the most recent smoothed +DI value /// public double PlusDI => _plusDi; /// /// Gets the most recent smoothed -DI value /// public double MinusDI => _minusDi; /// The number of periods used in the DMI calculation (default 14). /// The number of periods used in the JMA smoothing (default 10). /// The phase for the JMA smoothing (default 100). /// The factor for the JMA smoothing (default 0.25). /// Thrown when period is less than 1. [MethodImpl(MethodImplOptions.AggressiveInlining)] public Dmx(int period = DefaultDmiPeriod, int jmaPeriod = DefaultJmaPeriod, int phase = DefaultPhase, double factor = DefaultFactor) { if (period < 1 || jmaPeriod < 1) throw new ArgumentOutOfRangeException(nameof(period), "Periods must be greater than or equal to 1."); _dmi = new(period); _smoothedPlusDi = new(jmaPeriod, phase, factor); _smoothedMinusDi = new(jmaPeriod, phase, factor); WarmupPeriod = period + jmaPeriod; Name = $"DMX({period},{jmaPeriod})"; } [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void ManageState(bool isNew) { if (isNew) { _index++; } } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] protected override double Calculation() { ManageState(Input.IsNew); // Calculate DMI _dmi.Calc(Input); // Smooth the DMI values using JMA _plusDi = _smoothedPlusDi.Calc(_dmi.PlusDI, Input.IsNew).Value; _minusDi = _smoothedMinusDi.Calc(_dmi.MinusDI, Input.IsNew).Value; return _plusDi - _minusDi; // Return the difference as main value } }