using System.Runtime.CompilerServices; namespace QuanTAlib; /// /// PLUS_DM: Plus Directional Movement (Wilder, 1978) /// /// /// Wilder-smoothed upward directional movement in price units. /// Extracted from the DX calculation: Smoothed(+DM) using Wilder's method. /// Values ≥ 0 in price units. Higher values indicate stronger upward movement magnitude. /// [SkipLocalsInit] public sealed class PlusDm : ITValuePublisher { private readonly Dx _dx; /// Display name for the indicator. public string Name { get; } public event TValuePublishedHandler? Pub; /// Current smoothed +DM value. public TValue Last { get; private set; } /// True when the indicator has warmed up. public bool IsHot => _dx.IsHot; /// Bars required for warmup. public int WarmupPeriod => _dx.WarmupPeriod; /// The period parameter. public int Period => _dx.Period; /// /// Creates PlusDm with specified period. /// /// Wilder smoothing period (must be > 0) public PlusDm(int period = 14) { if (period <= 0) { throw new ArgumentException("Period must be greater than 0", nameof(period)); } _dx = new Dx(period); Name = $"PlusDm({period})"; } /// /// Creates PlusDm and immediately processes the bar series. /// public PlusDm(TBarSeries source, int period = 14) : this(period) { var result = Batch(source, period); Last = result[^1]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public TValue Update(TBar input, bool isNew = true) { _dx.Update(input, isNew); Last = _dx.DmPlus; if (isNew) { Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = isNew }); } return Last; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public TValue Update(TValue input, bool isNew = true) { // DM requires OHLC data — scalar update not meaningful return Last; } public TSeries Update(TBarSeries source) { var result = new TSeries(source.Count); foreach (var bar in source) { result.Add(Update(bar)); } return result; } /// /// Initializes the indicator state using the provided bar series history. /// public void Prime(TBarSeries source) { foreach (var bar in source) { Update(bar); } } public void Prime(ReadOnlySpan source, TimeSpan? step = null) { // Not applicable — DM requires OHLC bar data, not scalar values } public static TSeries Batch(TBarSeries source, int period = 14) { var indicator = new PlusDm(period); return indicator.Update(source); } public static (TSeries Results, PlusDm Indicator) Calculate(TBarSeries source, int period = 14) { var indicator = new PlusDm(period); return (indicator.Update(source), indicator); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Reset() { _dx.Reset(); Last = default; } }