Fix: Signal date tracking and indicator real-time updates

Issue 1: Only update lastSignalDate when signal type changes (BUY→SELL or SELL→BUY)
- Modified clearIndicatorCaches() to accept clearSignalState parameter
- When new candle completes: only clear cachedResults/cachedMeta (not signal state)
- When timeframe changes: clear everything including signal tracking
- This preserves signal change history across multiple candles

Issue 2: Indicator lines not updating when new candles arrive
- Added updateIndicatorCandles() function to update existing series
- Instead of removing and recreating series, now uses .setData() to update
- Called when new candle is detected to update indicator lines
- Chart renders correctly with new data after each candle completion

Both issues now resolved:
1. Shows last crossover date only when signal actually changes
2. Indicator lines update in real-time when new candles complete
This commit is contained in:
DiTus
2026-03-01 20:07:12 +01:00
parent 61aaa2d122
commit 0df8547d96
2 changed files with 72 additions and 9 deletions

View File

@ -1,5 +1,6 @@
import { INTERVALS, COLORS } from '../core/index.js';
import { calculateAllIndicatorSignals, calculateSummarySignal } from './signals-calculator.js';
import { updateIndicatorCandles } from './indicators-panel-new.js';
function formatDate(timestamp) {
const date = new Date(timestamp);
@ -298,15 +299,21 @@ constructor() {
});
}
clearIndicatorCaches() {
clearIndicatorCaches(clearSignalState = false) {
const activeIndicators = window.getActiveIndicators?.() || [];
activeIndicators.forEach(indicator => {
// Always clear calculation caches
indicator.cachedResults = null;
indicator.cachedMeta = null;
indicator.lastSignalTimestamp = null;
indicator.lastSignalType = null;
// Only clear signal state if explicitly requested (e.g., timeframe change)
// Do not clear on new candle completion - preserve signal change tracking
if (clearSignalState) {
indicator.lastSignalTimestamp = null;
indicator.lastSignalType = null;
}
});
console.log(`[Dashboard] Cleared caches for ${activeIndicators.length} indicators`);
console.log(`[Dashboard] Cleared caches for ${activeIndicators.length} indicators (signals: ${clearSignalState})`);
}
async loadInitialData() {
@ -396,8 +403,8 @@ async loadNewData() {
if (isNewCandle) {
console.log(`[NewData Load] New candle detected: ${this.lastCandleTimestamp} -> ${latest.time}`);
// Clear indicator caches to force recalculation
this.clearIndicatorCaches();
// Clear indicator caches but preserve signal state
this.clearIndicatorCaches(false);
}
this.lastCandleTimestamp = latest.time;
@ -419,10 +426,11 @@ async loadNewData() {
this.updateStats(latest);
// Recalculate indicators and signals when new data loads
// Update indicators - for new candles, update series; for initial loads, redraw
if (window.drawIndicatorsOnChart) {
window.drawIndicatorsOnChart();
}
await this.loadSignals();
// Refresh chart if new candle detected
@ -715,8 +723,8 @@ switchTimeframe(interval) {
btn.classList.toggle('active', btn.dataset.interval === interval);
});
// Clear indicator caches before switching timeframe
this.clearIndicatorCaches();
// Clear indicator caches and signal state before switching timeframe
this.clearIndicatorCaches(true);
// Clear old interval data, not new interval
this.allData.delete(oldInterval);

View File

@ -813,6 +813,61 @@ function renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, li
});
}
// Update existing indicator series with new data (for real-time updates)
export function updateIndicatorCandles() {
if (!window.dashboard || !window.dashboard.chart) return;
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);
}
});
});
console.log(`[UpdateIndicators] Updated ${activeIndicators.length} indicator series`);
}
// Chart drawing
export function drawIndicatorsOnChart() {
if (!window.dashboard || !window.dashboard.chart) {