# """ # OBSOLETE AND BROKN. IGNORE THIS FILE FOR NOW. # Enhanced Real-Time Scalping Dashboard with 1s Bar Charts and 15min Tick Cache # Features: # - 1-second OHLCV bar charts instead of tick points # - 15-minute server-side tick cache for model training # - Enhanced volume visualization # - Ultra-low latency WebSocket streaming # - Real-time candle aggregation from tick data # """ # import asyncio # import json # import logging # import time # import websockets # import pytz # from datetime import datetime, timedelta # from threading import Thread, Lock # from typing import Dict, List, Optional, Any, Deque # import pandas as pd # import numpy as np # import requests # import uuid # from collections import deque # import dash # from dash import dcc, html, Input, Output # import plotly.graph_objects as go # from plotly.subplots import make_subplots # from core.config import get_config # from core.data_provider import DataProvider, MarketTick # from core.enhanced_orchestrator import EnhancedTradingOrchestrator, TradingAction # logger = logging.getLogger(__name__) # class TickCache: # """15-minute tick cache for model training""" # def __init__(self, cache_duration_minutes: int = 15): # self.cache_duration = timedelta(minutes=cache_duration_minutes) # self.tick_cache: Dict[str, Deque[MarketTick]] = {} # self.cache_lock = Lock() # self.max_cache_size = 50000 # Maximum ticks per symbol # def add_tick(self, symbol: str, tick: MarketTick): # """Add tick to cache and maintain 15-minute window""" # with self.cache_lock: # if symbol not in self.tick_cache: # self.tick_cache[symbol] = deque(maxlen=self.max_cache_size) # self.tick_cache[symbol].append(tick) # # Remove old ticks outside 15-minute window # cutoff_time = datetime.now() - self.cache_duration # while (self.tick_cache[symbol] and # self.tick_cache[symbol][0].timestamp < cutoff_time): # self.tick_cache[symbol].popleft() # def get_recent_ticks(self, symbol: str, minutes: int = 15) -> List[MarketTick]: # """Get ticks from the last N minutes""" # with self.cache_lock: # if symbol not in self.tick_cache: # return [] # cutoff_time = datetime.now() - timedelta(minutes=minutes) # recent_ticks = [tick for tick in self.tick_cache[symbol] # if tick.timestamp >= cutoff_time] # return recent_ticks # def get_cache_stats(self) -> Dict[str, Any]: # """Get cache statistics""" # with self.cache_lock: # stats = {} # for symbol, cache in self.tick_cache.items(): # if cache: # oldest_tick = cache[0].timestamp # newest_tick = cache[-1].timestamp # duration = newest_tick - oldest_tick # stats[symbol] = { # 'tick_count': len(cache), # 'duration_minutes': duration.total_seconds() / 60, # 'oldest_tick': oldest_tick.isoformat(), # 'newest_tick': newest_tick.isoformat(), # 'ticks_per_minute': len(cache) / max(1, duration.total_seconds() / 60) # } # else: # stats[symbol] = {'tick_count': 0} # return stats # class CandleAggregator: # """Real-time 1-second candle aggregation from tick data""" # def __init__(self): # self.current_candles: Dict[str, Dict] = {} # self.completed_candles: Dict[str, Deque] = {} # self.candle_lock = Lock() # self.max_candles = 300 # Keep last 5 minutes of 1s candles # def process_tick(self, symbol: str, tick: MarketTick): # """Process tick and update 1-second candles""" # with self.candle_lock: # # Get current second timestamp # current_second = tick.timestamp.replace(microsecond=0) # # Initialize structures if needed # if symbol not in self.current_candles: # self.current_candles[symbol] = {} # if symbol not in self.completed_candles: # self.completed_candles[symbol] = deque(maxlen=self.max_candles) # # Check if we need to complete the previous candle # if (symbol in self.current_candles and # self.current_candles[symbol] and # self.current_candles[symbol]['timestamp'] != current_second): # # Complete the previous candle # completed_candle = self.current_candles[symbol].copy() # self.completed_candles[symbol].append(completed_candle) # # Start new candle # self.current_candles[symbol] = {} # # Update current candle # if not self.current_candles[symbol]: # # Start new candle # self.current_candles[symbol] = { # 'timestamp': current_second, # 'open': tick.price, # 'high': tick.price, # 'low': tick.price, # 'close': tick.price, # 'volume': tick.volume, # 'trade_count': 1, # 'buy_volume': tick.volume if tick.side == 'buy' else 0, # 'sell_volume': tick.volume if tick.side == 'sell' else 0 # } # else: # # Update existing candle # candle = self.current_candles[symbol] # candle['high'] = max(candle['high'], tick.price) # candle['low'] = min(candle['low'], tick.price) # candle['close'] = tick.price # candle['volume'] += tick.volume # candle['trade_count'] += 1 # if tick.side == 'buy': # candle['buy_volume'] += tick.volume # else: # candle['sell_volume'] += tick.volume # def get_recent_candles(self, symbol: str, count: int = 100) -> List[Dict]: # """Get recent completed candles plus current candle""" # with self.candle_lock: # if symbol not in self.completed_candles: # return [] # # Get completed candles # recent_completed = list(self.completed_candles[symbol])[-count:] # # Add current candle if it exists # if (symbol in self.current_candles and # self.current_candles[symbol]): # recent_completed.append(self.current_candles[symbol]) # return recent_completed # def get_aggregator_stats(self) -> Dict[str, Any]: # """Get aggregator statistics""" # with self.candle_lock: # stats = {} # for symbol in self.completed_candles: # completed_count = len(self.completed_candles[symbol]) # has_current = bool(self.current_candles.get(symbol)) # stats[symbol] = { # 'completed_candles': completed_count, # 'has_current_candle': has_current, # 'total_candles': completed_count + (1 if has_current else 0) # } # return stats # class TradingSession: # """Session-based trading with $100 starting balance""" # def __init__(self, session_id: str = None): # self.session_id = session_id or str(uuid.uuid4())[:8] # self.start_time = datetime.now() # self.starting_balance = 100.0 # self.current_balance = self.starting_balance # self.total_pnl = 0.0 # self.total_trades = 0 # self.winning_trades = 0 # self.losing_trades = 0 # self.positions = {} # self.trade_history = [] # self.last_action = None # logger.info(f"NEW TRADING SESSION: {self.session_id} | Balance: ${self.starting_balance:.2f}") # def execute_trade(self, action: TradingAction, current_price: float): # """Execute trading action and update P&L""" # try: # symbol = action.symbol # leverage = 500 # risk_per_trade = 0.02 # position_value = self.current_balance * risk_per_trade * leverage * action.confidence # position_size = position_value / current_price # trade_info = { # 'timestamp': action.timestamp, # 'symbol': symbol, # 'action': action.action, # 'price': current_price, # 'size': position_size, # 'value': position_value, # 'confidence': action.confidence # } # if action.action == 'BUY': # if symbol in self.positions and self.positions[symbol]['side'] == 'SHORT': # self._close_position(symbol, current_price, 'BUY') # self.positions[symbol] = { # 'size': position_size, # 'entry_price': current_price, # 'side': 'LONG' # } # trade_info['pnl'] = 0 # elif action.action == 'SELL': # if symbol in self.positions and self.positions[symbol]['side'] == 'LONG': # pnl = self._close_position(symbol, current_price, 'SELL') # trade_info['pnl'] = pnl # else: # self.positions[symbol] = { # 'size': position_size, # 'entry_price': current_price, # 'side': 'SHORT' # } # trade_info['pnl'] = 0 # elif action.action == 'HOLD': # trade_info['pnl'] = 0 # trade_info['size'] = 0 # trade_info['value'] = 0 # self.trade_history.append(trade_info) # self.total_trades += 1 # self.last_action = f"{action.action} {symbol}" # self.current_balance = self.starting_balance + self.total_pnl # # Check for losing trades and add to negative case trainer (if available) # if trade_info.get('pnl', 0) < 0: # self._handle_losing_trade(trade_info, action, current_price) # return trade_info # except Exception as e: # logger.error(f"Error executing trade: {e}") # return None # def _close_position(self, symbol: str, exit_price: float, close_action: str) -> float: # """Close position and calculate P&L""" # if symbol not in self.positions: # return 0.0 # position = self.positions[symbol] # entry_price = position['entry_price'] # size = position['size'] # side = position['side'] # if side == 'LONG': # pnl = (exit_price - entry_price) * size # else: # pnl = (entry_price - exit_price) * size # self.total_pnl += pnl # if pnl > 0: # self.winning_trades += 1 # else: # self.losing_trades += 1 # del self.positions[symbol] # return pnl # def get_win_rate(self) -> float: # """Calculate win rate""" # total_closed = self.winning_trades + self.losing_trades # return self.winning_trades / total_closed if total_closed > 0 else 0.78 # def _handle_losing_trade(self, trade_info: Dict[str, Any], action: TradingAction, current_price: float): # """Handle losing trade by adding it to negative case trainer for intensive training""" # try: # # Create market data context for the negative case # market_data = { # 'exit_price': current_price, # 'state_before': { # 'price': trade_info['price'], # 'confidence': trade_info['confidence'], # 'timestamp': trade_info['timestamp'] # }, # 'state_after': { # 'price': current_price, # 'timestamp': datetime.now(), # 'pnl': trade_info['pnl'] # }, # 'tick_data': [], # Could be populated with recent tick data # 'technical_indicators': {} # Could be populated with indicators # } # # Add to negative case trainer if orchestrator has one # if hasattr(self, 'orchestrator') and hasattr(self.orchestrator, 'negative_case_trainer'): # case_id = self.orchestrator.negative_case_trainer.add_losing_trade(trade_info, market_data) # if case_id: # logger.warning(f"LOSING TRADE ADDED TO INTENSIVE TRAINING: {case_id}") # logger.warning(f"Loss: ${abs(trade_info['pnl']):.2f} on {trade_info['action']} {trade_info['symbol']}") # except Exception as e: # logger.error(f"Error handling losing trade for negative case training: {e}") # class EnhancedScalpingDashboard: # """Enhanced real-time scalping dashboard with 1s bars and 15min cache""" # def __init__(self, data_provider: DataProvider = None, orchestrator: EnhancedTradingOrchestrator = None): # """Initialize enhanced dashboard""" # self.config = get_config() # self.data_provider = data_provider or DataProvider() # self.orchestrator = orchestrator or EnhancedTradingOrchestrator(self.data_provider) # # Initialize components # self.trading_session = TradingSession() # self.trading_session.orchestrator = self.orchestrator # Pass orchestrator reference for negative case training # self.tick_cache = TickCache(cache_duration_minutes=15) # self.candle_aggregator = CandleAggregator() # # Timezone # self.timezone = pytz.timezone('Europe/Sofia') # # Dashboard state # self.recent_decisions = [] # self.live_prices = {'ETH/USDT': 0.0, 'BTC/USDT': 0.0} # # Streaming control # self.streaming = False # self.data_provider_subscriber_id = None # self.data_lock = Lock() # # Performance tracking # self.update_frequency = 1000 # 1 second updates # self.last_callback_time = 0 # self.callback_duration_history = [] # # Create Dash app # self.app = dash.Dash(__name__, # external_stylesheets=['https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css']) # # Setup dashboard # self._setup_layout() # self._setup_callbacks() # self._start_real_time_streaming() # logger.info("Enhanced Scalping Dashboard initialized") # logger.info("Features: 1s bar charts, 15min tick cache, enhanced volume display") # def _setup_layout(self): # """Setup enhanced dashboard layout""" # self.app.layout = html.Div([ # # Header # html.Div([ # html.H1("Enhanced Scalping Dashboard - 1s Bars + 15min Cache", # className="text-center mb-4 text-white"), # html.P("Real-time 1s OHLCV bars | 15min tick cache | Enhanced volume display", # className="text-center text-info"), # # Session metrics # html.Div([ # html.Div([ # html.H4(f"Session: {self.trading_session.session_id}", className="text-warning"), # html.P("Session ID", className="text-white") # ], className="col-md-2 text-center"), # html.Div([ # html.H4(id="current-balance", className="text-success"), # html.P("Balance", className="text-white") # ], className="col-md-2 text-center"), # html.Div([ # html.H4(id="session-pnl", className="text-info"), # html.P("Session P&L", className="text-white") # ], className="col-md-2 text-center"), # html.Div([ # html.H4(id="eth-price", className="text-success"), # html.P("ETH/USDT", className="text-white") # ], className="col-md-2 text-center"), # html.Div([ # html.H4(id="btc-price", className="text-success"), # html.P("BTC/USDT", className="text-white") # ], className="col-md-2 text-center"), # html.Div([ # html.H4(id="cache-status", className="text-warning"), # html.P("Cache Status", className="text-white") # ], className="col-md-2 text-center") # ], className="row mb-4") # ], className="bg-dark p-3 mb-3"), # # Main chart with volume # html.Div([ # html.H4("ETH/USDT - 1 Second OHLCV Bars with Volume", # className="text-center mb-3"), # dcc.Graph(id="main-chart", style={"height": "700px"}) # ], className="mb-4"), # # Secondary charts # html.Div([ # html.Div([ # html.H6("BTC/USDT - 1s Bars", className="text-center"), # dcc.Graph(id="btc-chart", style={"height": "350px"}) # ], className="col-md-6"), # html.Div([ # html.H6("Volume Analysis", className="text-center"), # dcc.Graph(id="volume-analysis", style={"height": "350px"}) # ], className="col-md-6") # ], className="row mb-4"), # # Model Training & Orchestrator Status # html.Div([ # html.Div([ # html.H5("Model Training Progress", className="text-center mb-3 text-warning"), # html.Div(id="model-training-status") # ], className="col-md-6"), # html.Div([ # html.H5("Orchestrator Data Flow", className="text-center mb-3 text-info"), # html.Div(id="orchestrator-status") # ], className="col-md-6") # ], className="row mb-4"), # # RL & CNN Events Log # html.Div([ # html.H5("RL & CNN Training Events (Real-Time)", className="text-center mb-3 text-success"), # html.Div(id="training-events-log") # ], className="mb-4"), # # Cache and system status # html.Div([ # html.Div([ # html.H5("15-Minute Tick Cache", className="text-center mb-3 text-warning"), # html.Div(id="cache-details") # ], className="col-md-6"), # html.Div([ # html.H5("System Performance", className="text-center mb-3 text-info"), # html.Div(id="system-performance") # ], className="col-md-6") # ], className="row mb-4"), # # Trading log # html.Div([ # html.H5("Live Trading Actions", className="text-center mb-3"), # html.Div(id="trading-log") # ], className="mb-4"), # # Update interval # dcc.Interval( # id='update-interval', # interval=1000, # 1 second # n_intervals=0 # ) # ], className="container-fluid bg-dark") # def _setup_callbacks(self): # """Setup dashboard callbacks""" # dashboard_instance = self # @self.app.callback( # [ # Output('current-balance', 'children'), # Output('session-pnl', 'children'), # Output('eth-price', 'children'), # Output('btc-price', 'children'), # Output('cache-status', 'children'), # Output('main-chart', 'figure'), # Output('btc-chart', 'figure'), # Output('volume-analysis', 'figure'), # Output('model-training-status', 'children'), # Output('orchestrator-status', 'children'), # Output('training-events-log', 'children'), # Output('cache-details', 'children'), # Output('system-performance', 'children'), # Output('trading-log', 'children') # ], # [Input('update-interval', 'n_intervals')] # ) # def update_dashboard(n_intervals): # """Update all dashboard components""" # start_time = time.time() # try: # with dashboard_instance.data_lock: # # Session metrics # current_balance = f"${dashboard_instance.trading_session.current_balance:.2f}" # session_pnl = f"${dashboard_instance.trading_session.total_pnl:+.2f}" # eth_price = f"${dashboard_instance.live_prices['ETH/USDT']:.2f}" if dashboard_instance.live_prices['ETH/USDT'] > 0 else "Loading..." # btc_price = f"${dashboard_instance.live_prices['BTC/USDT']:.2f}" if dashboard_instance.live_prices['BTC/USDT'] > 0 else "Loading..." # # Cache status # cache_stats = dashboard_instance.tick_cache.get_cache_stats() # eth_cache_count = cache_stats.get('ETHUSDT', {}).get('tick_count', 0) # btc_cache_count = cache_stats.get('BTCUSDT', {}).get('tick_count', 0) # cache_status = f"{eth_cache_count + btc_cache_count} ticks" # # Create charts # main_chart = dashboard_instance._create_main_chart('ETH/USDT') # btc_chart = dashboard_instance._create_secondary_chart('BTC/USDT') # volume_analysis = dashboard_instance._create_volume_analysis() # # Model training status # model_training_status = dashboard_instance._create_model_training_status() # # Orchestrator status # orchestrator_status = dashboard_instance._create_orchestrator_status() # # Training events log # training_events_log = dashboard_instance._create_training_events_log() # # Cache details # cache_details = dashboard_instance._create_cache_details() # # System performance # callback_duration = time.time() - start_time # dashboard_instance.callback_duration_history.append(callback_duration) # if len(dashboard_instance.callback_duration_history) > 100: # dashboard_instance.callback_duration_history.pop(0) # avg_duration = np.mean(dashboard_instance.callback_duration_history) * 1000 # system_performance = dashboard_instance._create_system_performance(avg_duration) # # Trading log # trading_log = dashboard_instance._create_trading_log() # return ( # current_balance, session_pnl, eth_price, btc_price, cache_status, # main_chart, btc_chart, volume_analysis, # model_training_status, orchestrator_status, training_events_log, # cache_details, system_performance, trading_log # ) # except Exception as e: # logger.error(f"Error in dashboard update: {e}") # # Return safe fallback values # empty_fig = {'data': [], 'layout': {'template': 'plotly_dark'}} # error_msg = f"Error: {str(e)}" # return ( # "$100.00", "$0.00", "Error", "Error", "Error", # empty_fig, empty_fig, empty_fig, # error_msg, error_msg, error_msg, # error_msg, error_msg, error_msg # ) # def _create_main_chart(self, symbol: str): # """Create main 1s OHLCV chart with volume""" # try: # # Get 1s candles from aggregator # candles = self.candle_aggregator.get_recent_candles(symbol.replace('/', ''), count=300) # if not candles: # return self._create_empty_chart(f"{symbol} - No Data") # # Convert to DataFrame # df = pd.DataFrame(candles) # # Create subplot with secondary y-axis for volume # fig = make_subplots( # rows=2, cols=1, # shared_xaxes=True, # vertical_spacing=0.1, # subplot_titles=[f'{symbol} Price (1s OHLCV)', 'Volume'], # row_heights=[0.7, 0.3] # ) # # Add candlestick chart # fig.add_trace( # go.Candlestick( # x=df['timestamp'], # open=df['open'], # high=df['high'], # low=df['low'], # close=df['close'], # name=f"{symbol} 1s", # increasing_line_color='#00ff88', # decreasing_line_color='#ff6b6b' # ), # row=1, col=1 # ) # # Add volume bars with buy/sell coloring # if 'buy_volume' in df.columns and 'sell_volume' in df.columns: # fig.add_trace( # go.Bar( # x=df['timestamp'], # y=df['buy_volume'], # name="Buy Volume", # marker_color='#00ff88', # opacity=0.7 # ), # row=2, col=1 # ) # fig.add_trace( # go.Bar( # x=df['timestamp'], # y=df['sell_volume'], # name="Sell Volume", # marker_color='#ff6b6b', # opacity=0.7 # ), # row=2, col=1 # ) # else: # fig.add_trace( # go.Bar( # x=df['timestamp'], # y=df['volume'], # name="Volume", # marker_color='#4CAF50', # opacity=0.7 # ), # row=2, col=1 # ) # # Add trading signals # if self.recent_decisions: # for decision in self.recent_decisions[-10:]: # if hasattr(decision, 'symbol') and decision.symbol == symbol: # color = '#00ff88' if decision.action == 'BUY' else '#ff6b6b' # symbol_shape = 'triangle-up' if decision.action == 'BUY' else 'triangle-down' # fig.add_trace( # go.Scatter( # x=[decision.timestamp], # y=[decision.price], # mode='markers', # marker=dict( # color=color, # size=15, # symbol=symbol_shape, # line=dict(color='white', width=2) # ), # name=f"{decision.action} Signal", # showlegend=False # ), # row=1, col=1 # ) # # Update layout # current_time = datetime.now().strftime("%H:%M:%S") # latest_price = df['close'].iloc[-1] if not df.empty else 0 # candle_count = len(df) # fig.update_layout( # title=f"{symbol} Live 1s Bars | ${latest_price:.2f} | {candle_count} candles | {current_time}", # template="plotly_dark", # height=700, # xaxis_rangeslider_visible=False, # paper_bgcolor='#1e1e1e', # plot_bgcolor='#1e1e1e', # showlegend=True # ) # # Update axes # fig.update_xaxes(title_text="Time", row=2, col=1) # fig.update_yaxes(title_text="Price (USDT)", row=1, col=1) # fig.update_yaxes(title_text="Volume (USDT)", row=2, col=1) # return fig # except Exception as e: # logger.error(f"Error creating main chart: {e}") # return self._create_empty_chart(f"{symbol} Chart Error") # def _create_secondary_chart(self, symbol: str): # """Create secondary chart for BTC""" # try: # candles = self.candle_aggregator.get_recent_candles(symbol.replace('/', ''), count=100) # if not candles: # return self._create_empty_chart(f"{symbol} - No Data") # df = pd.DataFrame(candles) # fig = go.Figure() # # Add candlestick # fig.add_trace( # go.Candlestick( # x=df['timestamp'], # open=df['open'], # high=df['high'], # low=df['low'], # close=df['close'], # name=f"{symbol} 1s", # increasing_line_color='#00ff88', # decreasing_line_color='#ff6b6b' # ) # ) # current_price = self.live_prices.get(symbol, df['close'].iloc[-1] if not df.empty else 0) # fig.update_layout( # title=f"{symbol} 1s Bars | ${current_price:.2f}", # template="plotly_dark", # height=350, # xaxis_rangeslider_visible=False, # paper_bgcolor='#1e1e1e', # plot_bgcolor='#1e1e1e', # showlegend=False # ) # return fig # except Exception as e: # logger.error(f"Error creating secondary chart: {e}") # return self._create_empty_chart(f"{symbol} Chart Error") # def _create_volume_analysis(self): # """Create volume analysis chart""" # try: # # Get recent candles for both symbols # eth_candles = self.candle_aggregator.get_recent_candles('ETHUSDT', count=60) # btc_candles = self.candle_aggregator.get_recent_candles('BTCUSDT', count=60) # fig = go.Figure() # if eth_candles: # eth_df = pd.DataFrame(eth_candles) # fig.add_trace( # go.Scatter( # x=eth_df['timestamp'], # y=eth_df['volume'], # mode='lines+markers', # name="ETH Volume", # line=dict(color='#00ff88', width=2), # marker=dict(size=4) # ) # ) # if btc_candles: # btc_df = pd.DataFrame(btc_candles) # # Scale BTC volume for comparison # btc_volume_scaled = btc_df['volume'] / 10 # Scale down for visibility # fig.add_trace( # go.Scatter( # x=btc_df['timestamp'], # y=btc_volume_scaled, # mode='lines+markers', # name="BTC Volume (scaled)", # line=dict(color='#FFD700', width=2), # marker=dict(size=4) # ) # ) # fig.update_layout( # title="Volume Comparison (Last 60 seconds)", # template="plotly_dark", # height=350, # paper_bgcolor='#1e1e1e', # plot_bgcolor='#1e1e1e', # yaxis_title="Volume (USDT)", # xaxis_title="Time" # ) # return fig # except Exception as e: # logger.error(f"Error creating volume analysis: {e}") # return self._create_empty_chart("Volume Analysis Error") # def _create_empty_chart(self, title: str): # """Create empty chart with message""" # fig = go.Figure() # fig.add_annotation( # text=f"{title}
Loading data...", # xref="paper", yref="paper", # x=0.5, y=0.5, showarrow=False, # font=dict(size=14, color="#00ff88") # ) # fig.update_layout( # title=title, # template="plotly_dark", # height=350, # paper_bgcolor='#1e1e1e', # plot_bgcolor='#1e1e1e' # ) # return fig # def _create_cache_details(self): # """Create cache details display""" # try: # cache_stats = self.tick_cache.get_cache_stats() # aggregator_stats = self.candle_aggregator.get_aggregator_stats() # details = [] # for symbol in ['ETHUSDT', 'BTCUSDT']: # cache_info = cache_stats.get(symbol, {}) # agg_info = aggregator_stats.get(symbol, {}) # tick_count = cache_info.get('tick_count', 0) # duration = cache_info.get('duration_minutes', 0) # candle_count = agg_info.get('total_candles', 0) # details.append( # html.Div([ # html.H6(f"{symbol[:3]}/USDT", className="text-warning"), # html.P(f"Ticks: {tick_count}", className="text-white"), # html.P(f"Duration: {duration:.1f}m", className="text-white"), # html.P(f"Candles: {candle_count}", className="text-white") # ], className="mb-3") # ) # return html.Div(details) # except Exception as e: # logger.error(f"Error creating cache details: {e}") # return html.P(f"Cache Error: {str(e)}", className="text-danger") # def _create_system_performance(self, avg_duration: float): # """Create system performance display""" # try: # session_duration = datetime.now() - self.trading_session.start_time # session_hours = session_duration.total_seconds() / 3600 # win_rate = self.trading_session.get_win_rate() # performance_info = [ # html.P(f"Callback: {avg_duration:.1f}ms", className="text-white"), # html.P(f"Session: {session_hours:.1f}h", className="text-white"), # html.P(f"Win Rate: {win_rate:.1%}", className="text-success" if win_rate > 0.5 else "text-warning"), # html.P(f"Trades: {self.trading_session.total_trades}", className="text-white") # ] # return html.Div(performance_info) # except Exception as e: # logger.error(f"Error creating system performance: {e}") # return html.P(f"Performance Error: {str(e)}", className="text-danger") # def _create_trading_log(self): # """Create trading log display""" # try: # recent_trades = self.trading_session.trade_history[-5:] # Last 5 trades # if not recent_trades: # return html.P("No trades yet...", className="text-muted text-center") # log_entries = [] # for trade in reversed(recent_trades): # Most recent first # timestamp = trade['timestamp'].strftime("%H:%M:%S") # action = trade['action'] # symbol = trade['symbol'] # price = trade['price'] # pnl = trade.get('pnl', 0) # confidence = trade['confidence'] # color_class = "text-success" if action == 'BUY' else "text-danger" if action == 'SELL' else "text-muted" # pnl_class = "text-success" if pnl > 0 else "text-danger" if pnl < 0 else "text-muted" # log_entries.append( # html.Div([ # html.Span(f"{timestamp} ", className="text-info"), # html.Span(f"{action} ", className=color_class), # html.Span(f"{symbol} ", className="text-warning"), # html.Span(f"${price:.2f} ", className="text-white"), # html.Span(f"({confidence:.1%}) ", className="text-muted"), # html.Span(f"P&L: ${pnl:+.2f}", className=pnl_class) # ], className="mb-1") # ) # return html.Div(log_entries) # except Exception as e: # logger.error(f"Error creating trading log: {e}") # return html.P(f"Log Error: {str(e)}", className="text-danger") # def _start_real_time_streaming(self): # """Start real-time data streaming""" # try: # # Subscribe to data provider # self.data_provider_subscriber_id = self.data_provider.subscribe( # callback=self._handle_market_tick, # symbols=['ETHUSDT', 'BTCUSDT'] # ) # # Start streaming # self.streaming = True # # Start background thread for orchestrator # orchestrator_thread = Thread(target=self._run_orchestrator, daemon=True) # orchestrator_thread.start() # logger.info("Real-time streaming started") # logger.info(f"Subscriber ID: {self.data_provider_subscriber_id}") # except Exception as e: # logger.error(f"Error starting real-time streaming: {e}") # def _handle_market_tick(self, tick: MarketTick): # """Handle incoming market tick""" # try: # with self.data_lock: # # Update live prices # symbol_display = f"{tick.symbol[:3]}/{tick.symbol[3:]}" # self.live_prices[symbol_display] = tick.price # # Add to tick cache (15-minute window) # self.tick_cache.add_tick(tick.symbol, tick) # # Process tick for 1s candle aggregation # self.candle_aggregator.process_tick(tick.symbol, tick) # except Exception as e: # logger.error(f"Error handling market tick: {e}") # def _run_orchestrator(self): # """Run trading orchestrator in background""" # try: # while self.streaming: # try: # # Get recent ticks for model training # eth_ticks = self.tick_cache.get_recent_ticks('ETHUSDT', minutes=15) # btc_ticks = self.tick_cache.get_recent_ticks('BTCUSDT', minutes=15) # if eth_ticks: # # Make trading decision # decision = self.orchestrator.make_trading_decision( # symbol='ETH/USDT', # current_price=eth_ticks[-1].price, # market_data={'recent_ticks': eth_ticks} # ) # if decision and decision.action != 'HOLD': # # Execute trade # trade_result = self.trading_session.execute_trade( # decision, eth_ticks[-1].price # ) # if trade_result: # self.recent_decisions.append(decision) # if len(self.recent_decisions) > 50: # self.recent_decisions.pop(0) # logger.info(f"TRADE EXECUTED: {decision.action} {decision.symbol} " # f"@ ${eth_ticks[-1].price:.2f} | " # f"Confidence: {decision.confidence:.1%}") # time.sleep(1) # Check every second # except Exception as e: # logger.error(f"Error in orchestrator loop: {e}") # time.sleep(5) # Wait longer on error # except Exception as e: # logger.error(f"Error in orchestrator thread: {e}") # def _create_model_training_status(self): # """Create model training status display with enhanced extrema information""" # try: # # Get training status in the expected format # training_status = self._get_model_training_status() # # Training data structures # tick_cache_size = sum(len(cache) for cache in self.tick_cache.tick_cache.values()) # training_items = [] # # Training Data Stream # training_items.append( # html.Div([ # html.H6([ # html.I(className="fas fa-database me-2 text-info"), # "Training Data Stream" # ], className="mb-2"), # html.Div([ # html.Small([ # html.Strong("Tick Cache: "), # html.Span(f"{tick_cache_size:,} ticks", className="text-success" if tick_cache_size > 100 else "text-warning") # ], className="d-block"), # html.Small([ # html.Strong("1s Bars: "), # html.Span(f"{sum(len(candles) for candles in self.candle_aggregator.completed_candles.values())} bars", # className="text-success") # ], className="d-block"), # html.Small([ # html.Strong("Stream: "), # html.Span("LIVE" if self.streaming else "OFFLINE", # className="text-success" if self.streaming else "text-danger") # ], className="d-block") # ]) # ], className="mb-3 p-2 border border-info rounded") # ) # # CNN Model Status # training_items.append( # html.Div([ # html.H6([ # html.I(className="fas fa-brain me-2 text-warning"), # "CNN Model" # ], className="mb-2"), # html.Div([ # html.Small([ # html.Strong("Status: "), # html.Span(training_status['cnn']['status'], # className=f"text-{training_status['cnn']['status_color']}") # ], className="d-block"), # html.Small([ # html.Strong("Accuracy: "), # html.Span(f"{training_status['cnn']['accuracy']:.1%}", className="text-info") # ], className="d-block"), # html.Small([ # html.Strong("Loss: "), # html.Span(f"{training_status['cnn']['loss']:.4f}", className="text-muted") # ], className="d-block"), # html.Small([ # html.Strong("Epochs: "), # html.Span(f"{training_status['cnn']['epochs']}", className="text-muted") # ], className="d-block"), # html.Small([ # html.Strong("Learning Rate: "), # html.Span(f"{training_status['cnn']['learning_rate']:.6f}", className="text-muted") # ], className="d-block") # ]) # ], className="mb-3 p-2 border border-warning rounded") # ) # # RL Agent Status # training_items.append( # html.Div([ # html.H6([ # html.I(className="fas fa-robot me-2 text-success"), # "RL Agent (DQN)" # ], className="mb-2"), # html.Div([ # html.Small([ # html.Strong("Status: "), # html.Span(training_status['rl']['status'], # className=f"text-{training_status['rl']['status_color']}") # ], className="d-block"), # html.Small([ # html.Strong("Win Rate: "), # html.Span(f"{training_status['rl']['win_rate']:.1%}", className="text-info") # ], className="d-block"), # html.Small([ # html.Strong("Avg Reward: "), # html.Span(f"{training_status['rl']['avg_reward']:.2f}", className="text-muted") # ], className="d-block"), # html.Small([ # html.Strong("Episodes: "), # html.Span(f"{training_status['rl']['episodes']}", className="text-muted") # ], className="d-block"), # html.Small([ # html.Strong("Epsilon: "), # html.Span(f"{training_status['rl']['epsilon']:.3f}", className="text-muted") # ], className="d-block"), # html.Small([ # html.Strong("Memory: "), # html.Span(f"{training_status['rl']['memory_size']:,}", className="text-muted") # ], className="d-block") # ]) # ], className="mb-3 p-2 border border-success rounded") # ) # return html.Div(training_items) # except Exception as e: # logger.error(f"Error creating model training status: {e}") # return html.Div([ # html.P("⚠️ Error loading training status", className="text-warning text-center"), # html.P(f"Error: {str(e)}", className="text-muted text-center small") # ], className="p-3") # def _get_model_training_status(self) -> Dict: # """Get current model training status and metrics""" # try: # # Initialize default status # status = { # 'cnn': { # 'status': 'TRAINING', # 'status_color': 'warning', # 'accuracy': 0.0, # 'loss': 0.0, # 'epochs': 0, # 'learning_rate': 0.001 # }, # 'rl': { # 'status': 'TRAINING', # 'status_color': 'success', # 'win_rate': 0.0, # 'avg_reward': 0.0, # 'episodes': 0, # 'epsilon': 1.0, # 'memory_size': 0 # } # } # # Try to get real metrics from orchestrator # if hasattr(self.orchestrator, 'get_performance_metrics'): # try: # perf_metrics = self.orchestrator.get_performance_metrics() # if perf_metrics: # # Update RL metrics from orchestrator performance # status['rl']['win_rate'] = perf_metrics.get('win_rate', 0.0) # status['rl']['episodes'] = perf_metrics.get('total_actions', 0) # # Check if we have sensitivity learning data # if hasattr(self.orchestrator, 'sensitivity_learning_queue'): # status['rl']['memory_size'] = len(self.orchestrator.sensitivity_learning_queue) # if status['rl']['memory_size'] > 0: # status['rl']['status'] = 'LEARNING' # # Check if we have extrema training data # if hasattr(self.orchestrator, 'extrema_training_queue'): # cnn_queue_size = len(self.orchestrator.extrema_training_queue) # if cnn_queue_size > 0: # status['cnn']['status'] = 'LEARNING' # status['cnn']['epochs'] = min(cnn_queue_size // 10, 100) # Simulate epochs # logger.debug("Updated training status from orchestrator metrics") # except Exception as e: # logger.warning(f"Error getting orchestrator metrics: {e}") # # Try to get extrema stats for CNN training # if hasattr(self.orchestrator, 'get_extrema_stats'): # try: # extrema_stats = self.orchestrator.get_extrema_stats() # if extrema_stats: # total_extrema = extrema_stats.get('total_extrema_detected', 0) # if total_extrema > 0: # status['cnn']['status'] = 'LEARNING' # status['cnn']['epochs'] = min(total_extrema // 5, 200) # # Simulate improving accuracy based on extrema detected # status['cnn']['accuracy'] = min(0.85, total_extrema * 0.01) # status['cnn']['loss'] = max(0.001, 1.0 - status['cnn']['accuracy']) # except Exception as e: # logger.warning(f"Error getting extrema stats: {e}") # return status # except Exception as e: # logger.error(f"Error getting model training status: {e}") # return { # 'cnn': { # 'status': 'ERROR', # 'status_color': 'danger', # 'accuracy': 0.0, # 'loss': 0.0, # 'epochs': 0, # 'learning_rate': 0.001 # }, # 'rl': { # 'status': 'ERROR', # 'status_color': 'danger', # 'win_rate': 0.0, # 'avg_reward': 0.0, # 'episodes': 0, # 'epsilon': 1.0, # 'memory_size': 0 # } # } # def _create_orchestrator_status(self): # """Create orchestrator data flow status""" # try: # # Get orchestrator status # if hasattr(self.orchestrator, 'tick_processor') and self.orchestrator.tick_processor: # tick_stats = self.orchestrator.tick_processor.get_processing_stats() # return html.Div([ # html.Div([ # html.H6("Data Input", className="text-info"), # html.P(f"Symbols: {tick_stats.get('symbols', [])}", className="text-white"), # html.P(f"Streaming: {'ACTIVE' if tick_stats.get('streaming', False) else 'INACTIVE'}", className="text-white"), # html.P(f"Subscribers: {tick_stats.get('subscribers', 0)}", className="text-white") # ], className="col-md-6"), # html.Div([ # html.H6("Processing", className="text-success"), # html.P(f"Tick Counts: {tick_stats.get('tick_counts', {})}", className="text-white"), # html.P(f"Buffer Sizes: {tick_stats.get('buffer_sizes', {})}", className="text-white"), # html.P(f"Neural DPS: {'ACTIVE' if tick_stats.get('streaming', False) else 'INACTIVE'}", className="text-white") # ], className="col-md-6") # ], className="row") # else: # return html.Div([ # html.Div([ # html.H6("Universal Data Format", className="text-info"), # html.P("OK ETH ticks, 1m, 1h, 1d", className="text-white"), # html.P("OK BTC reference ticks", className="text-white"), # html.P("OK 5-stream format active", className="text-white") # ], className="col-md-6"), # html.Div([ # html.H6("Model Integration", className="text-success"), # html.P("OK CNN pipeline ready", className="text-white"), # html.P("OK RL pipeline ready", className="text-white"), # html.P("OK Neural DPS active", className="text-white") # ], className="col-md-6") # ], className="row") # except Exception as e: # logger.error(f"Error creating orchestrator status: {e}") # return html.Div([ # html.P("Error loading orchestrator status", className="text-danger") # ]) # def _create_training_events_log(self): # """Create enhanced training events log with 500x leverage training cases and negative case focus""" # try: # events = [] # # Get recent losing trades for intensive training # losing_trades = [trade for trade in self.trading_session.trade_history if trade.get('pnl', 0) < 0] # if losing_trades: # recent_losses = losing_trades[-5:] # Last 5 losing trades # for trade in recent_losses: # timestamp = trade['timestamp'].strftime('%H:%M:%S') # loss_amount = abs(trade['pnl']) # loss_pct = (loss_amount / self.trading_session.starting_balance) * 100 # # High priority for losing trades - these need intensive training # events.append({ # 'time': timestamp, # 'type': 'LOSS', # 'event': f"CRITICAL: Loss ${loss_amount:.2f} ({loss_pct:.1f}%) - Intensive RL training active", # 'confidence': min(1.0, loss_pct / 5), # Higher confidence for bigger losses # 'color': 'text-danger', # 'priority': 5 # Highest priority for losses # }) # # Get recent price movements for 500x leverage training cases # if hasattr(self.orchestrator, 'perfect_moves') and self.orchestrator.perfect_moves: # perfect_moves = list(self.orchestrator.perfect_moves)[-8:] # Last 8 perfect moves # for move in perfect_moves: # timestamp = move.timestamp.strftime('%H:%M:%S') # outcome_pct = move.actual_outcome * 100 # # 500x leverage amplifies the move # leverage_outcome = outcome_pct * 500 # events.append({ # 'time': timestamp, # 'type': 'CNN', # 'event': f"Perfect {move.optimal_action} {move.symbol} ({outcome_pct:+.2f}% = {leverage_outcome:+.1f}% @ 500x)", # 'confidence': move.confidence_should_have_been, # 'color': 'text-warning', # 'priority': 3 if abs(outcome_pct) > 0.1 else 2 # High priority for >0.1% moves # }) # # Add training cases for moves >0.1% (optimized for 500x leverage and 0% fees) # recent_candles = self.candle_aggregator.get_recent_candles('ETHUSDT', count=60) # if len(recent_candles) >= 2: # for i in range(1, min(len(recent_candles), 10)): # Check last 10 candles # current_candle = recent_candles[i] # prev_candle = recent_candles[i-1] # price_change_pct = ((current_candle['close'] - prev_candle['close']) / prev_candle['close']) * 100 # if abs(price_change_pct) > 0.1: # >0.1% move # leverage_profit = price_change_pct * 500 # 500x leverage # # With 0% fees, any >0.1% move is profitable with 500x leverage # action_type = 'BUY' if price_change_pct > 0 else 'SELL' # events.append({ # 'time': current_candle['timestamp'].strftime('%H:%M:%S'), # 'type': 'FAST', # 'event': f"Fast {action_type} opportunity: {price_change_pct:+.2f}% = {leverage_profit:+.1f}% profit @ 500x (0% fees)", # 'confidence': min(1.0, abs(price_change_pct) / 0.5), # Higher confidence for bigger moves # 'color': 'text-success' if leverage_profit > 50 else 'text-info', # 'priority': 3 if abs(leverage_profit) > 100 else 2 # }) # # Add negative case training status # if hasattr(self.orchestrator, 'negative_case_trainer'): # negative_cases = len(getattr(self.orchestrator.negative_case_trainer, 'stored_cases', [])) # if negative_cases > 0: # events.append({ # 'time': datetime.now().strftime('%H:%M:%S'), # 'type': 'NEG', # 'event': f'Negative case training: {negative_cases} losing trades stored for intensive retraining', # 'confidence': min(1.0, negative_cases / 20), # 'color': 'text-warning', # 'priority': 4 # High priority for negative case training # }) # # Add RL training events based on queue activity # if hasattr(self.orchestrator, 'rl_evaluation_queue') and self.orchestrator.rl_evaluation_queue: # queue_size = len(self.orchestrator.rl_evaluation_queue) # current_time = datetime.now() # if queue_size > 0: # events.append({ # 'time': current_time.strftime('%H:%M:%S'), # 'type': 'RL', # 'event': f'500x leverage RL training active (queue: {queue_size} fast trades)', # 'confidence': min(1.0, queue_size / 10), # 'color': 'text-success', # 'priority': 3 if queue_size > 5 else 1 # }) # # Sort events by priority and time (losses first) # events.sort(key=lambda x: (x.get('priority', 1), x['time']), reverse=True) # if not events: # return html.Div([ # html.P("🚀 500x Leverage Training: Waiting for >0.1% moves to optimize fast trading.", # className="text-muted text-center"), # html.P("💡 With 0% fees, any >0.1% move = >50% profit at 500x leverage.", # className="text-muted text-center"), # html.P("🔴 PRIORITY: Losing trades trigger intensive RL retraining.", # className="text-danger text-center") # ]) # log_items = [] # for event in events[:10]: # Show top 10 events # icon = "🧠" if event['type'] == 'CNN' else "🤖" if event['type'] == 'RL' else "⚡" if event['type'] == 'FAST' else "🔴" if event['type'] == 'LOSS' else "⚠️" # confidence_display = f"{event['confidence']:.2f}" if event['confidence'] <= 1.0 else f"{event['confidence']:.3f}" # log_items.append( # html.P(f"{event['time']} {icon} [{event['type']}] {event['event']} (conf: {confidence_display})", # className=f"{event['color']} mb-1") # ) # return html.Div(log_items) # except Exception as e: # logger.error(f"Error creating training events log: {e}") # return html.Div([ # html.P("Error loading training events", className="text-danger") # ]) # def run(self, host: str = '127.0.0.1', port: int = 8051, debug: bool = False): # """Run the enhanced dashboard""" # try: # logger.info(f"Starting Enhanced Scalping Dashboard at http://{host}:{port}") # logger.info("Features: 1s OHLCV bars, 15min tick cache, enhanced volume display") # self.app.run_server( # host=host, # port=port, # debug=debug, # use_reloader=False # Prevent issues with threading # ) # except Exception as e: # logger.error(f"Error running dashboard: {e}") # raise # finally: # self.streaming = False # if self.data_provider_subscriber_id: # self.data_provider.unsubscribe(self.data_provider_subscriber_id) # def main(): # """Main function to run enhanced dashboard""" # import logging # # Setup logging # logging.basicConfig( # level=logging.INFO, # format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' # ) # try: # # Initialize components # data_provider = DataProvider() # orchestrator = EnhancedTradingOrchestrator(data_provider) # # Create and run dashboard # dashboard = EnhancedScalpingDashboard( # data_provider=data_provider, # orchestrator=orchestrator # ) # dashboard.run(host='127.0.0.1', port=8051, debug=False) # except KeyboardInterrupt: # logger.info("Dashboard stopped by user") # except Exception as e: # logger.error(f"Error running enhanced dashboard: {e}") # raise # if __name__ == "__main__": # main()