remove ws, fix predictions

This commit is contained in:
Dobromir Popov
2025-12-10 00:26:57 +02:00
parent 992d6de25b
commit c21d8cbea1
6 changed files with 526 additions and 284 deletions

View File

@@ -715,22 +715,9 @@ class AnnotationDashboard:
static_folder='static'
)
# Initialize SocketIO for WebSocket support
try:
from flask_socketio import SocketIO, emit
self.socketio = SocketIO(
self.server,
cors_allowed_origins="*",
async_mode='threading',
logger=False,
engineio_logger=False
)
self.has_socketio = True
logger.info("SocketIO initialized for real-time updates")
except ImportError:
self.socketio = None
self.has_socketio = False
logger.warning("flask-socketio not installed - live updates will use polling")
# WebSocket support removed - using HTTP polling only
self.socketio = None
self.has_socketio = False
# Suppress werkzeug request logs (reduce noise from polling endpoints)
werkzeug_logger = logging.getLogger('werkzeug')
@@ -777,9 +764,7 @@ class AnnotationDashboard:
# Initialize training strategy manager (controls training decisions)
self.training_strategy = TrainingStrategyManager(self.data_provider, self.training_adapter)
self.training_strategy.dashboard = self
# Pass socketio to training adapter for live trade updates
if self.has_socketio and self.socketio:
self.training_adapter.socketio = self.socketio
# WebSocket removed - using HTTP polling only
# Backtest runner for replaying visible chart with predictions
self.backtest_runner = BacktestRunner()
@@ -2546,27 +2531,46 @@ class AnnotationDashboard:
'prediction': None
}
# Get latest candle for the requested timeframe
if self.orchestrator and self.orchestrator.data_provider:
# Get latest candle for the requested timeframe using data_loader
if self.data_loader:
try:
# Get latest candle
ohlcv_data = self.orchestrator.data_provider.get_ohlcv_data(symbol, timeframe, limit=1)
if ohlcv_data and len(ohlcv_data) > 0:
latest_candle = ohlcv_data[-1]
# Get latest candle from data_loader
df = self.data_loader.get_data(symbol, timeframe, limit=2, direction='latest')
if df is not None and not df.empty:
latest_candle = df.iloc[-1]
# Format timestamp as ISO string (ensure UTC format for frontend)
timestamp = latest_candle.name
if hasattr(timestamp, 'isoformat'):
# If timezone-aware, convert to UTC ISO string
if timestamp.tzinfo is not None:
timestamp_str = timestamp.astimezone(timezone.utc).isoformat()
else:
# Assume UTC if no timezone info
timestamp_str = timestamp.isoformat() + 'Z'
else:
timestamp_str = str(timestamp)
# Determine if candle is confirmed (we have 2 candles, so previous is confirmed)
is_confirmed = len(df) >= 2
response['chart_update'] = {
'symbol': symbol,
'timeframe': timeframe,
'candle': {
'timestamp': latest_candle[0],
'open': float(latest_candle[1]),
'high': float(latest_candle[2]),
'low': float(latest_candle[3]),
'close': float(latest_candle[4]),
'volume': float(latest_candle[5])
}
'timestamp': timestamp_str,
'open': float(latest_candle['open']),
'high': float(latest_candle['high']),
'low': float(latest_candle['low']),
'close': float(latest_candle['close']),
'volume': float(latest_candle['volume'])
},
'is_confirmed': is_confirmed
}
except Exception as e:
logger.debug(f"Error getting latest candle: {e}")
logger.debug(f"Error getting latest candle from data_loader: {e}", exc_info=True)
else:
logger.debug("Data loader not available for live updates")
# Get latest model predictions
if self.orchestrator:
@@ -2762,9 +2766,7 @@ class AnnotationDashboard:
'error': str(e)
})
# WebSocket event handlers (if SocketIO is available)
if self.has_socketio:
self._setup_websocket_handlers()
# WebSocket removed - using HTTP polling only
def _serialize_prediction(self, prediction: Dict) -> Dict:
"""Convert PyTorch tensors in prediction dict to JSON-serializable Python types"""
@@ -2793,184 +2795,7 @@ class AnnotationDashboard:
# Fallback: return as-is (might fail JSON serialization but won't crash)
return prediction
def _setup_websocket_handlers(self):
"""Setup WebSocket event handlers for real-time updates"""
if not self.has_socketio:
return
@self.socketio.on('connect')
def handle_connect():
"""Handle client connection"""
logger.info(f"WebSocket client connected")
from flask_socketio import emit
emit('connection_response', {'status': 'connected', 'message': 'Connected to ANNOTATE live updates'})
@self.socketio.on('disconnect')
def handle_disconnect():
"""Handle client disconnection"""
logger.info(f"WebSocket client disconnected")
@self.socketio.on('subscribe_live_updates')
def handle_subscribe(data):
"""Subscribe to live chart and prediction updates"""
from flask_socketio import emit, join_room
symbol = data.get('symbol', 'ETH/USDT')
timeframe = data.get('timeframe', '1s')
room = f"{symbol}_{timeframe}"
join_room(room)
logger.info(f"Client subscribed to live updates: {room}")
emit('subscription_confirmed', {'room': room, 'symbol': symbol, 'timeframe': timeframe})
# Start live update thread if not already running
if not hasattr(self, '_live_update_thread') or not self._live_update_thread.is_alive():
self._start_live_update_thread()
@self.socketio.on('request_prediction')
def handle_prediction_request(data):
"""Handle manual prediction request"""
from flask_socketio import emit
try:
symbol = data.get('symbol', 'ETH/USDT')
timeframe = data.get('timeframe', '1s')
prediction_steps = data.get('prediction_steps', 1)
# Get prediction from model
prediction = self._get_live_prediction(symbol, timeframe, prediction_steps)
emit('prediction_update', prediction)
except Exception as e:
logger.error(f"Error handling prediction request: {e}")
emit('prediction_error', {'error': str(e)})
@self.socketio.on('prediction_accuracy')
def handle_prediction_accuracy(data):
"""
Handle validated prediction accuracy - trigger incremental training
This is called when frontend validates a prediction against actual candle.
We use this data to incrementally train the model for continuous improvement.
"""
from flask_socketio import emit
try:
timeframe = data.get('timeframe')
timestamp = data.get('timestamp')
predicted = data.get('predicted') # [O, H, L, C, V]
actual = data.get('actual') # [O, H, L, C]
errors = data.get('errors') # {open, high, low, close}
pct_errors = data.get('pctErrors')
direction_correct = data.get('directionCorrect')
accuracy = data.get('accuracy')
if not all([timeframe, timestamp, predicted, actual]):
logger.warning("Incomplete prediction accuracy data received")
return
logger.info(f"[{timeframe}] Prediction validated: {accuracy:.1f}% accuracy, direction: {direction_correct}")
logger.debug(f" Errors: O={pct_errors['open']:.2f}% H={pct_errors['high']:.2f}% L={pct_errors['low']:.2f}% C={pct_errors['close']:.2f}%")
# Trigger incremental training on this validated prediction
self._train_on_validated_prediction(
timeframe=timeframe,
timestamp=timestamp,
predicted=predicted,
actual=actual,
errors=errors,
direction_correct=direction_correct,
accuracy=accuracy
)
# Send confirmation back to frontend
emit('training_update', {
'status': 'training_triggered',
'timestamp': timestamp,
'accuracy': accuracy,
'message': f'Incremental training triggered on validated prediction'
})
except Exception as e:
logger.error(f"Error handling prediction accuracy: {e}", exc_info=True)
emit('training_error', {'error': str(e)})
def _start_live_update_thread(self):
"""Start background thread for live updates"""
import threading
def live_update_worker():
"""Background worker for live updates"""
import time
from flask_socketio import emit
logger.info("Live update thread started")
while True:
try:
# Get active rooms (symbol_timeframe combinations)
# For now, update all subscribed clients every second
# Get latest chart data
if self.data_provider:
for symbol in ['ETH/USDT', 'BTC/USDT']: # TODO: Get from active subscriptions
for timeframe in ['1s', '1m']:
room = f"{symbol}_{timeframe}"
# Get latest candles (need last 2 to determine confirmation status)
try:
candles = self.data_provider.get_ohlcv(symbol, timeframe, limit=2)
if candles and len(candles) > 0:
latest_candle = candles[-1]
# Determine if candle is confirmed (closed)
# For 1s: candle is confirmed when next candle starts (2s delay)
# For others: candle is confirmed when next candle starts
is_confirmed = len(candles) >= 2 # If we have 2 candles, the first is confirmed
# Format timestamp consistently
timestamp = latest_candle.get('timestamp')
if isinstance(timestamp, str):
# Already formatted
formatted_timestamp = timestamp
else:
# Convert to ISO string then format
from datetime import datetime
if isinstance(timestamp, datetime):
formatted_timestamp = timestamp.strftime('%Y-%m-%d %H:%M:%S')
else:
formatted_timestamp = str(timestamp)
# Emit chart update with full candle data
self.socketio.emit('chart_update', {
'symbol': symbol,
'timeframe': timeframe,
'candle': {
'timestamp': formatted_timestamp,
'open': float(latest_candle.get('open', 0)),
'high': float(latest_candle.get('high', 0)),
'low': float(latest_candle.get('low', 0)),
'close': float(latest_candle.get('close', 0)),
'volume': float(latest_candle.get('volume', 0))
},
'is_confirmed': is_confirmed, # True if this candle is closed/confirmed
'has_previous': len(candles) >= 2 # True if we have previous candle for validation
}, room=room)
# Get prediction if model is loaded
if self.orchestrator and hasattr(self.orchestrator, 'primary_transformer'):
prediction = self._get_live_prediction(symbol, timeframe, 1)
if prediction:
self.socketio.emit('prediction_update', prediction, room=room)
except Exception as e:
logger.debug(f"Error getting data for {symbol} {timeframe}: {e}")
time.sleep(1) # Update every second
except Exception as e:
logger.error(f"Error in live update thread: {e}")
time.sleep(5) # Wait longer on error
self._live_update_thread = threading.Thread(target=live_update_worker, daemon=True)
self._live_update_thread.start()
# WebSocket code removed - using HTTP polling only
def _get_live_transformer_prediction(self, symbol: str = 'ETH/USDT'):
"""
@@ -3423,12 +3248,10 @@ class AnnotationDashboard:
logger.info(f"Access locally at: http://localhost:{port}")
logger.info(f"Access from network at: http://<your-ip>:{port}")
if self.has_socketio:
logger.info("Running with WebSocket support (SocketIO)")
self.socketio.run(self.server, host=host, port=port, debug=debug, allow_unsafe_werkzeug=True)
else:
logger.warning("Running without WebSocket support - install flask-socketio for live updates")
self.server.run(host=host, port=port, debug=debug)
# WebSocket removed - using HTTP polling only
# Start Flask server
self.server.run(host=host, port=port, debug=debug, use_reloader=False)
def main():