live updates wip

This commit is contained in:
Dobromir Popov
2025-11-13 18:10:15 +02:00
parent 59f2382b3a
commit 4fcadcdbff
6 changed files with 568 additions and 14 deletions

View File

@@ -0,0 +1,243 @@
/**
* WebSocket-based Live Updates for ANNOTATE
* Provides real-time chart updates and model predictions
*/
class LiveUpdatesWebSocket {
constructor() {
this.socket = null;
this.connected = false;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectDelay = 1000; // Start with 1 second
this.subscriptions = new Set();
// Callbacks
this.onChartUpdate = null;
this.onPredictionUpdate = null;
this.onConnectionChange = null;
console.log('LiveUpdatesWebSocket initialized');
}
connect() {
if (this.connected) {
console.log('Already connected to WebSocket');
return;
}
try {
// Initialize SocketIO connection
this.socket = io({
transports: ['websocket', 'polling'],
upgrade: true,
rememberUpgrade: true
});
this._setupEventHandlers();
console.log('Connecting to WebSocket...');
} catch (error) {
console.error('Failed to initialize WebSocket:', error);
this._scheduleReconnect();
}
}
_setupEventHandlers() {
// Connection events
this.socket.on('connect', () => {
console.log('✅ WebSocket connected');
this.connected = true;
this.reconnectAttempts = 0;
this.reconnectDelay = 1000;
if (this.onConnectionChange) {
this.onConnectionChange(true);
}
// Resubscribe to previous subscriptions
this.subscriptions.forEach(sub => {
this._subscribe(sub.symbol, sub.timeframe);
});
});
this.socket.on('disconnect', () => {
console.log('❌ WebSocket disconnected');
this.connected = false;
if (this.onConnectionChange) {
this.onConnectionChange(false);
}
this._scheduleReconnect();
});
this.socket.on('connection_response', (data) => {
console.log('Connection response:', data);
});
this.socket.on('subscription_confirmed', (data) => {
console.log('Subscription confirmed:', data);
});
// Data events
this.socket.on('chart_update', (data) => {
console.debug('Chart update received:', data);
if (this.onChartUpdate) {
this.onChartUpdate(data);
}
});
this.socket.on('prediction_update', (data) => {
console.debug('Prediction update received:', data);
if (this.onPredictionUpdate) {
this.onPredictionUpdate(data);
}
});
this.socket.on('prediction_error', (data) => {
console.error('Prediction error:', data);
});
// Error events
this.socket.on('connect_error', (error) => {
console.error('WebSocket connection error:', error);
this._scheduleReconnect();
});
this.socket.on('error', (error) => {
console.error('WebSocket error:', error);
});
}
_scheduleReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('Max reconnection attempts reached. Please refresh the page.');
return;
}
this.reconnectAttempts++;
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1); // Exponential backoff
console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
setTimeout(() => {
if (!this.connected) {
this.connect();
}
}, delay);
}
subscribe(symbol, timeframe) {
this.subscriptions.add({ symbol, timeframe });
if (this.connected) {
this._subscribe(symbol, timeframe);
}
}
_subscribe(symbol, timeframe) {
if (!this.socket || !this.connected) {
console.warn('Cannot subscribe - not connected');
return;
}
console.log(`Subscribing to live updates: ${symbol} ${timeframe}`);
this.socket.emit('subscribe_live_updates', {
symbol: symbol,
timeframe: timeframe
});
}
requestPrediction(symbol, timeframe, predictionSteps = 1) {
if (!this.socket || !this.connected) {
console.warn('Cannot request prediction - not connected');
return;
}
console.log(`Requesting prediction: ${symbol} ${timeframe} (${predictionSteps} steps)`);
this.socket.emit('request_prediction', {
symbol: symbol,
timeframe: timeframe,
prediction_steps: predictionSteps
});
}
disconnect() {
if (this.socket) {
console.log('Disconnecting WebSocket...');
this.socket.disconnect();
this.socket = null;
this.connected = false;
this.subscriptions.clear();
}
}
isConnected() {
return this.connected;
}
}
// Global instance
window.liveUpdatesWS = null;
// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
// Check if SocketIO is available
if (typeof io === 'undefined') {
console.warn('⚠️ Socket.IO not loaded - live updates will not work');
console.warn('Add <script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script> to your HTML');
return;
}
// Initialize WebSocket
window.liveUpdatesWS = new LiveUpdatesWebSocket();
// Setup callbacks
window.liveUpdatesWS.onConnectionChange = function(connected) {
const statusElement = document.getElementById('ws-connection-status');
if (statusElement) {
if (connected) {
statusElement.innerHTML = '<span class="badge bg-success">🟢 Live</span>';
} else {
statusElement.innerHTML = '<span class="badge bg-danger">🔴 Disconnected</span>';
}
}
};
window.liveUpdatesWS.onChartUpdate = function(data) {
// Update chart with new candle
if (window.appState && window.appState.chartManager) {
window.appState.chartManager.updateLatestCandle(data.symbol, data.timeframe, data.candle);
}
};
window.liveUpdatesWS.onPredictionUpdate = function(data) {
// Update prediction display
if (typeof updatePredictionDisplay === 'function') {
updatePredictionDisplay(data);
}
// Add to prediction history
if (typeof predictionHistory !== 'undefined') {
predictionHistory.unshift(data);
if (predictionHistory.length > 5) {
predictionHistory = predictionHistory.slice(0, 5);
}
if (typeof updatePredictionHistory === 'function') {
updatePredictionHistory();
}
}
};
// Auto-connect
console.log('Auto-connecting to WebSocket...');
window.liveUpdatesWS.connect();
});
// Cleanup on page unload
window.addEventListener('beforeunload', function() {
if (window.liveUpdatesWS) {
window.liveUpdatesWS.disconnect();
}
});