try to fix chart udates - wip

This commit is contained in:
Dobromir Popov
2025-12-10 11:58:53 +02:00
parent 1d49269301
commit c7a37bf5f0
9 changed files with 364 additions and 170 deletions

View File

@@ -548,9 +548,17 @@ class ChartManager {
*/
updateLatestCandle(symbol, timeframe, candle) {
try {
console.log(`[updateLatestCandle] Called for ${timeframe}:`, {
symbol: symbol,
timestamp: candle.timestamp,
is_confirmed: candle.is_confirmed,
hasChart: !!this.charts[timeframe],
availableCharts: Object.keys(this.charts)
});
const chart = this.charts[timeframe];
if (!chart) {
console.debug(`Chart ${timeframe} not found for live update`);
console.warn(`[updateLatestCandle] Chart ${timeframe} not found for live update. Available charts:`, Object.keys(this.charts));
return;
}
@@ -558,7 +566,7 @@ class ChartManager {
const plotElement = document.getElementById(plotId);
if (!plotElement) {
console.debug(`Plot element ${plotId} not found`);
console.warn(`[updateLatestCandle] Plot element ${plotId} not found in DOM`);
return;
}
@@ -575,11 +583,11 @@ class ChartManager {
}
// CRITICAL FIX: Parse timestamp ensuring UTC handling
// Backend now sends ISO format with 'Z' (e.g., '2025-12-08T21:00:00Z')
// Backend now sends ISO format with timezone (e.g., '2025-12-10T09:19:51+00:00')
// JavaScript Date will parse this correctly as UTC
let candleTimestamp;
if (typeof candle.timestamp === 'string') {
// If it's already ISO format with 'Z', parse directly
// If it's already ISO format with 'Z' or timezone offset, parse directly
if (candle.timestamp.includes('T') && (candle.timestamp.endsWith('Z') || candle.timestamp.includes('+'))) {
candleTimestamp = new Date(candle.timestamp);
} else if (candle.timestamp.includes('T')) {
@@ -593,6 +601,12 @@ class ChartManager {
candleTimestamp = new Date(candle.timestamp);
}
// Validate timestamp
if (isNaN(candleTimestamp.getTime())) {
console.error(`[${timeframe}] Invalid timestamp: ${candle.timestamp}`);
return;
}
// Format using UTC methods and ISO format with 'Z' for consistency
const year = candleTimestamp.getUTCFullYear();
const month = String(candleTimestamp.getUTCMonth() + 1).padStart(2, '0');
@@ -604,65 +618,86 @@ class ChartManager {
const formattedTimestamp = `${year}-${month}-${day}T${hours}:${minutes}:${seconds}Z`;
// Get current chart data from Plotly
const chartData = Plotly.Plots.data(plotId);
const chartData = plotElement.data;
if (!chartData || chartData.length < 2) {
console.debug(`Chart ${plotId} not initialized yet`);
console.warn(`[updateLatestCandle] Chart ${plotId} not initialized yet (no data traces)`);
return;
}
const candlestickTrace = chartData[0];
const volumeTrace = chartData[1];
// Ensure we have valid trace data
if (!candlestickTrace || !candlestickTrace.x || candlestickTrace.x.length === 0) {
console.warn(`[updateLatestCandle] Candlestick trace has no data for ${timeframe}`);
return;
}
console.log(`[updateLatestCandle] Chart ${timeframe} has ${candlestickTrace.x.length} candles currently`);
// CRITICAL FIX: Check is_confirmed flag first
// If candle is confirmed, it's a NEW completed candle (not an update to the current one)
const isConfirmed = candle.is_confirmed === true;
// Check if this is updating the last candle or adding a new one
// Use more lenient comparison to handle timestamp format differences
const lastTimestamp = candlestickTrace.x[candlestickTrace.x.length - 1];
const lastTimeMs = lastTimestamp ? new Date(lastTimestamp).getTime() : 0;
const candleTimeMs = candleTimestamp.getTime();
// Consider it a new candle if timestamp is at least 500ms newer (to handle jitter)
const isNewCandle = !lastTimestamp || (candleTimeMs - lastTimeMs) >= 500;
if (isNewCandle) {
// Add new candle - update both Plotly and internal data structure
Plotly.extendTraces(plotId, {
x: [[formattedTimestamp]],
open: [[candle.open]],
high: [[candle.high]],
low: [[candle.low]],
close: [[candle.close]]
}, [0]);
// Update volume color based on price direction
const volumeColor = candle.close >= candle.open ? '#10b981' : '#ef4444';
Plotly.extendTraces(plotId, {
x: [[formattedTimestamp]],
y: [[candle.volume]],
marker: { color: [[volumeColor]] }
}, [1]);
// Update internal data structure
chart.data.timestamps.push(formattedTimestamp);
chart.data.open.push(candle.open);
chart.data.high.push(candle.high);
chart.data.low.push(candle.low);
chart.data.close.push(candle.close);
chart.data.volume.push(candle.volume);
console.log(`[${timeframe}] Added new candle: ${formattedTimestamp}`, {
// Determine if this is a new candle:
// 1. If no last timestamp exists, it's always new
// 2. If timestamp is significantly newer (at least 1 second for 1s, or timeframe period for others)
// 3. If confirmed AND timestamp is different, it's a new candle
// 4. If confirmed AND timestamp matches, we REPLACE the last candle (it was forming, now confirmed)
let timeframePeriodMs = 1000; // Default 1 second
if (timeframe === '1m') timeframePeriodMs = 60000;
else if (timeframe === '1h') timeframePeriodMs = 3600000;
else if (timeframe === '1d') timeframePeriodMs = 86400000;
// Check if timestamps match (within 1 second tolerance)
const timestampMatches = lastTimestamp && Math.abs(candleTimeMs - lastTimeMs) < 1000;
// If confirmed and timestamp matches, we replace the last candle (it was forming, now confirmed)
// Otherwise, if timestamp is newer or confirmed with different timestamp, it's a new candle
const isNewCandle = !lastTimestamp ||
(isConfirmed && !timestampMatches) ||
(!isConfirmed && (candleTimeMs - lastTimeMs) >= timeframePeriodMs);
// Special case: if confirmed and timestamp matches, we update the last candle (replace forming with confirmed)
const shouldReplaceLast = isConfirmed && timestampMatches && lastTimestamp;
if (shouldReplaceLast) {
// Special case: Confirmed candle with same timestamp - replace the last candle (forming -> confirmed)
console.log(`[${timeframe}] REPLACING last candle (forming -> confirmed): ${formattedTimestamp}`, {
timestamp: candle.timestamp,
open: candle.open,
high: candle.high,
low: candle.low,
close: candle.close,
volume: candle.volume
});
} else {
// Update last candle - update both Plotly and internal data structure
// Use the same update logic as updating existing candle
const x = [...candlestickTrace.x];
const open = [...candlestickTrace.open];
const high = [...candlestickTrace.high];
const low = [...candlestickTrace.low];
const close = [...candlestickTrace.close];
const volume = [...volumeTrace.y];
const colors = Array.isArray(volumeTrace.marker.color) ? [...volumeTrace.marker.color] : [volumeTrace.marker.color];
// Handle volume colors
let colors;
if (Array.isArray(volumeTrace.marker.color)) {
colors = [...volumeTrace.marker.color];
} else if (volumeTrace.marker && volumeTrace.marker.color) {
colors = new Array(volume.length).fill(volumeTrace.marker.color);
} else {
colors = volume.map((v, i) => {
if (i === 0) return '#3b82f6';
return close[i] >= open[i] ? '#10b981' : '#ef4444';
});
}
const lastIdx = x.length - 1;
@@ -700,7 +735,141 @@ class ChartManager {
chart.data.volume[lastIdx] = candle.volume;
}
console.log(`[${timeframe}] Updated last candle: ${formattedTimestamp}`);
console.log(`[${timeframe}] Successfully replaced last candle (confirmed)`);
} else if (isNewCandle) {
// Add new candle - update both Plotly and internal data structure
console.log(`[${timeframe}] Adding NEW candle (confirmed: ${isConfirmed}): ${formattedTimestamp}`, {
timestamp: candle.timestamp,
formattedTimestamp: formattedTimestamp,
open: candle.open,
high: candle.high,
low: candle.low,
close: candle.close,
volume: candle.volume,
lastTimestamp: lastTimestamp,
timeDiff: lastTimestamp ? (candleTimeMs - lastTimeMs) + 'ms' : 'N/A',
currentCandleCount: candlestickTrace.x.length
});
try {
// CRITICAL: Plotly.extendTraces expects arrays of arrays
// Each trace gets an array, and each array contains the new data points
Plotly.extendTraces(plotId, {
x: [[formattedTimestamp]],
open: [[candle.open]],
high: [[candle.high]],
low: [[candle.low]],
close: [[candle.close]]
}, [0]).then(() => {
console.log(`[${timeframe}] Candlestick trace extended successfully`);
}).catch(err => {
console.error(`[${timeframe}] Error extending candlestick trace:`, err);
});
// Update volume color based on price direction
const volumeColor = candle.close >= candle.open ? '#10b981' : '#ef4444';
Plotly.extendTraces(plotId, {
x: [[formattedTimestamp]],
y: [[candle.volume]],
marker: { color: [[volumeColor]] }
}, [1]).then(() => {
console.log(`[${timeframe}] Volume trace extended successfully`);
}).catch(err => {
console.error(`[${timeframe}] Error extending volume trace:`, err);
});
// Update internal data structure
chart.data.timestamps.push(formattedTimestamp);
chart.data.open.push(candle.open);
chart.data.high.push(candle.high);
chart.data.low.push(candle.low);
chart.data.close.push(candle.close);
chart.data.volume.push(candle.volume);
console.log(`[${timeframe}] Successfully added new candle. Total candles: ${chart.data.timestamps.length}`);
} catch (error) {
console.error(`[${timeframe}] Error adding new candle:`, error);
}
} else {
// Update last candle - update both Plotly and internal data structure
console.log(`[${timeframe}] Updating EXISTING candle: ${formattedTimestamp}`, {
timestamp: candle.timestamp,
open: candle.open,
high: candle.high,
low: candle.low,
close: candle.close,
volume: candle.volume,
lastTimestamp: lastTimestamp,
timeDiff: (candleTimeMs - lastTimeMs) + 'ms'
});
const x = [...candlestickTrace.x];
const open = [...candlestickTrace.open];
const high = [...candlestickTrace.high];
const low = [...candlestickTrace.low];
const close = [...candlestickTrace.close];
const volume = [...volumeTrace.y];
// Handle volume colors - ensure it's an array
let colors;
if (Array.isArray(volumeTrace.marker.color)) {
colors = [...volumeTrace.marker.color];
} else if (volumeTrace.marker && volumeTrace.marker.color) {
// Single color - convert to array
colors = new Array(volume.length).fill(volumeTrace.marker.color);
} else {
// No color - create default array
colors = volume.map((v, i) => {
if (i === 0) return '#3b82f6';
return close[i] >= open[i] ? '#10b981' : '#ef4444';
});
}
const lastIdx = x.length - 1;
// Update local arrays
x[lastIdx] = formattedTimestamp;
open[lastIdx] = candle.open;
high[lastIdx] = candle.high;
low[lastIdx] = candle.low;
close[lastIdx] = candle.close;
volume[lastIdx] = candle.volume;
colors[lastIdx] = candle.close >= candle.open ? '#10b981' : '#ef4444';
// Push updates to Plotly
Plotly.restyle(plotId, {
x: [x],
open: [open],
high: [high],
low: [low],
close: [close]
}, [0]);
Plotly.restyle(plotId, {
x: [x],
y: [volume],
'marker.color': [colors]
}, [1]);
// Update internal data structure
if (chart.data.timestamps.length > lastIdx) {
chart.data.timestamps[lastIdx] = formattedTimestamp;
chart.data.open[lastIdx] = candle.open;
chart.data.high[lastIdx] = candle.high;
chart.data.low[lastIdx] = candle.low;
chart.data.close[lastIdx] = candle.close;
chart.data.volume[lastIdx] = candle.volume;
} else {
// If internal data is shorter, append
chart.data.timestamps.push(formattedTimestamp);
chart.data.open.push(candle.open);
chart.data.high.push(candle.high);
chart.data.low.push(candle.low);
chart.data.close.push(candle.close);
chart.data.volume.push(candle.volume);
}
console.log(`[${timeframe}] Successfully updated last candle`);
}
// CRITICAL: Check if we have enough candles to validate predictions (2s delay logic)
@@ -2024,10 +2193,7 @@ class ChartManager {
plotElement.style.height = `${chartHeight}px`;
// Trigger Plotly resize
const plotId = plotElement.id;
if (plotId) {
Plotly.Plots.resize(plotId);
}
Plotly.Plots.resize(plotElement);
}
});
} else {
@@ -2040,10 +2206,7 @@ class ChartManager {
plotElement.style.height = '300px';
// Trigger Plotly resize
const plotId = plotElement.id;
if (plotId) {
Plotly.Plots.resize(plotId);
}
Plotly.Plots.resize(plotElement);
}
});
}
@@ -3087,11 +3250,11 @@ class ChartManager {
console.log(`[updatePredictions] Timeframe: ${timeframe}, Predictions:`, predictions);
const plotId = chart.plotId;
const plotElement = document.getElementById(plotId);
if (!plotElement) return;
const chartElement = document.getElementById(plotId);
if (!chartElement) return;
// Get current chart data
const chartData = plotElement.data;
const chartData = chartElement.data;
if (!chartData || chartData.length < 2) return;
// Prepare prediction markers