From 97b1ddd73d36de6ae49b0a5f42bc2d918a8b414e Mon Sep 17 00:00:00 2001 From: AlbertoAmadorBelchistim Date: Sat, 18 Apr 2026 07:56:18 +0200 Subject: [PATCH 1/5] fix(Delta): clear opposite divergence series in MinimizedMode When a bar changes divergence direction during a real-time recalc (e.g. from bullish to bearish divergence), the previously-set series kept its value because only the active branch was written. This left a "ghost" dot of the previous divergence visible on the last bar. Reset the opposite series to 0 explicitly in both branches so the dots reflect the current divergence state only. Only affects the MinimizedMode path; the non-minimized branch already resets both series correctly. --- Technical/Delta.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Technical/Delta.cs b/Technical/Delta.cs index 62dad7e4..5d8f5e80 100644 --- a/Technical/Delta.cs +++ b/Technical/Delta.cs @@ -804,12 +804,14 @@ protected override void OnCalculate(int bar, decimal value) if (candle.Close > candle.Open && deltaValue < 0) { _downSeries[bar] = _downCandles[bar].High; - hasDivergence = true; + _upSeries[bar] = 0; + hasDivergence = true; } else if (candle.Close < candle.Open && deltaValue > 0) { _upSeries[bar] = _candles[bar].High; - hasDivergence = true; + _downSeries[bar] = 0; + hasDivergence = true; } else { From 784114abc3d47714013f5827dce01146ebd2152a Mon Sep 17 00:00:00 2001 From: AlbertoAmadorBelchistim Date: Sat, 18 Apr 2026 08:00:02 +0200 Subject: [PATCH 2/5] fix(Delta): align GetMinWidth with OnRender for negative delta in MinimizedMode GetMinWidth used `-_candles[i].Open` to represent the magnitude of a negative-delta bar in MinimizedMode, but `_candles[i]` is an empty Candle in that case (the real data lives in `_downCandles[i]`), so the computed sample width was always 0 for negative bars. This could let the `minWidth > barWidth` guard pass incorrectly and render volume labels that no longer fit the bar. Match the formula OnRender already uses: `_candles[i].Close > 0 ? _candles[i].Close : -_downCandles[i].Close`. --- Technical/Delta.cs | 266 ++++++++++++++++++++++----------------------- 1 file changed, 133 insertions(+), 133 deletions(-) diff --git a/Technical/Delta.cs b/Technical/Delta.cs index 5d8f5e80..b596c001 100644 --- a/Technical/Delta.cs +++ b/Technical/Delta.cs @@ -204,11 +204,11 @@ public enum Location IgnoredByAlerts = true }; - #endregion + #endregion - #region Properties + #region Properties - #region Visualization + #region Visualization [Display(ResourceType = typeof(Strings), Name = nameof(Strings.VisualMode), GroupName = nameof(Strings.Visualization), Description = nameof(Strings.VisualModeDescription), Order = 10)] @@ -220,48 +220,48 @@ public DeltaVisualMode Mode { _mode = value; - if (_mode == DeltaVisualMode.Histogram) - { - _delta.VisualType = VisualMode.Histogram; - _diapasonHigh.VisualType = VisualMode.Hide; - _diapasonLow.VisualType = VisualMode.Hide; - _candles.Visible = _downCandles.Visible = false; - _divergenceCandles.Visible = _divergenceDownCandles.Visible = false; - } - else if (_mode == DeltaVisualMode.HighLow) - { - _delta.VisualType = VisualMode.Histogram; - _diapasonHigh.VisualType = VisualMode.Histogram; - _diapasonLow.VisualType = VisualMode.Histogram; - _candles.Visible = _downCandles.Visible = false; - _divergenceCandles.Visible = _divergenceDownCandles.Visible = false; - } - else if (_mode == DeltaVisualMode.Candles) - { - _delta.VisualType = VisualMode.Hide; - _diapasonHigh.VisualType = VisualMode.Hide; - _diapasonLow.VisualType = VisualMode.Hide; - _candles.Visible = _downCandles.Visible = true; - _candles.Mode = _downCandles.Mode = CandleVisualMode.Candles; - _divergenceCandles.Mode = _divergenceDownCandles.Mode = CandleVisualMode.Candles; - } - else - { - _delta.VisualType = VisualMode.Hide; - _diapasonHigh.VisualType = VisualMode.Hide; - _diapasonLow.VisualType = VisualMode.Hide; - _candles.Visible = _downCandles.Visible = true; - _candles.Mode = _downCandles.Mode = CandleVisualMode.Bars; - _divergenceCandles.Mode = _divergenceDownCandles.Mode = CandleVisualMode.Bars; - } - - RaisePropertyChanged("Mode"); - RecalculateValues(); + if (_mode == DeltaVisualMode.Histogram) + { + _delta.VisualType = VisualMode.Histogram; + _diapasonHigh.VisualType = VisualMode.Hide; + _diapasonLow.VisualType = VisualMode.Hide; + _candles.Visible = _downCandles.Visible = false; + _divergenceCandles.Visible = _divergenceDownCandles.Visible = false; + } + else if (_mode == DeltaVisualMode.HighLow) + { + _delta.VisualType = VisualMode.Histogram; + _diapasonHigh.VisualType = VisualMode.Histogram; + _diapasonLow.VisualType = VisualMode.Histogram; + _candles.Visible = _downCandles.Visible = false; + _divergenceCandles.Visible = _divergenceDownCandles.Visible = false; + } + else if (_mode == DeltaVisualMode.Candles) + { + _delta.VisualType = VisualMode.Hide; + _diapasonHigh.VisualType = VisualMode.Hide; + _diapasonLow.VisualType = VisualMode.Hide; + _candles.Visible = _downCandles.Visible = true; + _candles.Mode = _downCandles.Mode = CandleVisualMode.Candles; + _divergenceCandles.Mode = _divergenceDownCandles.Mode = CandleVisualMode.Candles; + } + else + { + _delta.VisualType = VisualMode.Hide; + _diapasonHigh.VisualType = VisualMode.Hide; + _diapasonLow.VisualType = VisualMode.Hide; + _candles.Visible = _downCandles.Visible = true; + _candles.Mode = _downCandles.Mode = CandleVisualMode.Bars; + _divergenceCandles.Mode = _divergenceDownCandles.Mode = CandleVisualMode.Bars; + } + + RaisePropertyChanged("Mode"); + RecalculateValues(); ApplyDivergenceColorsToCurrentMode(); - UpdateDivergenceCandlesVisibility(); - } - } + UpdateDivergenceCandlesVisibility(); + } + } [Display(ResourceType = typeof(Strings), Name = nameof(Strings.MinimizedMode), GroupName = nameof(Strings.Visualization), Description = nameof(Strings.HistogramMinimizedModeDescription), Order = 20)] @@ -275,8 +275,8 @@ public bool MinimizedMode RaisePropertyChanged("MinimizedMode"); RecalculateValues(); UpdateDivergenceCandlesVisibility(); - } - } + } + } [Display(ResourceType = typeof(Strings), Name = nameof(Strings.ShowCurrentValue), GroupName = nameof(Strings.Visualization), Description = nameof(Strings.ShowCurrentValueDescription), Order = 30)] @@ -334,9 +334,9 @@ public CrossColor NeutralColor } } - #endregion + #endregion - #region Filters + #region Filters [Display(ResourceType = typeof(Strings), Name = nameof(Strings.BarsDirection), GroupName = nameof(Strings.Filters), Description = nameof(Strings.BarDirectionDescription), Order = 100)] @@ -379,11 +379,11 @@ public decimal Filter } } - #endregion + #endregion - #region Divergence + #region Divergence - private Indicators.FilterColor _divergenceBarsFilter = new(true) { Enabled = false, Value = CrossColor.FromArgb(255, 255, 165, 0) }; + private Indicators.FilterColor _divergenceBarsFilter = new(true) { Enabled = false, Value = CrossColor.FromArgb(255, 255, 165, 0) }; [Display(ResourceType = typeof(Strings), Name = nameof(Strings.DivergenceDots), GroupName = nameof(Strings.Divergence), Description = nameof(Strings.BarDirVsDeltaDivergenceDescription), Order = 130)] @@ -400,37 +400,37 @@ public Indicators.FilterColor DivergenceBarsFilter if (_divergenceBarsFilter == value) return; - if (_divergenceBarsFilter != null) - _divergenceBarsFilter.PropertyChanged -= OnDivergenceFilterChanged; + if (_divergenceBarsFilter != null) + _divergenceBarsFilter.PropertyChanged -= OnDivergenceFilterChanged; - _divergenceBarsFilter = value; + _divergenceBarsFilter = value; - if (_divergenceBarsFilter != null) - _divergenceBarsFilter.PropertyChanged += OnDivergenceFilterChanged; + if (_divergenceBarsFilter != null) + _divergenceBarsFilter.PropertyChanged += OnDivergenceFilterChanged; - RaisePropertyChanged(nameof(DivergenceBarsFilter)); - } - } + RaisePropertyChanged(nameof(DivergenceBarsFilter)); + } + } - #endregion + #endregion - #region Absorption + #region Absorption - private readonly CandleDataSeries _absorptionCandles = new("AbsorptionDotsCandles", "Absorption Dots") - { - UpCandleColor = Color.Green.Convert(), - DownCandleColor = Color.Red.Convert(), - BorderColor = CrossColor.FromArgb(0, 0, 0, 0), - IsHidden = false, - UseMinimizedModeIfEnabled = true, - ShowCurrentValue = false - }; + private readonly CandleDataSeries _absorptionCandles = new("AbsorptionDotsCandles", "Absorption Dots") + { + UpCandleColor = Color.Green.Convert(), + DownCandleColor = Color.Red.Convert(), + BorderColor = CrossColor.FromArgb(0, 0, 0, 0), + IsHidden = false, + UseMinimizedModeIfEnabled = true, + ShowCurrentValue = false + }; #pragma warning disable CS0414 private int _absorptionThreshold = 250; #pragma warning restore CS0414 - private FilterInt _absorption = new(true) { Enabled = false, Value = 250 }; + private FilterInt _absorption = new(true) { Enabled = false, Value = 250 }; [Display(ResourceType = typeof(Strings), Name = nameof(Strings.Absorption), GroupName = nameof(Strings.Absorption), Description = nameof(Strings.AbsorptionThresholdDesc), Order = 140)] @@ -444,36 +444,36 @@ public FilterInt Absorption if (_absorption == value) return; - if (_absorption != null) - _absorption.PropertyChanged -= OnAbsorptionFilterChanged; + if (_absorption != null) + _absorption.PropertyChanged -= OnAbsorptionFilterChanged; - _absorption = value; + _absorption = value; - if (_absorption != null) - _absorption.PropertyChanged += OnAbsorptionFilterChanged; + if (_absorption != null) + _absorption.PropertyChanged += OnAbsorptionFilterChanged; - RaisePropertyChanged(nameof(Absorption)); - } - } + RaisePropertyChanged(nameof(Absorption)); + } + } - // Backward compatibility properties (hidden from UI) - [Browsable(false)] - public bool ShowAbsorptionDots - { - get => Absorption.Enabled; - set => Absorption.Enabled = value; - } + // Backward compatibility properties (hidden from UI) + [Browsable(false)] + public bool ShowAbsorptionDots + { + get => Absorption.Enabled; + set => Absorption.Enabled = value; + } - [Browsable(false)] - public int AbsorptionDeltaThreshold - { - get => Absorption.Value; - set => Absorption.Value = value; - } + [Browsable(false)] + public int AbsorptionDeltaThreshold + { + get => Absorption.Value; + set => Absorption.Value = value; + } - #endregion + #endregion - #region Volume + #region Volume [Display(ResourceType = typeof(Strings), Name = nameof(Strings.Show), GroupName = nameof(Strings.VolumeLabel), Order = 200, Description = nameof(Strings.VolumeLabelDescription))] @@ -499,9 +499,9 @@ public CrossColor FontColor [Tab(TabName = nameof(Strings.Visualization), TabOrder = 1, ResourceType = typeof(Strings))] public FontSetting Font { get; set; } = new("Arial", 10); - #endregion + #endregion - #region Alerts + #region Alerts [Display(ResourceType = typeof(Strings), Name = nameof(Strings.UpAlert), GroupName = nameof(Strings.Alerts), Description = nameof(Strings.UpAlertFileFilterDescription), Order = 300)] @@ -519,33 +519,33 @@ public CrossColor FontColor public Filter DownAlert { get; set; } = new() { Enabled = false, Value = 0 }; - [Browsable(false)] - public bool UseAlerts - { - get => UpAlert.Enabled; - set => UpAlert.Enabled = value; - } + [Browsable(false)] + public bool UseAlerts + { + get => UpAlert.Enabled; + set => UpAlert.Enabled = value; + } - [Browsable(false)] - public decimal AlertFilter - { - get => UpAlert.Value; - set => UpAlert.Value = value; - } + [Browsable(false)] + public decimal AlertFilter + { + get => UpAlert.Value; + set => UpAlert.Value = value; + } - [Browsable(false)] - public bool UseNegativeAlerts - { - get => DownAlert.Enabled; - set => DownAlert.Enabled = value; - } + [Browsable(false)] + public bool UseNegativeAlerts + { + get => DownAlert.Enabled; + set => DownAlert.Enabled = value; + } - [Browsable(false)] - public decimal NegativeAlertFilter - { - get => DownAlert.Value; - set => DownAlert.Value = value; - } + [Browsable(false)] + public decimal NegativeAlertFilter + { + get => DownAlert.Value; + set => DownAlert.Value = value; + } [Display(ResourceType = typeof(Strings), Name = nameof(Strings.AlertFile), GroupName = nameof(Strings.Alerts), Description = nameof(Strings.AlertFileDescription), Order = 320)] @@ -562,13 +562,13 @@ public decimal NegativeAlertFilter [Tab(TabName = nameof(Strings.Alerts), TabOrder = 2, ResourceType = typeof(Strings))] public CrossColor AlertBGColor { get; set; } = CrossColor.FromArgb(255, 75, 72, 72); - #endregion + #endregion - #endregion + #endregion - #region ctor + #region ctor - public Delta() + public Delta() : base(true) { EnableCustomDrawing = true; @@ -730,7 +730,7 @@ protected override void OnCalculate(int bar, decimal value) minDelta = 0; else maxDelta = 0; - } + } var isUnderFilter = absDelta < _filter; @@ -804,14 +804,14 @@ protected override void OnCalculate(int bar, decimal value) if (candle.Close > candle.Open && deltaValue < 0) { _downSeries[bar] = _downCandles[bar].High; - _upSeries[bar] = 0; - hasDivergence = true; + _upSeries[bar] = 0; + hasDivergence = true; } else if (candle.Close < candle.Open && deltaValue > 0) { _upSeries[bar] = _candles[bar].High; - _downSeries[bar] = 0; - hasDivergence = true; + _downSeries[bar] = 0; + hasDivergence = true; } else { @@ -841,7 +841,7 @@ protected override void OnCalculate(int bar, decimal value) _divergenceBars[bar] = hasDivergence; if (hasDivergence && DivergenceBarsFilter != null && DivergenceBarsFilter.Enabled && - (_mode == DeltaVisualMode.Candles || _mode == DeltaVisualMode.Bars)) + (_mode == DeltaVisualMode.Candles || _mode == DeltaVisualMode.Bars)) { if (MinimizedMode) { @@ -871,7 +871,7 @@ protected override void OnCalculate(int bar, decimal value) _delta.Colors[bar] = deltaValue > 0 ? _upColor : _downColor; if (DivergenceBarsFilter != null && DivergenceBarsFilter.Enabled && - (_mode == DeltaVisualMode.Histogram || _mode == DeltaVisualMode.HighLow)) + (_mode == DeltaVisualMode.Histogram || _mode == DeltaVisualMode.HighLow)) { if (hasDivergence) { @@ -902,7 +902,7 @@ protected override void OnCalculate(int bar, decimal value) var negativeAlertValue = DownAlert.Value; if ((deltaValue >= negativeAlertValue && _prevDeltaValue < negativeAlertValue) || - (deltaValue <= negativeAlertValue && _prevDeltaValue > negativeAlertValue)) + (deltaValue <= negativeAlertValue && _prevDeltaValue > negativeAlertValue)) { _lastBarNegativeAlert = bar; AddAlert(AlertFile, InstrumentInfo.Instrument, $"Delta reached {negativeAlertValue} filter", AlertBGColor, AlertForeColor); @@ -1000,9 +1000,9 @@ private int GetMinWidth(RenderContext context, int startBar, int endBar) if (MinimizedMode) { - value = _candles[i].Close > _candles[i].Open + value = _candles[i].Close > 0 ? _candles[i].Close - : -_candles[i].Open; + : -_downCandles[i].Close; } else value = _candles[i].Close; From 492688c9e6f2b993c019627d2f08b951137de496 Mon Sep 17 00:00:00 2001 From: AlbertoAmadorBelchistim Date: Sat, 18 Apr 2026 08:09:13 +0200 Subject: [PATCH 3/5] refactor(delta): replace anonymous PropertyChanged lambdas with named handlers UpAlert and DownAlert subscribed to PropertyChanged with anonymous lambdas, which cannot be detached. The rest of the indicator follows a symmetric +=/-= pattern in the setters of FilterColor / FilterInt properties; the alert subscriptions broke that symmetry. Promote the lambdas to named methods (OnUpAlertChanged / OnDownAlertChanged) so that, if UpAlert/DownAlert are ever reassigned through the UI the same way DivergenceBarsFilter is, the old handler can be properly unsubscribed. No behavioural change today. --- Technical/Delta.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Technical/Delta.cs b/Technical/Delta.cs index b596c001..8e0e84eb 100644 --- a/Technical/Delta.cs +++ b/Technical/Delta.cs @@ -591,9 +591,9 @@ public Delta() DataSeries.Add(_absorptionCandles); - UpAlert.PropertyChanged += (sender, e) => _lastBarAlert = 0; - DownAlert.PropertyChanged += (sender, e) => _lastBarNegativeAlert = 0; - _divergenceBarsFilter.PropertyChanged += OnDivergenceFilterChanged; + UpAlert.PropertyChanged += OnUpAlertChanged; + DownAlert.PropertyChanged += OnDownAlertChanged; + _divergenceBarsFilter.PropertyChanged += OnDivergenceFilterChanged; _absorption.PropertyChanged += OnAbsorptionFilterChanged; UpdateDivergenceCandlesVisibility(); @@ -1099,5 +1099,8 @@ private void UpdateDivergenceCandlesVisibility() } } - #endregion + private void OnUpAlertChanged(object sender, PropertyChangedEventArgs e) => _lastBarAlert = 0; + private void OnDownAlertChanged(object sender, PropertyChangedEventArgs e) => _lastBarNegativeAlert = 0; + + #endregion } \ No newline at end of file From d072846937588a12442c667456579ce0df410adc Mon Sep 17 00:00:00 2001 From: AlbertoAmadorBelchistim Date: Sat, 18 Apr 2026 08:28:37 +0200 Subject: [PATCH 4/5] chore(delta): remove unused _absorptionThreshold field and normalize style MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Drop the `_absorptionThreshold` field; it was declared but never read — the live value is always taken from `_absorption.Value`. - Replace the O(n) string-concat loop in GetMinWidth with `new string('0', maxLength)`. - Normalize indentation to tabs across the file. No functional change. --- Technical/Delta.cs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/Technical/Delta.cs b/Technical/Delta.cs index 8e0e84eb..c5c6f900 100644 --- a/Technical/Delta.cs +++ b/Technical/Delta.cs @@ -426,10 +426,6 @@ public Indicators.FilterColor DivergenceBarsFilter ShowCurrentValue = false }; -#pragma warning disable CS0414 - private int _absorptionThreshold = 250; -#pragma warning restore CS0414 - private FilterInt _absorption = new(true) { Enabled = false, Value = 250 }; [Display(ResourceType = typeof(Strings), Name = nameof(Strings.Absorption), GroupName = nameof(Strings.Absorption), @@ -591,9 +587,9 @@ public Delta() DataSeries.Add(_absorptionCandles); - UpAlert.PropertyChanged += OnUpAlertChanged; - DownAlert.PropertyChanged += OnDownAlertChanged; - _divergenceBarsFilter.PropertyChanged += OnDivergenceFilterChanged; + UpAlert.PropertyChanged += OnUpAlertChanged; + DownAlert.PropertyChanged += OnDownAlertChanged; + _divergenceBarsFilter.PropertyChanged += OnDivergenceFilterChanged; _absorption.PropertyChanged += OnAbsorptionFilterChanged; UpdateDivergenceCandlesVisibility(); @@ -1013,10 +1009,7 @@ private int GetMinWidth(RenderContext context, int startBar, int endBar) maxLength = length; } - var sampleStr = ""; - - for (var i = 0; i < maxLength; i++) - sampleStr += '0'; + var sampleStr = new string('0', maxLength); return context.MeasureString(sampleStr, Font.RenderObject).Width; } @@ -1099,8 +1092,8 @@ private void UpdateDivergenceCandlesVisibility() } } - private void OnUpAlertChanged(object sender, PropertyChangedEventArgs e) => _lastBarAlert = 0; - private void OnDownAlertChanged(object sender, PropertyChangedEventArgs e) => _lastBarNegativeAlert = 0; + private void OnUpAlertChanged(object sender, PropertyChangedEventArgs e) => _lastBarAlert = 0; + private void OnDownAlertChanged(object sender, PropertyChangedEventArgs e) => _lastBarNegativeAlert = 0; - #endregion + #endregion } \ No newline at end of file From c602d0b5b2c57fd034f0528651f02fe1fdc6e0ba Mon Sep 17 00:00:00 2001 From: AlbertoAmadorBelchistim Date: Sat, 18 Apr 2026 08:33:31 +0200 Subject: [PATCH 5/5] fix(Delta): clamp divergence dots to price panel top and bottom The divergence dot renderer only checked the bottom edge of the price panel region, so dots could be drawn above the top of the panel when the candle's Low/High projected off-screen (for example under strong zoom). Add a symmetric Top check and hoist the region lookup. --- Technical/Delta.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Technical/Delta.cs b/Technical/Delta.cs index c5c6f900..d386744e 100644 --- a/Technical/Delta.cs +++ b/Technical/Delta.cs @@ -632,12 +632,13 @@ protected override void OnRender(RenderContext context, DrawingLayouts layout) var candle = GetCandle(i); var x = ChartInfo.PriceChartContainer.GetXByBar(i, false); + var region = ChartInfo.PriceChartContainer.Region; if (_upSeries[i] != 0) { var yPrice = ChartInfo.PriceChartContainer.GetYByPrice(candle.Low, false) + 10; - if (yPrice <= ChartInfo.PriceChartContainer.Region.Bottom) + if (yPrice >= region.Top && yPrice <= region.Bottom) { var rect = new Rectangle(x - 5, yPrice - 4, 8, 8); context.FillEllipse(_upColor, rect); @@ -648,7 +649,7 @@ protected override void OnRender(RenderContext context, DrawingLayouts layout) { var yPrice = ChartInfo.PriceChartContainer.GetYByPrice(candle.High, false) - 10; - if (yPrice <= ChartInfo.PriceChartContainer.Region.Bottom) + if (yPrice >= region.Top && yPrice <= region.Bottom) { var rect = new Rectangle(x - 5, yPrice - 4, 8, 8); context.FillEllipse(_downColor, rect);