From 01f0a2608fcd22dc57d5162b3c5b88f502b4de05 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Sat, 24 May 2025 12:21:03 +0300 Subject: [PATCH] wip-dash-broken --- web/scalping_dashboard.py | 579 ++++++++++++++++++++++---------------- 1 file changed, 338 insertions(+), 241 deletions(-) diff --git a/web/scalping_dashboard.py b/web/scalping_dashboard.py index 3f992bf..5165107 100644 --- a/web/scalping_dashboard.py +++ b/web/scalping_dashboard.py @@ -1,26 +1,31 @@ """ -Ultra-Fast Scalping Dashboard (500x Leverage) - Real Market Data +Ultra-Fast Real-Time Scalping Dashboard (500x Leverage) - Live Data Streaming -Dashboard using ONLY real market data from APIs with: -- Main 1s ETH/USDT chart (full width) +Real-time WebSocket streaming dashboard with: +- Main 1s ETH/USDT chart (full width) with live updates - 4 small charts: 1m ETH, 1h ETH, 1d ETH, 1s BTC -- 500 candles preloaded at startup -- Real-time updates from data provider +- WebSocket price streaming for instant updates +- Europe/Sofia timezone support +- Ultra-low latency UI updates (100ms) +- NO CACHED DATA - 100% live streaming """ import asyncio import json import logging import time +import websockets +import pytz from datetime import datetime, timedelta -from threading import Thread +from threading import Thread, Lock from typing import Dict, List, Optional, Any +import pandas as pd +import numpy as np +import requests import dash from dash import dcc, html, Input, Output import plotly.graph_objects as go -import pandas as pd -import numpy as np from core.config import get_config from core.data_provider import DataProvider @@ -28,185 +33,156 @@ from core.enhanced_orchestrator import EnhancedTradingOrchestrator, TradingActio logger = logging.getLogger(__name__) -class ScalpingDashboard: - """Real market data scalping dashboard for 500x leverage trading""" +class RealTimeScalpingDashboard: + """Real-time scalping dashboard with WebSocket streaming and ultra-low latency""" def __init__(self, data_provider: DataProvider = None, orchestrator: EnhancedTradingOrchestrator = None): - """Initialize the dashboard with real market data""" + """Initialize the real-time dashboard with WebSocket streaming""" self.config = get_config() self.data_provider = data_provider or DataProvider() self.orchestrator = orchestrator or EnhancedTradingOrchestrator(self.data_provider) + # Timezone setup + self.timezone = pytz.timezone('Europe/Sofia') + # Dashboard state self.recent_decisions = [] self.scalping_metrics = { 'total_trades': 0, 'win_rate': 0.78, - 'total_pnl': 0.0, # Will be updated by runner - 'avg_trade_time': 3.2, # seconds + 'total_pnl': 0.0, + 'avg_trade_time': 3.2, 'leverage': '500x', 'last_action': None } - # Real market data cache - preload 500 candles for each chart - self.market_data = { + # Real-time price streaming data + self.live_prices = { + 'ETH/USDT': 0.0, + 'BTC/USDT': 0.0 + } + + # Real-time chart data (no caching - always fresh) + self.chart_data = { 'ETH/USDT': { - '1s': None, # Main chart - '1m': None, # Small chart - '1h': None, # Small chart - '1d': None # Small chart + '1s': pd.DataFrame(), + '1m': pd.DataFrame(), + '1h': pd.DataFrame(), + '1d': pd.DataFrame() }, 'BTC/USDT': { - '1s': None # Small chart + '1s': pd.DataFrame() } } - # Initialize real market data - self._preload_market_data() + # WebSocket streaming control + self.streaming = False + self.websocket_threads = [] + self.data_lock = Lock() - # Create Dash app - self.app = dash.Dash(__name__) + # Create Dash app with real-time updates + self.app = dash.Dash(__name__, + external_stylesheets=['https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css']) # Setup layout and callbacks self._setup_layout() self._setup_callbacks() + self._start_real_time_streaming() - logger.info("Ultra-Fast Scalping Dashboard initialized with REAL MARKET DATA") - - def _preload_market_data(self): - """Preload 500 candles for each chart from real market APIs""" - logger.info("🔄 Preloading 500 candles of real market data...") - - try: - # Load ETH/USDT data for main chart and small charts - self.market_data['ETH/USDT']['1s'] = self.data_provider.get_historical_data( - 'ETH/USDT', '1s', limit=500 - ) - self.market_data['ETH/USDT']['1m'] = self.data_provider.get_historical_data( - 'ETH/USDT', '1m', limit=500 - ) - self.market_data['ETH/USDT']['1h'] = self.data_provider.get_historical_data( - 'ETH/USDT', '1h', limit=500 - ) - self.market_data['ETH/USDT']['1d'] = self.data_provider.get_historical_data( - 'ETH/USDT', '1d', limit=500 - ) - - # Load BTC/USDT 1s data for small chart - self.market_data['BTC/USDT']['1s'] = self.data_provider.get_historical_data( - 'BTC/USDT', '1s', limit=500 - ) - - # Log successful data loading - for symbol in self.market_data: - for timeframe, data in self.market_data[symbol].items(): - if data is not None and not data.empty: - logger.info(f"✅ Loaded {len(data)} candles for {symbol} {timeframe}") - logger.info(f" Price range: ${data['close'].min():.2f} - ${data['close'].max():.2f}") - else: - logger.warning(f"⚠️ No data loaded for {symbol} {timeframe}") - - logger.info("✅ Market data preload complete - ready for real-time updates") - - except Exception as e: - logger.error(f"❌ Error preloading market data: {e}") - # Initialize empty DataFrames as fallback - for symbol in self.market_data: - for timeframe in self.market_data[symbol]: - self.market_data[symbol][timeframe] = pd.DataFrame() + logger.info("🚀 Real-Time Scalping Dashboard initialized with LIVE STREAMING") + logger.info("📡 WebSocket price streaming enabled") + logger.info(f"🌍 Timezone: {self.timezone}") def _setup_layout(self): - """Setup the 5-chart dashboard layout""" + """Setup the ultra-fast real-time dashboard layout""" self.app.layout = html.Div([ - # Header with real-time metrics + # Header with live metrics html.Div([ - html.H1("ULTRA-FAST SCALPING DASHBOARD - 500x LEVERAGE - REAL MARKET DATA", - className="text-center mb-4"), + html.H1("🚀 REAL-TIME SCALPING DASHBOARD - 500x LEVERAGE - LIVE STREAMING", + className="text-center mb-4 text-white"), + html.P(f"🌍 Sofia Time Zone | 📡 Live WebSocket Streaming | ⚡ 100ms Updates", + className="text-center text-info"), - # Real-time metrics row + # Live metrics row html.Div([ html.Div([ html.H3(id="live-pnl", className="text-success"), - html.P("Total P&L") + html.P("Total P&L", className="text-white") ], className="col-md-2 text-center"), html.Div([ html.H3(id="win-rate", className="text-info"), - html.P("Win Rate") + html.P("Win Rate", className="text-white") ], className="col-md-2 text-center"), html.Div([ html.H3(id="total-trades", className="text-primary"), - html.P("Total Trades") + html.P("Total Trades", className="text-white") ], className="col-md-2 text-center"), html.Div([ html.H3(id="last-action", className="text-warning"), - html.P("Last Action") + html.P("Last Action", className="text-white") ], className="col-md-2 text-center"), html.Div([ - html.H3(id="eth-price", className="text-white"), - html.P("ETH/USDT Live") + html.H3(id="eth-price", className="text-success"), + html.P("ETH/USDT LIVE", className="text-white") ], className="col-md-2 text-center"), html.Div([ - html.H3(id="btc-price", className="text-white"), - html.P("BTC/USDT Live") + html.H3(id="btc-price", className="text-success"), + html.P("BTC/USDT LIVE", className="text-white") ], className="col-md-2 text-center") ], className="row mb-4") ], className="bg-dark p-3 mb-3"), - # Main 1s ETH/USDT chart (full width) + # Main 1s ETH/USDT chart (full width) - REAL-TIME html.Div([ - html.H4("ETH/USDT 1s Real-Time Chart (Main Trading Signal)", + html.H4("📈 ETH/USDT 1s Real-Time Chart (Live WebSocket Feed)", className="text-center mb-3"), - dcc.Graph(id="main-eth-1s-chart", style={"height": "500px"}) + dcc.Graph(id="main-eth-1s-chart", style={"height": "600px"}) ], className="mb-4"), - # Row of 4 small charts + # Row of 4 small charts - ALL REAL-TIME html.Div([ - # ETH/USDT 1m html.Div([ - html.H6("ETH/USDT 1m", className="text-center"), - dcc.Graph(id="eth-1m-chart", style={"height": "250px"}) + html.H6("ETH/USDT 1m LIVE", className="text-center"), + dcc.Graph(id="eth-1m-chart", style={"height": "300px"}) ], className="col-md-3"), - # ETH/USDT 1h html.Div([ - html.H6("ETH/USDT 1h", className="text-center"), - dcc.Graph(id="eth-1h-chart", style={"height": "250px"}) + html.H6("ETH/USDT 1h LIVE", className="text-center"), + dcc.Graph(id="eth-1h-chart", style={"height": "300px"}) ], className="col-md-3"), - # ETH/USDT 1d html.Div([ - html.H6("ETH/USDT 1d", className="text-center"), - dcc.Graph(id="eth-1d-chart", style={"height": "250px"}) + html.H6("ETH/USDT 1d LIVE", className="text-center"), + dcc.Graph(id="eth-1d-chart", style={"height": "300px"}) ], className="col-md-3"), - # BTC/USDT 1s html.Div([ - html.H6("BTC/USDT 1s", className="text-center"), - dcc.Graph(id="btc-1s-chart", style={"height": "250px"}) + html.H6("BTC/USDT 1s LIVE", className="text-center"), + dcc.Graph(id="btc-1s-chart", style={"height": "300px"}) ], className="col-md-3") ], className="row mb-4"), - # Recent actions log + # Live actions log html.Div([ - html.H5("Live Trading Actions (Real Market Data)", className="text-center mb-3"), + html.H5("🔥 Live Trading Actions (Real-Time Stream)", className="text-center mb-3"), html.Div(id="actions-log") ], className="mb-4"), - # Auto-refresh for real-time updates + # Ultra-fast refresh for real-time updates (100ms) dcc.Interval( - id='market-data-interval', - interval=1000, # Update every 1 second for real-time feel + id='ultra-fast-interval', + interval=100, # 100ms for ultra-low latency n_intervals=0 ) - ], className="container-fluid") + ], className="container-fluid bg-dark") def _setup_callbacks(self): - """Setup dashboard callbacks with real market data""" + """Setup ultra-fast callbacks with real-time streaming data""" @self.app.callback( [ @@ -223,107 +199,198 @@ class ScalpingDashboard: Output('btc-1s-chart', 'figure'), Output('actions-log', 'children') ], - [Input('market-data-interval', 'n_intervals')] + [Input('ultra-fast-interval', 'n_intervals')] ) - def update_dashboard_with_real_data(n_intervals): - """Update all dashboard components with real market data""" + def update_real_time_dashboard(n_intervals): + """Update all components with real-time streaming data""" try: - # Update metrics - pnl = f"${self.scalping_metrics['total_pnl']:+.2f}" - win_rate = f"{self.scalping_metrics['win_rate']*100:.1f}%" - total_trades = str(self.scalping_metrics['total_trades']) - last_action = self.scalping_metrics['last_action'] or "WAITING" - - # Get current prices from real market data - eth_price = self._get_current_price('ETH/USDT') - btc_price = self._get_current_price('BTC/USDT') - - # Refresh market data periodically (every 10 updates) - if n_intervals % 10 == 0: - self._refresh_market_data() - - # Create charts with real market data - main_eth_chart = self._create_real_chart('ETH/USDT', '1s', main_chart=True) - eth_1m_chart = self._create_real_chart('ETH/USDT', '1m') - eth_1h_chart = self._create_real_chart('ETH/USDT', '1h') - eth_1d_chart = self._create_real_chart('ETH/USDT', '1d') - btc_1s_chart = self._create_real_chart('BTC/USDT', '1s') - - # Create actions log - actions_log = self._create_actions_log() - - return ( - pnl, win_rate, total_trades, last_action, eth_price, btc_price, - main_eth_chart, eth_1m_chart, eth_1h_chart, eth_1d_chart, btc_1s_chart, - actions_log - ) + with self.data_lock: + # Update metrics + pnl = f"${self.scalping_metrics['total_pnl']:+.2f}" + win_rate = f"{self.scalping_metrics['win_rate']*100:.1f}%" + total_trades = str(self.scalping_metrics['total_trades']) + last_action = self.scalping_metrics['last_action'] or "⏳ WAITING" + + # Live prices from WebSocket stream + eth_price = f"${self.live_prices['ETH/USDT']:.2f}" if self.live_prices['ETH/USDT'] > 0 else "🔄 Loading..." + btc_price = f"${self.live_prices['BTC/USDT']:.2f}" if self.live_prices['BTC/USDT'] > 0 else "🔄 Loading..." + + # Refresh chart data every 10 intervals (1 second) + if n_intervals % 10 == 0: + self._refresh_live_data() + + # Create real-time charts + main_eth_chart = self._create_live_chart('ETH/USDT', '1s', main_chart=True) + eth_1m_chart = self._create_live_chart('ETH/USDT', '1m') + eth_1h_chart = self._create_live_chart('ETH/USDT', '1h') + eth_1d_chart = self._create_live_chart('ETH/USDT', '1d') + btc_1s_chart = self._create_live_chart('BTC/USDT', '1s') + + # Live actions log + actions_log = self._create_live_actions_log() + + return ( + pnl, win_rate, total_trades, last_action, eth_price, btc_price, + main_eth_chart, eth_1m_chart, eth_1h_chart, eth_1d_chart, btc_1s_chart, + actions_log + ) except Exception as e: - logger.error(f"Error updating dashboard: {e}") - # Return safe defaults + logger.error(f"Error in real-time update: {e}") return ( - "$0.00", "0%", "0", "ERROR", "$0", "$0", - {}, {}, {}, {}, {}, "Loading real market data..." + "$0.00", "0%", "0", "ERROR", "🔄 Loading...", "🔄 Loading...", + {}, {}, {}, {}, {}, "🔄 Loading real-time data..." ) - def _refresh_market_data(self): - """Refresh market data from APIs""" - try: - # Get latest data for each chart (last 100 candles for efficiency) - for symbol in self.market_data: - for timeframe in self.market_data[symbol]: - latest_data = self.data_provider.get_latest_candles(symbol, timeframe, limit=100) - if latest_data is not None and not latest_data.empty: - # Update our cache with latest data - if self.market_data[symbol][timeframe] is not None: - # Append new data and keep last 500 candles - combined = pd.concat([self.market_data[symbol][timeframe], latest_data]) - combined = combined.drop_duplicates(subset=['timestamp'], keep='last') - self.market_data[symbol][timeframe] = combined.tail(500) - else: - self.market_data[symbol][timeframe] = latest_data.tail(500) + def _start_real_time_streaming(self): + """Start WebSocket streaming for real-time price updates""" + logger.info("🚀 Starting real-time WebSocket price streaming...") + self.streaming = True + + # Start WebSocket streams for each symbol + for symbol in ['ETHUSDT', 'BTCUSDT']: + thread = Thread(target=self._websocket_price_stream, args=(symbol,), daemon=True) + thread.start() + self.websocket_threads.append(thread) + + logger.info("📡 WebSocket streams started for ETH/USDT and BTC/USDT") + + def _websocket_price_stream(self, symbol: str): + """WebSocket stream for real-time price updates""" + url = f"wss://stream.binance.com:9443/ws/{symbol.lower()}@ticker" + + while self.streaming: + try: + async def stream_prices(): + async with websockets.connect(url) as websocket: + logger.info(f"📡 WebSocket connected for {symbol}") + async for message in websocket: + if not self.streaming: + break - except Exception as e: - logger.warning(f"Error refreshing market data: {e}") + try: + data = json.loads(message) + price = float(data.get('c', 0)) + + # Update live prices + with self.data_lock: + formatted_symbol = f"{symbol[:3]}/{symbol[3:]}" + self.live_prices[formatted_symbol] = price + + logger.debug(f"💰 {formatted_symbol}: ${price:.2f}") + + except Exception as e: + logger.warning(f"Error processing WebSocket data for {symbol}: {e}") + + # Run the async stream + asyncio.new_event_loop().run_until_complete(stream_prices()) + + except Exception as e: + logger.error(f"WebSocket error for {symbol}: {e}") + if self.streaming: + logger.info(f"🔄 Reconnecting WebSocket for {symbol} in 5 seconds...") + time.sleep(5) - def _get_current_price(self, symbol: str) -> str: - """Get current price from real market data""" + def _refresh_live_data(self): + """Refresh chart data with fresh API calls (NO CACHING)""" try: - data = self.market_data[symbol]['1s'] - if data is not None and not data.empty: - current_price = data['close'].iloc[-1] - return f"${current_price:.2f}" - return "$0.00" - except Exception as e: - logger.warning(f"Error getting current price for {symbol}: {e}") - return "$0.00" - - def _create_real_chart(self, symbol: str, timeframe: str, main_chart: bool = False): - """Create chart using real market data""" - try: - data = self.market_data[symbol][timeframe] + logger.info("🔄 Fetching fresh market data (NO CACHE)...") - if data is None or data.empty: - # Return empty chart with message + # Force fresh API calls for all timeframes + for symbol, timeframes in self.chart_data.items(): + for timeframe in timeframes: + try: + # FORCE fresh data - explicitly set refresh=True + fresh_data = self._fetch_fresh_candles(symbol, timeframe, limit=200) + + if fresh_data is not None and not fresh_data.empty: + with self.data_lock: + self.chart_data[symbol][timeframe] = fresh_data + logger.debug(f"✅ Fresh data loaded: {symbol} {timeframe} - {len(fresh_data)} candles") + + except Exception as e: + logger.warning(f"Error fetching fresh data for {symbol} {timeframe}: {e}") + + except Exception as e: + logger.error(f"Error in live data refresh: {e}") + + def _fetch_fresh_candles(self, symbol: str, timeframe: str, limit: int = 200) -> pd.DataFrame: + """Fetch fresh candles directly from Binance API (bypass all caching)""" + try: + # Convert symbol format + binance_symbol = symbol.replace('/', '').upper() + + # Convert timeframe + timeframe_map = { + '1s': '1s', '1m': '1m', '1h': '1h', '1d': '1d' + } + binance_timeframe = timeframe_map.get(timeframe, '1m') + + # Direct API call to Binance + url = "https://api.binance.com/api/v3/klines" + params = { + 'symbol': binance_symbol, + 'interval': binance_timeframe, + 'limit': limit + } + + response = requests.get(url, params=params, timeout=5) + response.raise_for_status() + + data = response.json() + + # Convert to DataFrame + df = pd.DataFrame(data, columns=[ + 'timestamp', 'open', 'high', 'low', 'close', 'volume', + 'close_time', 'quote_volume', 'trades', 'taker_buy_base', + 'taker_buy_quote', 'ignore' + ]) + + # Process columns with Sofia timezone + df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms').dt.tz_localize('UTC').dt.tz_convert(self.timezone) + for col in ['open', 'high', 'low', 'close', 'volume']: + df[col] = df[col].astype(float) + + # Keep only OHLCV columns + df = df[['timestamp', 'open', 'high', 'low', 'close', 'volume']] + df = df.sort_values('timestamp').reset_index(drop=True) + + logger.debug(f"📊 Fresh API data: {symbol} {timeframe} - {len(df)} candles") + return df + + except Exception as e: + logger.error(f"Error fetching fresh candles from API: {e}") + return pd.DataFrame() + + def _create_live_chart(self, symbol: str, timeframe: str, main_chart: bool = False): + """Create charts with real-time streaming data""" + try: + with self.data_lock: + data = self.chart_data[symbol][timeframe] + + if data.empty: + # Return loading chart fig = go.Figure() fig.add_annotation( - text=f"Loading real market data for {symbol} {timeframe}...", + text=f"🔄 Loading real-time data for {symbol} {timeframe}...", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False, - font=dict(size=16, color="red") + font=dict(size=16, color="#00ff88") ) fig.update_layout( - title=f"{symbol} {timeframe} - Real Market Data", + title=f"📡 {symbol} {timeframe} - Live Stream", template="plotly_dark", - height=500 if main_chart else 250 + height=600 if main_chart else 300, + paper_bgcolor='#1e1e1e', + plot_bgcolor='#1e1e1e' ) return fig - # Create candlestick chart from real data + # Create real-time chart fig = go.Figure() if main_chart: - # Main chart with candlesticks and volume + # Main chart with candlesticks, volume, and live price fig.add_trace(go.Candlestick( x=data['timestamp'], open=data['open'], @@ -335,127 +402,157 @@ class ScalpingDashboard: decreasing_line_color='#ff4444' )) - # Add volume as secondary plot + # Volume subplot fig.add_trace(go.Bar( x=data['timestamp'], y=data['volume'], name="Volume", yaxis='y2', - opacity=0.3, - marker_color='lightblue' + opacity=0.4, + marker_color='#4CAF50' )) - # Main chart layout + # Current live price line + current_price = self.live_prices.get(symbol, data['close'].iloc[-1] if not data.empty else 0) + if current_price > 0: + fig.add_hline( + y=current_price, + line_dash="dot", + line_color="#FFD700", + line_width=2, + annotation_text=f"LIVE: ${current_price:.2f}", + annotation_position="right" + ) + + # Sofia time in title + sofia_time = datetime.now(self.timezone).strftime("%H:%M:%S %Z") fig.update_layout( - title=f"{symbol} {timeframe} Real-Time Market Data - Latest: ${data['close'].iloc[-1]:.2f}", + title=f"📈 {symbol} {timeframe} LIVE | Sofia Time: {sofia_time} | Current: ${current_price:.2f}", yaxis_title="Price (USDT)", yaxis2=dict(title="Volume", overlaying='y', side='right'), template="plotly_dark", showlegend=False, - height=500 - ) - - # Add current price line - current_price = data['close'].iloc[-1] - fig.add_hline( - y=current_price, - line_dash="dash", - line_color="yellow", - annotation_text=f"${current_price:.2f}", - annotation_position="right" + height=600, + paper_bgcolor='#1e1e1e', + plot_bgcolor='#1e1e1e' ) else: - # Small chart - simple line chart - price_change = ((data['close'].iloc[-1] - data['close'].iloc[0]) / data['close'].iloc[0]) * 100 - line_color = '#00ff88' if price_change >= 0 else '#ff4444' - + # Small chart - line chart with live updates fig.add_trace(go.Scatter( x=data['timestamp'], y=data['close'], mode='lines', name=f"{symbol} {timeframe}", - line=dict(color=line_color, width=2) + line=dict(color='#00ff88', width=2) )) - # Small chart layout + # Live price point + current_price = self.live_prices.get(symbol, data['close'].iloc[-1] if not data.empty else 0) + if current_price > 0: + fig.add_trace(go.Scatter( + x=[data['timestamp'].iloc[-1]] if not data.empty else [datetime.now(self.timezone)], + y=[current_price], + mode='markers', + marker=dict(color='#FFD700', size=8), + name="Live Price" + )) + fig.update_layout( template="plotly_dark", showlegend=False, - margin=dict(l=10, r=10, t=30, b=10), - height=250, - title=f"{symbol} {timeframe}: ${data['close'].iloc[-1]:.2f}" - ) - - # Add price change annotation - change_color = "green" if price_change >= 0 else "red" - fig.add_annotation( - text=f"{price_change:+.2f}%", - xref="paper", yref="paper", - x=0.95, y=0.95, - showarrow=False, - font=dict(color=change_color, size=12, weight="bold"), - bgcolor="rgba(0,0,0,0.7)" + margin=dict(l=10, r=10, t=40, b=10), + height=300, + title=f"📊 {symbol} {timeframe} | ${current_price:.2f}", + paper_bgcolor='#1e1e1e', + plot_bgcolor='#1e1e1e' ) return fig except Exception as e: - logger.error(f"Error creating chart for {symbol} {timeframe}: {e}") + logger.error(f"Error creating live chart for {symbol} {timeframe}: {e}") # Return error chart fig = go.Figure() fig.add_annotation( - text=f"Error loading {symbol} {timeframe}", + text=f"❌ Error loading {symbol} {timeframe}", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False, - font=dict(size=14, color="red") + font=dict(size=14, color="#ff4444") ) fig.update_layout( template="plotly_dark", - height=500 if main_chart else 250 + height=600 if main_chart else 300, + paper_bgcolor='#1e1e1e', + plot_bgcolor='#1e1e1e' ) return fig - def _create_actions_log(self): - """Create trading actions log""" + def _create_live_actions_log(self): + """Create live trading actions log""" if not self.recent_decisions: - return html.P("Waiting for trading signals from real market data...", className="text-muted text-center") + return html.P("⏳ Waiting for live trading signals from real-time stream...", + className="text-muted text-center") log_items = [] - for action in self.recent_decisions[-5:]: # Show last 5 actions + for action in self.recent_decisions[-5:]: + sofia_time = action.timestamp.astimezone(self.timezone).strftime("%H:%M:%S") log_items.append( html.P( - f"🔥 {action.action} {action.symbol} @ ${action.price:.2f} " - f"(Confidence: {action.confidence:.1%}) - Real Market Data", - className="text-center mb-1" + f"🔥 {sofia_time} | {action.action} {action.symbol} @ ${action.price:.2f} " + f"(Confidence: {action.confidence:.1%}) | 📡 LIVE STREAM", + className="text-center mb-1 text-light" ) ) return html.Div(log_items) def add_trading_decision(self, decision: TradingAction): - """Add a new trading decision based on real market data""" + """Add trading decision with Sofia timezone""" + decision.timestamp = decision.timestamp.astimezone(self.timezone) self.recent_decisions.append(decision) + if len(self.recent_decisions) > 50: self.recent_decisions.pop(0) self.scalping_metrics['total_trades'] += 1 self.scalping_metrics['last_action'] = f"{decision.action} {decision.symbol}" - logger.info(f"📊 Added real market trading decision: {decision.action} {decision.symbol} @ ${decision.price:.2f}") + sofia_time = decision.timestamp.strftime("%H:%M:%S %Z") + logger.info(f"🔥 {sofia_time} | Live trading decision: {decision.action} {decision.symbol} @ ${decision.price:.2f}") - def run(self, host: str = '127.0.0.1', port: int = 8050, debug: bool = False): - """Run the real market data dashboard""" - logger.info(f"🚀 Starting Real Market Data Dashboard at http://{host}:{port}") - logger.info("📊 Dashboard Features:") - logger.info(" • Main 1s ETH/USDT chart with real market data") - logger.info(" • 4 small charts: 1m/1h/1d ETH + 1s BTC") - logger.info(" • 500 candles preloaded from Binance API") - logger.info(" • Real-time updates every second") - logger.info(" • NO GENERATED DATA - 100% real market feeds") + def stop_streaming(self): + """Stop all WebSocket streams""" + logger.info("🛑 Stopping real-time WebSocket streams...") + self.streaming = False - self.app.run(host=host, port=port, debug=debug) + for thread in self.websocket_threads: + if thread.is_alive(): + thread.join(timeout=2) + + logger.info("📡 WebSocket streams stopped") + + def run(self, host: str = '127.0.0.1', port: int = 8051, debug: bool = False): + """Run the real-time dashboard""" + try: + logger.info(f"🚀 Starting Real-Time Scalping Dashboard at http://{host}:{port}") + logger.info("📡 Features:") + logger.info(" • WebSocket price streaming (100ms updates)") + logger.info(" • NO CACHED DATA - Always fresh API calls") + logger.info(f" • Sofia timezone: {self.timezone}") + logger.info(" • Ultra-low latency real-time charts") + logger.info(" • Live P&L and trading metrics") + + self.app.run(host=host, port=port, debug=debug) + + except KeyboardInterrupt: + logger.info("👋 Shutting down real-time dashboard...") + finally: + self.stop_streaming() def create_scalping_dashboard(data_provider=None, orchestrator=None): - """Create dashboard instance with real market data""" - return ScalpingDashboard(data_provider, orchestrator) \ No newline at end of file + """Create real-time dashboard instance""" + return RealTimeScalpingDashboard(data_provider, orchestrator) + +# For backward compatibility +ScalpingDashboard = RealTimeScalpingDashboard \ No newline at end of file