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
}
}