244 lines
7.5 KiB
JavaScript
244 lines
7.5 KiB
JavaScript
/**
|
|
* 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();
|
|
}
|
|
});
|