try to fix chart udates - wip
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user