277 lines
9.8 KiB
JavaScript
277 lines
9.8 KiB
JavaScript
/**
|
|
* Polling-based Live Updates for ANNOTATE
|
|
* Replaces WebSocket with simple polling (like clean_dashboard)
|
|
*/
|
|
|
|
class LiveUpdatesPolling {
|
|
constructor() {
|
|
this.pollInterval = null;
|
|
this.pollDelay = 2000; // Poll every 2 seconds (like clean_dashboard)
|
|
this.subscriptions = new Set();
|
|
this.isPolling = false;
|
|
|
|
// Callbacks
|
|
this.onChartUpdate = null;
|
|
this.onPredictionUpdate = null;
|
|
this.onConnectionChange = null;
|
|
|
|
console.log('LiveUpdatesPolling initialized');
|
|
}
|
|
|
|
start() {
|
|
if (this.isPolling) {
|
|
console.log('Already polling');
|
|
return;
|
|
}
|
|
|
|
this.isPolling = true;
|
|
this._startPolling();
|
|
|
|
if (this.onConnectionChange) {
|
|
this.onConnectionChange(true);
|
|
}
|
|
|
|
console.log('Started polling for live updates');
|
|
}
|
|
|
|
stop() {
|
|
if (this.pollInterval) {
|
|
clearInterval(this.pollInterval);
|
|
this.pollInterval = null;
|
|
}
|
|
this.isPolling = false;
|
|
|
|
if (this.onConnectionChange) {
|
|
this.onConnectionChange(false);
|
|
}
|
|
|
|
console.log('Stopped polling for live updates');
|
|
}
|
|
|
|
_startPolling() {
|
|
// Poll immediately, then set interval
|
|
this._poll();
|
|
this.pollInterval = setInterval(() => {
|
|
this._poll();
|
|
}, this.pollDelay);
|
|
}
|
|
|
|
_poll() {
|
|
// OPTIMIZATION: Batch all subscriptions into a single API call
|
|
// Group by symbol to reduce API calls from 4 to 1
|
|
const symbolGroups = {};
|
|
this.subscriptions.forEach(sub => {
|
|
if (!symbolGroups[sub.symbol]) {
|
|
symbolGroups[sub.symbol] = [];
|
|
}
|
|
symbolGroups[sub.symbol].push(sub.timeframe);
|
|
});
|
|
|
|
// Make one call per symbol with all timeframes
|
|
Object.entries(symbolGroups).forEach(([symbol, timeframes]) => {
|
|
fetch('/api/live-updates-batch', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
symbol: symbol,
|
|
timeframes: timeframes
|
|
})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
// Handle chart updates for each timeframe
|
|
if (data.chart_updates && this.onChartUpdate) {
|
|
// chart_updates is an object: { '1s': {...}, '1m': {...}, ... }
|
|
Object.entries(data.chart_updates).forEach(([timeframe, update]) => {
|
|
if (update) {
|
|
this.onChartUpdate(update);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Handle prediction update (single prediction for all timeframes)
|
|
// data.prediction is in format { transformer: {...}, dqn: {...}, cnn: {...} }
|
|
if (data.prediction && this.onPredictionUpdate) {
|
|
console.log('[Live Updates] Received prediction data:', {
|
|
has_transformer: !!data.prediction.transformer,
|
|
has_dqn: !!data.prediction.dqn,
|
|
has_cnn: !!data.prediction.cnn,
|
|
transformer_action: data.prediction.transformer?.action,
|
|
transformer_confidence: data.prediction.transformer?.confidence,
|
|
has_predicted_candle: !!data.prediction.transformer?.predicted_candle
|
|
});
|
|
|
|
this.onPredictionUpdate(data.prediction);
|
|
}
|
|
} else {
|
|
console.debug('[Live Updates] Response not successful:', data);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.debug('Polling error:', error);
|
|
});
|
|
});
|
|
}
|
|
|
|
subscribe(symbol, timeframe) {
|
|
const key = `${symbol}_${timeframe}`;
|
|
this.subscriptions.add({ symbol, timeframe, key });
|
|
|
|
// Auto-start polling if not already started
|
|
if (!this.isPolling) {
|
|
this.start();
|
|
}
|
|
|
|
console.log(`Subscribed to live updates: ${symbol} ${timeframe}`);
|
|
}
|
|
|
|
unsubscribe(symbol, timeframe) {
|
|
const key = `${symbol}_${timeframe}`;
|
|
this.subscriptions.forEach(sub => {
|
|
if (sub.key === key) {
|
|
this.subscriptions.delete(sub);
|
|
}
|
|
});
|
|
|
|
// Stop polling if no subscriptions
|
|
if (this.subscriptions.size === 0) {
|
|
this.stop();
|
|
}
|
|
|
|
console.log(`Unsubscribed from live updates: ${symbol} ${timeframe}`);
|
|
}
|
|
|
|
isConnected() {
|
|
return this.isPolling;
|
|
}
|
|
}
|
|
|
|
// Global instance
|
|
window.liveUpdatesPolling = null;
|
|
|
|
// Initialize on page load
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Initialize polling
|
|
window.liveUpdatesPolling = new LiveUpdatesPolling();
|
|
|
|
// Setup callbacks (same interface as WebSocket version)
|
|
window.liveUpdatesPolling.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-secondary">Offline</span>';
|
|
}
|
|
}
|
|
};
|
|
|
|
window.liveUpdatesPolling.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.liveUpdatesPolling.onPredictionUpdate = function(data) {
|
|
// CRITICAL FIX: data is already in format { transformer: {...}, dqn: {...}, cnn: {...} }
|
|
console.log('[Live Updates] Prediction received:', data);
|
|
|
|
// Update prediction visualization on charts
|
|
if (window.appState && window.appState.chartManager) {
|
|
// Store predictions for later use
|
|
if (!window.appState.chartManager.predictions) {
|
|
window.appState.chartManager.predictions = {};
|
|
}
|
|
|
|
// Update stored predictions
|
|
if (data.transformer) {
|
|
window.appState.chartManager.predictions['transformer'] = data.transformer;
|
|
}
|
|
if (data.dqn) {
|
|
window.appState.chartManager.predictions['dqn'] = data.dqn;
|
|
}
|
|
if (data.cnn) {
|
|
window.appState.chartManager.predictions['cnn'] = data.cnn;
|
|
}
|
|
|
|
// Update charts with predictions
|
|
window.appState.chartManager.updatePredictions(data);
|
|
}
|
|
|
|
// Update prediction display in UI
|
|
if (typeof updatePredictionDisplay === 'function') {
|
|
// updatePredictionDisplay expects a single prediction object, not the full data structure
|
|
// Pass the transformer prediction if available
|
|
if (data.transformer) {
|
|
updatePredictionDisplay(data.transformer);
|
|
}
|
|
}
|
|
|
|
// Add to prediction history (use transformer prediction if available)
|
|
if (typeof predictionHistory !== 'undefined') {
|
|
const predictionToAdd = data.transformer || data.dqn || data.cnn || data;
|
|
if (predictionToAdd) {
|
|
predictionHistory.unshift(predictionToAdd);
|
|
if (predictionHistory.length > 5) {
|
|
predictionHistory = predictionHistory.slice(0, 5);
|
|
}
|
|
if (typeof updatePredictionHistory === 'function') {
|
|
updatePredictionHistory();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Function to subscribe to all active timeframes
|
|
function subscribeToActiveTimeframes() {
|
|
if (window.appState && window.appState.currentSymbol && window.appState.currentTimeframes) {
|
|
const symbol = window.appState.currentSymbol;
|
|
window.appState.currentTimeframes.forEach(timeframe => {
|
|
window.liveUpdatesPolling.subscribe(symbol, timeframe);
|
|
});
|
|
console.log(`Subscribed to live updates for ${symbol}: ${window.appState.currentTimeframes.join(', ')}`);
|
|
}
|
|
}
|
|
|
|
// Auto-start polling
|
|
console.log('Auto-starting polling for live updates...');
|
|
window.liveUpdatesPolling.start();
|
|
|
|
// Wait for DOM and appState to be ready, then subscribe
|
|
function initializeSubscriptions() {
|
|
// Wait a bit for charts to initialize
|
|
setTimeout(() => {
|
|
subscribeToActiveTimeframes();
|
|
}, 2000);
|
|
}
|
|
|
|
// Subscribe when DOM is ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initializeSubscriptions);
|
|
} else {
|
|
initializeSubscriptions();
|
|
}
|
|
|
|
// Also monitor for appState changes (fallback)
|
|
let lastTimeframes = null;
|
|
setInterval(() => {
|
|
if (window.appState && window.appState.currentTimeframes && window.appState.chartManager) {
|
|
const currentTimeframes = window.appState.currentTimeframes.join(',');
|
|
if (currentTimeframes !== lastTimeframes) {
|
|
lastTimeframes = currentTimeframes;
|
|
subscribeToActiveTimeframes();
|
|
}
|
|
}
|
|
}, 3000);
|
|
});
|
|
|
|
// Cleanup on page unload
|
|
window.addEventListener('beforeunload', function() {
|
|
if (window.liveUpdatesPolling) {
|
|
window.liveUpdatesPolling.stop();
|
|
}
|
|
});
|
|
|