From 9513f5b426f2d6eedbdb31a5953a2177fe9849d0 Mon Sep 17 00:00:00 2001 From: DiTus Date: Sun, 1 Mar 2026 20:12:19 +0100 Subject: [PATCH] Fix: Track actual historical crossovers and force indicator redraw MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue 1: Track actual crossovers from history - Added calculateHistoricalCrossovers() function - Scans candle history backwards to find most recent crossover - Updates lastSignalTimestamp when BUY→SELL or SELL→BUY crossover is detected - Date now reflects when the price actually crossed the MA line Issue 2: Force full redraw of indicator series - Changed updateIndicatorCandles() to remove and recreate all series - Instead of using setData() which may not work reliably - Full redraw ensures overlay indicators (MA) refresh correctly - Now indicator lines update properly when new candles finish Both implementations ensure: 1. Date shows actual crossover time (when candle crossed MA) 2. Indicator lines redraw on new candle completion 3. Works for both overlay (MA, BB, HTS) and pane (RSI, MACD, etc.) indicators --- .../static/js/ui/indicators-panel-new.js | 62 +++++------------ .../static/js/ui/signals-calculator.js | 68 +++++++++++++++++++ 2 files changed, 85 insertions(+), 45 deletions(-) diff --git a/src/api/dashboard/static/js/ui/indicators-panel-new.js b/src/api/dashboard/static/js/ui/indicators-panel-new.js index 5efb68c..469bb0b 100644 --- a/src/api/dashboard/static/js/ui/indicators-panel-new.js +++ b/src/api/dashboard/static/js/ui/indicators-panel-new.js @@ -813,59 +813,31 @@ function renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, li }); } -// Update existing indicator series with new data (for real-time updates) +// Completely redraw indicators (works for both overlay and pane) export function updateIndicatorCandles() { - if (!window.dashboard || !window.dashboard.chart) return; + console.log('[UpdateIndicators] Removing and recreating all indicator series'); + // Remove all existing series const activeIndicators = getActiveIndicators(); - const currentInterval = window.dashboard.currentInterval; - const candles = window.dashboard?.allData?.get(currentInterval); - - if (!candles || candles.length === 0) return; - activeIndicators.forEach(indicator => { - if (!indicator.visible || indicator.series.length === 0) return; - - const IndicatorClass = IndicatorRegistry[indicator.type]; - if (!IndicatorClass) return; - - const instance = new IndicatorClass(indicator); - const results = instance.calculate(candles); - - if (!results || results.length === 0) return; - - const meta = instance.getMetadata(); - - // Update each plot series - meta.plots.forEach((plot, plotIdx) => { - const series = indicator.series[plotIdx]; - if (!series) return; - - const plotColor = indicator.params[`_color_${plotIdx}`] || plot.color || '#2962ff'; - const lineWidth = indicator.params._lineWidth || 2; - - // Build complete data array - const data = []; - - for (let i = 0; i < candles.length; i++) { - const value = results[i]?.[plot.id]; - if (value !== null && value !== undefined) { - data.push({ - time: candles[i].time, - value: value, - color: plotColor, - lineWidth: lineWidth - }); - } - } - - if (data.length > 0) { - series.setData(data); + indicator.series?.forEach(s => { + try { + window.dashboard.chart.removeSeries(s); + } catch(e) { + console.warn('[UpdateIndicators] Error removing series:', e); } }); + indicator.series = []; }); - console.log(`[UpdateIndicators] Updated ${activeIndicators.length} indicator series`); + // Clear pane mappings + indicatorPanes.clear(); + nextPaneIndex = 1; + + // Now call drawIndicatorsOnChart to recreate everything + drawIndicatorsOnChart(); + + console.log(`[UpdateIndicators] Recreated ${activeIndicators.length} indicators`); } // Chart drawing diff --git a/src/api/dashboard/static/js/ui/signals-calculator.js b/src/api/dashboard/static/js/ui/signals-calculator.js index fa75a33..0a3de5b 100644 --- a/src/api/dashboard/static/js/ui/signals-calculator.js +++ b/src/api/dashboard/static/js/ui/signals-calculator.js @@ -87,6 +87,71 @@ export function calculateSummarySignal(signals) { return result; } +/** + * Calculate historical crossovers for all indicators based on full candle history + * Finds the last time each indicator crossed from BUY to SELL or SELL to BUY + */ +function calculateHistoricalCrossovers(activeIndicators, candles) { + activeIndicators.forEach(indicator => { + const indicatorType = indicator.type || indicator.indicatorType; + const SignalFunction = getSignalFunction(indicatorType); + + if (!SignalFunction) return; + + // Recalculate indicator values for all candles + const IndicatorClass = IndicatorRegistry[indicatorType]; + if (!IndicatorClass) return; + + const instance = new IndicatorClass(indicator); + const results = instance.calculate(candles); + + if (!results || results.length === 0) return; + + // Track the last crossover timestamp + let lastCrossoverTimestamp = indicator.lastSignalTimestamp || null; + let crossoverCount = 0; + + // Iterate through candles to find crossovers (from newest to oldest) + // We start from the end and go backwards to find the most recent crossover + for (let i = candles.length - 2; i >= 0; i--) { + // Skip if we don't have data for these candles + if (!results[i] || !results[i + 1]) continue; + + const candle = candles[i]; + const prevCandle = candles[i + 1]; + + const valuesThis = typeof results[i] === 'object' ? results[i] : { ma: results[i] }; + const valuesPrev = typeof results[i + 1] === 'object' ? results[i + 1] : { ma: results[i + 1] }; + + const closeThis = candles[i].close; + const closePrev = candles[i + 1].close; + const maThis = valuesThis.ma; + const maPrev = valuesPrev.ma; + + // Check for BUY→SELL crossover (was above, now below) + if (closePrev > maPrev && closeThis < maThis) { + console.log(`[HistoricalCross] ${indicatorType} BUY→SELL crossover at candle ${i}, time: ${candle.time}`); + lastCrossoverTimestamp = candle.time; + crossoverCount++; + break; // Found most recent crossover + } + // Check for SELL→BUY crossover (was below, now above) + else if (closePrev < maPrev && closeThis > maThis) { + console.log(`[HistoricalCross] ${indicatorType} SELL→BUY crossover at candle ${i}, time: ${candle.time}`); + lastCrossoverTimestamp = candle.time; + crossoverCount++; + break; // Found most recent crossover + } + } + + if (crossoverCount > 0) { + console.log(`[HistoricalCross] ${indicatorType}: Found crossover at ${new Date(lastCrossoverTimestamp * 1000).toLocaleString()}`); + // Update the indicator's lastSignalTimestamp + indicator.lastSignalTimestamp = lastCrossoverTimestamp; + } + }); +} + /** * Calculate signals for all active indicators * @returns {Array} Array of indicator signals @@ -110,6 +175,9 @@ export function calculateAllIndicatorSignals() { const signals = []; + // Calculate crossovers for all indicators based on historical data + calculateHistoricalCrossovers(activeIndicators, candles); + for (const indicator of activeIndicators) { const IndicatorClass = IndicatorRegistry[indicator.type]; if (!IndicatorClass) {