Skip to content

fix(delta): MinimizedMode correctness and alert handler cleanup#164

Open
AlbertoAmadorBelchistim wants to merge 5 commits into
AtasPlatform:Developfrom
AlbertoAmadorBelchistim:fix/delta-minimized-mode-correctness
Open

fix(delta): MinimizedMode correctness and alert handler cleanup#164
AlbertoAmadorBelchistim wants to merge 5 commits into
AtasPlatform:Developfrom
AlbertoAmadorBelchistim:fix/delta-minimized-mode-correctness

Conversation

@AlbertoAmadorBelchistim
Copy link
Copy Markdown
Contributor

@AlbertoAmadorBelchistim AlbertoAmadorBelchistim commented Apr 18, 2026

Summary

  • Fix: clear the opposite divergence series in OnCalculate (MinimizedMode) so that flipping divergence direction on the same bar between two divergent states (without passing through the non-divergent else branch in between) no longer leaves a stale dot from the previous direction. Realistic only on the live last bar during real-time recalculation.
  • Fix: clamp divergence dots in OnRender to both the top and bottom of the price panel. The previous guard only checked Region.Bottom, so under strong vertical zoom the dot could render above Region.Top.
  • Fix: align GetMinWidth with OnRender for negative-delta bars in MinimizedMode. The old expression read _candles[i] for the negative branch, but _candles[bar] is reset to new Candle() in that branch of OnCalculate, so width was measured as 0 and the volume-label width gate (minWidth > barWidth) failed to suppress labels that did not fit.
  • Refactor: replace the anonymous PropertyChanged lambdas wired to UpAlert / DownAlert with named handlers (OnUpAlertChanged / OnDownAlertChanged), matching the += / -= symmetry already used for DivergenceBarsFilter and Absorption. No behavioural change.
  • Style / cleanup: remove unused _absorptionThreshold field (and its #pragma warning disable CS0414), replace a concat loop with new string('0', maxLength) in GetMinWidth, and normalize tabs / spaces.

No behavioural changes outside the three bug fixes.

Changes

Commit Area Visibility
fix(delta): clear opposite divergence series in MinimizedMode OnCalculate (MinimizedMode branch) User-visible — no more ghost dot when divergence direction flips on the live bar
fix(delta): clamp divergence dots to price panel top and bottom OnRender User-visible — dots no longer draw above the panel under vertical zoom
fix(delta): align GetMinWidth with OnRender for negative delta in MinimizedMode GetMinWidth User-visible — volume-label width gate now fires correctly for negative-delta bars
refactor(delta): replace anonymous PropertyChanged lambdas with named handlers ctor / event handlers No behavioural change
style(delta): use string constructor and normalize indentation GetMinWidth + whole-file whitespace No functional change

Test plan

Smoke-tested on a liquid instrument with active order flow on the flavors the indicator targets.

Bug-fix verification

  • Ghost divergence dot — Mode = Candles, MinimizedMode = on, ShowDivergence = on, DivergenceBarsFilter.Enabled = on. On the live last bar, induce ticks that flip divergence direction between two divergent states without going through a non-divergent state (e.g. bullish-candle/bearish-delta → bearish-candle/bullish-delta). After the flip, only one divergence dot is visible on that bar; the previous-direction dot disappears.
  • Same flip in Mode = Bars, MinimizedMode = on — identical expectation.
  • Volume-label width gate — negative-delta bar. ShowVolume = on, clusters chart, MinimizedMode = on. On a chart zoomed so the bar is narrower than the rendered label width: a bar with negative delta no longer draws an oversized label that overflows the bar. With wide bars: label is drawn and fits inside the bar (no false suppression).
  • Divergence dot clamping — top edge. ShowDivergence = on. Apply strong vertical zoom so that an up-divergence bar's candle.Low projects to a yPrice above Region.Top (i.e. GetYByPrice(candle.Low) + 10 < Region.Top): the dot is not drawn outside the panel.
  • Divergence dot clamping — bottom edge. Same setup, this time forcing yPrice below Region.Bottom: dot is not drawn (regression check; this case already worked before).

Regressions on adjacent surfaces

  • Volume label, MinimizedMode = offShowVolume = on, clusters: width gate behaves as before (no regression from the GetMinWidth change; the non-MinimizedMode branch was untouched).
  • Alerts after refactor. UpAlert.Enabled = on, Value = X and DownAlert.Enabled = on, Value = -Y: each alert fires once per bar on threshold crossing. Change UpAlert.Value / DownAlert.Value at runtime → _lastBarAlert / _lastBarNegativeAlert reset and the alert can re-trigger after the new threshold is crossed (this is the behaviour the lambdas guaranteed; the named handlers must keep it).
  • DivergenceBarsFilter.Enabled toggled at runtime — colors and divergence-candles visibility update as before (untouched in this PR).
  • Absorption.Enabled toggled at runtime — absorption dots appear / disappear as before (untouched in this PR).

Mode coverage (regression only)

  • Mode = Histogram, MinimizedMode = on/off — divergence coloring on the histogram via _delta.Colors[bar] works as before.
  • Mode = HighLow, MinimizedMode = on/off — delta histogram (_delta) plus the _diapasonHigh / _diapasonLow whiskers (max / min delta of the bar) render and colorize as before. With MinimizedMode = on the two whiskers collapse onto the positive side because both go through Math.Abs.
  • Mode = Candles and Mode = Bars, MinimizedMode = off — divergence renders via _divergenceCandles / _divergenceDownCandles (this branch of OnCalculate was not touched).

Notes

  • Files touched: Technical/Delta.cs only.
  • No public API changes; hidden backward-compat properties (ShowAbsorptionDots, AbsorptionDeltaThreshold, UseAlerts, AlertFilter, UseNegativeAlerts, NegativeAlertFilter) are untouched.
  • The style(delta) commit produces a noisy whole-file diff because of tabs / spaces normalization in regions that previously mixed both. Reviewing it on its own commit, or with ?w=1 / "Ignore whitespace", makes the actual functional change in GetMinWidth (new string('0', maxLength)) trivial to verify.

Out of scope - filter toggle immediate apply

Smoke-testing surfaced that toggling DivergenceBarsFilter.Enabled / Absorption.Enabled (or changing their values) in the indicator settings does not visually apply until either a new bar opens or the user zooms / scrolls the chart. Ticks on the live bar are not enough on their own. The historical bars do update correctly once any of those events happen,
but plain UI interaction with the filter alone is not.

This is pre-existing behaviour, not introduced by this PR, and was already present in the original OnAbsorptionFilterChanged / OnDivergenceFilterChanged handlers prior to any change here.

A focused investigation on a debug branch ruled out the obvious causes:

  • The PropertyChanged handler does run and RedrawChart() returns cleanly. OnRender is invoked immediately afterwards (verified via per-call logging).
  • Adding RecalculateValues() from the handler triggers a full OnCalculate(0..CurrentBar) pass (verified via per-bar logging), which itself repopulates _divergenceCandles[bar] / _absorptionCandles[bar] inline. The orange divergence candles still do not appear.
  • Manually re-populating the candle series for every historical bar via helpers called from the handler — without going through RecalculateValues() - also has no visible effect.
  • Subscribing to the public BaseDataSeries<T>.Changed event from the indicator constructor shows that it does not fire on indexer mutations in this scenario, neither from outside OnCalculate nor during a RecalculateValues-driven pass. The per-bar invalidation channel documented in the SDK is therefore unreachable from the indicator side.
  • CandleDataSeries is sealed, so exposing the protected RaiseChanged(int bar) of BaseDataSeries<T> via a subclass is not an option either.
  • Evidence that the data path itself is healthy: a custom OnRender overlay drawing a marker for each bar where the candle series is non-empty shows the marker immediately on toggle. The data is being written; ATAS's CandleDataSeries painter is what does not pick up the change.
  • For comparison, mutating series-level properties from an indicator property setter - such as _candles.UpCandleColor = value inside the UpColor setter - does cause an immediate visual update on
    the existing candles. The repaint mechanism that works for property-driven changes on a series invoked from an indicator property setter does not appear to extend to indexer-driven changes applied from a sub-object's PropertyChanged handler.
  • Stronger evidence of the same split: if the user toggles DivergenceBarsFilter.Enabled first (which populates
    _divergenceCandles[bar] correctly but does not repaint) and then toggles ShowDivergence (an unrelated public bool ShowDivergence { get; set; } auto-property on the indicator that only governs custom dot drawing in OnRender), the orange divergence candles appear immediately. ShowDivergence does not touch
    _divergenceCandles at all — its toggle simply goes through the indicator-property-setter pipeline, which forces a repaint that reads the already-populated indexer data. This isolates the problem to the repaint pathway, not to data freshness or render correctness.

This appears to be a limitation of the public SDK surface rather than something this indicator can fix from the outside. The clean fix belongs at the SDK level - for example, exposing a public per-bar invalidation hook on BaseDataSeries<T>, or having the indexer setter fire the existing Changed event so the public channel
becomes usable from indicator code. Flagged here for upstream attention, not folded into this PR.

@AlbertoAmadorBelchistim AlbertoAmadorBelchistim force-pushed the fix/delta-minimized-mode-correctness branch from 7e6282b to 6074555 Compare April 24, 2026 08:16
@AlbertoAmadorBelchistim
Copy link
Copy Markdown
Contributor Author

I've included yesterday's changes in this branch to resolve conflicts.

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.
…imizedMode

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`.
… 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.
…style

- 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.
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.
@AlbertoAmadorBelchistim AlbertoAmadorBelchistim force-pushed the fix/delta-minimized-mode-correctness branch from 6074555 to c602d0b Compare April 29, 2026 13:03
@AlbertoAmadorBelchistim
Copy link
Copy Markdown
Contributor Author

During smoke testing, I identified an issue where the filter toggle doesn't apply immediately. I’ve confirmed this is a pre-existing upstream behavior and not caused by this PR. I've added it to the notes, as I haven't yet found a viable fix after debugging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant