more acc stats and predictions storage. chart optimisation

This commit is contained in:
Dobromir Popov
2025-12-10 11:02:36 +02:00
parent 2fea288f62
commit 1d49269301
4 changed files with 229 additions and 12 deletions

View File

@@ -15,12 +15,17 @@ class ChartManager {
this.lastPredictionUpdate = {}; // Track last prediction update per timeframe
this.predictionUpdateThrottle = 500; // Min ms between prediction updates
this.lastPredictionHash = null; // Track if predictions actually changed
this.ghostCandleHistory = {}; // Store ghost candles per timeframe (max 50 each)
this.ghostCandleHistory = {}; // Store ghost candles per timeframe (max 150 each)
this.maxGhostCandles = 150; // Maximum number of ghost candles to keep
this.modelAccuracyMetrics = {}; // Track overall model accuracy per timeframe
this.predictionHistory = []; // Store last 20 predictions with fading
this.maxPredictions = 20; // Maximum number of predictions to display
// PERFORMANCE: Debounced updates and batching
this.pendingUpdates = {};
this.updateDebounceMs = 100; // 100ms debounce for chart updates
this.batchSize = 10; // Max traces to add in one batch
// Prediction display toggles (all enabled by default)
this.displayToggles = {
ghostCandles: true,
@@ -182,6 +187,14 @@ class ChartManager {
}, 1000);
}
// PERFORMANCE: Periodic cleanup every 30 seconds
this.cleanupTimer = setInterval(() => {
this._performPeriodicCleanup();
}, 30000); // 30 seconds
// PERFORMANCE: Optimize Plotly rendering
this._optimizePlotlyConfig();
console.log('Auto-update enabled for:', Object.keys(this.updateTimers));
}
@@ -199,9 +212,135 @@ class ChartManager {
Object.values(this.updateTimers).forEach(timer => clearInterval(timer));
this.updateTimers = {};
// Clear cleanup timer
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = null;
}
console.log('Auto-update stopped');
}
/**
* Periodic cleanup to prevent memory bloat and chart lag
*/
_performPeriodicCleanup() {
console.log('[Cleanup] Starting periodic cleanup...');
// Clean up ghost candles
Object.keys(this.ghostCandleHistory).forEach(timeframe => {
if (this.ghostCandleHistory[timeframe]) {
const before = this.ghostCandleHistory[timeframe].length;
this.ghostCandleHistory[timeframe] = this.ghostCandleHistory[timeframe].slice(-this.maxGhostCandles);
const after = this.ghostCandleHistory[timeframe].length;
if (before > after) {
console.log(`[Cleanup] ${timeframe}: Removed ${before - after} old ghost candles`);
}
}
});
// Clean up prediction history
if (this.predictionHistory.length > this.maxPredictions) {
const before = this.predictionHistory.length;
this.predictionHistory = this.predictionHistory.slice(-this.maxPredictions);
console.log(`[Cleanup] Removed ${before - this.predictionHistory.length} old predictions`);
}
// Clean up chart traces (remove old prediction traces)
Object.keys(this.charts).forEach(timeframe => {
const chart = this.charts[timeframe];
if (chart && chart.element) {
const plotElement = document.getElementById(chart.plotId);
if (plotElement && plotElement.data) {
const traces = plotElement.data;
// Keep only first 2 traces (candlestick + volume) and last 10 prediction traces
if (traces.length > 12) {
const keepTraces = traces.slice(0, 2).concat(traces.slice(-10));
const removed = traces.length - keepTraces.length;
if (removed > 0) {
console.log(`[Cleanup] ${timeframe}: Removed ${removed} old chart traces`);
Plotly.react(chart.plotId, keepTraces, plotElement.layout, plotElement.config);
}
}
}
}
});
console.log('[Cleanup] Periodic cleanup completed');
}
/**
* PERFORMANCE: Debounced chart update to prevent excessive redraws
*/
_debouncedChartUpdate(timeframe, updateFn) {
// Clear existing timeout for this timeframe
if (this.pendingUpdates[timeframe]) {
clearTimeout(this.pendingUpdates[timeframe]);
}
// Set new timeout
this.pendingUpdates[timeframe] = setTimeout(() => {
updateFn();
delete this.pendingUpdates[timeframe];
}, this.updateDebounceMs);
}
/**
* PERFORMANCE: Batch trace operations to reduce Plotly calls
*/
_batchAddTraces(plotId, traces) {
if (traces.length === 0) return;
// Add traces in batches to prevent UI blocking
const batches = [];
for (let i = 0; i < traces.length; i += this.batchSize) {
batches.push(traces.slice(i, i + this.batchSize));
}
// Add batches with small delays to keep UI responsive
batches.forEach((batch, index) => {
setTimeout(() => {
Plotly.addTraces(plotId, batch);
}, index * 10); // 10ms delay between batches
});
}
/**
* PERFORMANCE: Optimize Plotly configuration for better performance
*/
_optimizePlotlyConfig() {
// Set global Plotly config for better performance
if (typeof Plotly !== 'undefined') {
Plotly.setPlotConfig({
// Reduce animation for better performance
plotGlPixelRatio: 1,
// Use faster rendering
staticPlot: false,
// Optimize for frequent updates
responsive: true
});
}
}
/**
* PERFORMANCE: Check if element is visible in viewport
*/
_isElementVisible(element) {
if (!element) return false;
const rect = element.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
const windowWidth = window.innerWidth || document.documentElement.clientWidth;
// Element is visible if any part is in viewport
return (
rect.bottom > 0 &&
rect.right > 0 &&
rect.top < windowHeight &&
rect.left < windowWidth
);
}
/**
* Update a single chart with fresh data
*/
@@ -359,12 +498,12 @@ class ChartManager {
});
}
// CRITICAL: Preserve all historical candles - never truncate below 2500
// Only keep last 2500 candles if we exceed that limit (to prevent memory issues)
const maxCandles = 2500;
// PERFORMANCE: Limit to 1200 candles for responsive UI
// Keep only last 1200 candles to prevent memory issues and chart lag
const maxCandles = 1200;
if (chart.data.timestamps.length > maxCandles) {
const excess = chart.data.timestamps.length - maxCandles;
console.log(`[${timeframe}] Truncating ${excess} old candles (keeping last ${maxCandles})`);
console.log(`[${timeframe}] Truncating ${excess} old candles (keeping last ${maxCandles} for performance)`);
chart.data.timestamps = chart.data.timestamps.slice(-maxCandles);
chart.data.open = chart.data.open.slice(-maxCandles);
chart.data.high = chart.data.high.slice(-maxCandles);
@@ -2759,9 +2898,9 @@ class ChartManager {
Plotly.deleteTraces(plotId, indicesToRemove);
}
// Add updated traces
// PERFORMANCE: Use batched trace addition
if (predictionTraces.length > 0) {
Plotly.addTraces(plotId, predictionTraces);
this._batchAddTraces(plotId, predictionTraces);
console.log(`[${timeframe}] Refreshed ${predictionTraces.length} prediction candles with updated accuracy`);
}
}
@@ -2892,8 +3031,10 @@ class ChartManager {
console.log(`[updatePredictions] Drawing predictions on primary timeframe: ${primaryTimeframe}`);
// Update only the primary timeframe
this._updatePredictionsForTimeframe(primaryTimeframe, predictions);
// PERFORMANCE: Use debounced update to prevent excessive redraws
this._debouncedChartUpdate(primaryTimeframe, () => {
this._updatePredictionsForTimeframe(primaryTimeframe, predictions);
});
} catch (error) {
console.error('[updatePredictions] Error:', error);
@@ -2912,6 +3053,13 @@ class ChartManager {
console.debug(`[updatePredictions] Chart not found for timeframe: ${timeframe}`);
return;
}
// PERFORMANCE: Only update visible charts
const plotElement = document.getElementById(chart.plotId);
if (!plotElement || !this._isElementVisible(plotElement)) {
console.debug(`[updatePredictions] Chart ${timeframe} not visible, skipping update`);
return;
}
// Throttle prediction updates to avoid flickering
const now = Date.now();
@@ -3175,9 +3323,10 @@ class ChartManager {
return;
}
// Add new traces - these will overlay on top of real candles
// Plotly renders traces in order, so predictions added last appear on top
Plotly.addTraces(plotId, predictionTraces);
// PERFORMANCE: Use batched trace addition for better performance
if (predictionTraces.length > 0) {
this._batchAddTraces(plotId, predictionTraces);
}
// Ensure predictions are visible above real candles by setting z-order
// Update layout to ensure prediction traces are on top