Fix: Track actual historical crossovers and force indicator redraw

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
This commit is contained in:
DiTus
2026-03-01 20:12:19 +01:00
parent 0df8547d96
commit 9513f5b426
2 changed files with 85 additions and 45 deletions

View File

@ -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() { 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 activeIndicators = getActiveIndicators();
const currentInterval = window.dashboard.currentInterval;
const candles = window.dashboard?.allData?.get(currentInterval);
if (!candles || candles.length === 0) return;
activeIndicators.forEach(indicator => { activeIndicators.forEach(indicator => {
if (!indicator.visible || indicator.series.length === 0) return; indicator.series?.forEach(s => {
try {
const IndicatorClass = IndicatorRegistry[indicator.type]; window.dashboard.chart.removeSeries(s);
if (!IndicatorClass) return; } catch(e) {
console.warn('[UpdateIndicators] Error removing series:', e);
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 = [];
}); });
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 // Chart drawing

View File

@ -87,6 +87,71 @@ export function calculateSummarySignal(signals) {
return result; 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 * Calculate signals for all active indicators
* @returns {Array} Array of indicator signals * @returns {Array} Array of indicator signals
@ -110,6 +175,9 @@ export function calculateAllIndicatorSignals() {
const signals = []; const signals = [];
// Calculate crossovers for all indicators based on historical data
calculateHistoricalCrossovers(activeIndicators, candles);
for (const indicator of activeIndicators) { for (const indicator of activeIndicators) {
const IndicatorClass = IndicatorRegistry[indicator.type]; const IndicatorClass = IndicatorRegistry[indicator.type];
if (!IndicatorClass) { if (!IndicatorClass) {