namespace QuanTAlib; /// /// DEMA: Double Exponential Moving Average /// DEMA reduces the lag of a traditional EMA by applying a second EMA over EMA. /// It responds more quickly to price changes than a standard EMA while maintaining /// smoothness, at the cost of overshooting the signal line. /// /// /// Smoothness: ★★★☆☆ (3/5) /// Sensitivity: ★★★★☆ (4/5) /// Overshooting: ★★★☆☆ (3/5) /// Lag: ★★★★☆ (4/5) /// /// Sources: /// https://www.investopedia.com/terms/d/double-exponential-moving-average.asp /// https://www.tradingview.com/support/solutions/43000502589-double-exponential-moving-average-dema/ /// /// Validation: /// Skender.Stock.Indicators /// public class Dema : AbstractBase { // inherited _index // inherited _value private readonly int _period; private double _lastEma1, _p_lastEma1; private double _lastEma2, _p_lastEma2; private double _k, _e, _p_e; public Dema(int period) : base() { if (period < 1) { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1."); } _period = period; Name = "Dema"; double percentile = 0.85; //targeting 85th percentile of correctness of converging EMA WarmupPeriod = (int)Math.Ceiling(-period * Math.Log(1 - percentile)); Init(); } public Dema(object source, int period) : this(period) { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } //inhereted public void Sub(object source, in ValueEventArgs args) public override void Init() { base.Init(); _k = 2.0 / (_period + 1); _e = 1.0; _lastEma1 = 0; _lastEma2 = 0; } protected override void ManageState(bool isNew) { if (isNew) { _p_lastEma1 = _lastEma1; _p_lastEma2 = _lastEma2; _p_e = _e; _index++; } else { _lastEma1 = _p_lastEma1; _lastEma2 = _p_lastEma2; _e = _p_e; } } /// /// Core DEMA calculation /// protected override double Calculation() { ManageState(Input.IsNew); double result, _ema1, _ema2; // compensator for early ema values _e = (_e > 1e-10) ? (1 - _k) * _e : 0; double _invE = (_e > 1e-10) ? 1 / (1 - _e) : 1; // Calculate EMA1 _ema1 = _k * (Input.Value - _lastEma1) + _lastEma1; // Calculate EMA2 using compensatedEma1 _ema2 = _k * (_ema1 * _invE - _lastEma2) + _lastEma2; // Calculate DEMA double _dema = 2 * _ema1 * _invE - (_ema2 * _invE); result = _dema; _lastEma1 = _ema1; _lastEma2 = _ema2; IsHot = _index >= WarmupPeriod; return result; } }