diff --git a/core/orchestrator.py b/core/orchestrator.py index 3864e7a..1433c55 100644 --- a/core/orchestrator.py +++ b/core/orchestrator.py @@ -385,7 +385,7 @@ class TradingOrchestrator: logger.info(f"Transformer checkpoint loaded: {metadata.checkpoint_id}") except Exception as e: logger.debug(f"No transformer checkpoint found: {e}") - + if not checkpoint_loaded: self.model_states['transformer']['checkpoint_loaded'] = False self.model_states['transformer']['checkpoint_filename'] = 'none (fresh start)' @@ -1182,137 +1182,156 @@ class TradingOrchestrator: logger.debug(f"Error getting current price for {symbol}: {e}") return 0.0 - # Get standard feature matrix for this timeframe - feature_matrix = self.data_provider.get_feature_matrix( - symbol=symbol, - timeframes=[timeframe], - window_size=getattr(model, 'window_size', 20) - ) - - # Enhance with COB feature matrix if available - enhanced_features = feature_matrix - if feature_matrix is not None and self.cob_integration: - try: - # Get COB feature matrix (5-minute history) - cob_feature_matrix = self.get_cob_feature_matrix(symbol, sequence_length=60) - - if cob_feature_matrix is not None: - # Take the latest COB features to augment the standard features - latest_cob_features = cob_feature_matrix[-1:, :] # Shape: (1, 400) - - # Resize to match the feature matrix timeframe dimension - timeframe_count = feature_matrix.shape[0] - cob_features_expanded = np.repeat(latest_cob_features, timeframe_count, axis=0) - - # Concatenate COB features with standard features - # Standard features shape: (timeframes, window_size, features) - # COB features shape: (timeframes, 400) - # We'll add COB as additional features to each timeframe - window_size = feature_matrix.shape[1] - cob_features_reshaped = cob_features_expanded.reshape(timeframe_count, 1, 400) - cob_features_tiled = np.tile(cob_features_reshaped, (1, window_size, 1)) - - # Concatenate along feature dimension - enhanced_features = np.concatenate([feature_matrix, cob_features_tiled], axis=2) - - logger.debug(f"Enhanced CNN features with COB data for {symbol}: " - f"{feature_matrix.shape} + COB -> {enhanced_features.shape}") - - except Exception as cob_error: - logger.debug(f"Could not enhance CNN features with COB data: {cob_error}") - enhanced_features = feature_matrix - - # Add extrema features if available - if self.extrema_trainer: - try: - extrema_features = self.extrema_trainer.get_context_features_for_model(symbol) - if extrema_features is not None: - # Reshape and tile to match the enhanced_features shape - extrema_features = extrema_features.flatten() - tiled_extrema = np.tile(extrema_features, (enhanced_features.shape[0], enhanced_features.shape[1], 1)) - enhanced_features = np.concatenate([enhanced_features, tiled_extrema], axis=2) - logger.debug(f"Enhanced CNN features with Extrema data for {symbol}") - except Exception as extrema_error: - logger.debug(f"Could not enhance CNN features with Extrema data: {extrema_error}") - - if enhanced_features is not None: - # Get CNN prediction - use the actual underlying model - try: - # Ensure features are properly shaped and limited - if isinstance(enhanced_features, np.ndarray): - # Flatten and limit features to prevent shape mismatches - enhanced_features = enhanced_features.flatten() - if len(enhanced_features) > 100: # Limit to 100 features - enhanced_features = enhanced_features[:100] - elif len(enhanced_features) < 100: # Pad with zeros - padded = np.zeros(100) - padded[:len(enhanced_features)] = enhanced_features - enhanced_features = padded - - if hasattr(model.model, 'act'): - # Use the CNN's act method - action_result = model.model.act(enhanced_features, explore=False) - if isinstance(action_result, tuple): - action_idx, confidence = action_result - else: - action_idx = action_result - confidence = 0.7 # Default confidence - - # Convert to action probabilities - action_probs = [0.1, 0.1, 0.8] # Default distribution - action_probs[action_idx] = confidence - else: - # Fallback to generic predict method - action_probs, confidence = model.predict(enhanced_features) - except Exception as e: - logger.warning(f"CNN prediction failed: {e}") - action_probs, confidence = None, None - - if action_probs is not None: - # Convert to prediction object - action_names = ['SELL', 'HOLD', 'BUY'] - best_action_idx = np.argmax(action_probs) - best_action = action_names[best_action_idx] - - prediction = Prediction( - action=best_action, - confidence=float(confidence) if confidence is not None else float(action_probs[best_action_idx]), - probabilities={name: float(prob) for name, prob in zip(action_names, action_probs)}, - timeframe=timeframe, - timestamp=datetime.now(), - model_name=model.name, - metadata={ - 'timeframe_specific': True, - 'cob_enhanced': enhanced_features is not feature_matrix, - 'feature_shape': str(enhanced_features.shape) - } - ) - - predictions.append(prediction) - - # Capture CNN prediction for dashboard visualization - current_price = self._get_current_price(symbol) - if current_price: - direction = best_action_idx # 0=SELL, 1=HOLD, 2=BUY - pred_confidence = float(confidence) if confidence is not None else float(action_probs[best_action_idx]) - predicted_price = current_price * (1 + (pred_confidence * 0.01 if best_action == 'BUY' else -pred_confidence * 0.01 if best_action == 'SELL' else 0)) - self.capture_cnn_prediction(symbol, int(direction), pred_confidence, current_price, predicted_price) - - except Exception as e: - logger.error(f"Error getting CNN predictions: {e}") - - return predictions - - async def _get_rl_prediction(self, model: RLAgentInterface, symbol: str) -> Optional[Prediction]: - """Get prediction from RL agent""" + async def _get_cob_rl_prediction(self, model: COBRLModelInterface, symbol: str) -> Optional[Prediction]: + """Get prediction from COB RL model""" try: - # Get current state for RL agent - state = self._get_rl_state(symbol) - if state is None: + # Get COB state from current market data + cob_state = self._get_cob_state(symbol) + if cob_state is None: return None - # Get RL agent's action, confidence, and q_values from the underlying model + # Get prediction from COB RL model if hasattr(model.model, 'act_with_confidence'): + result = model.model.act_with_confidence(cob_state) + if len(result) == 2: + action_idx, confidence = result + else: + action_idx = result[0] if isinstance(result, (list, tuple)) else result + confidence = 0.6 + else: + action_idx = model.model.act(cob_state) + confidence = 0.6 + + # Convert to action name + action_names = ['BUY', 'SELL', 'HOLD'] + if 0 <= action_idx < len(action_names): + action = action_names[action_idx] + else: + return None + + # Store prediction in database for tracking + if (hasattr(self, 'enhanced_training_system') and + self.enhanced_training_system and + hasattr(self.enhanced_training_system, 'store_model_prediction')): + + current_price = self._get_current_price_safe(symbol) + if current_price > 0: + prediction_id = self.enhanced_training_system.store_model_prediction( + model_name=f"COB_RL_{model.model_name}" if hasattr(model, 'model_name') else "COB_RL", + symbol=symbol, + prediction_type=action, + confidence=confidence, + current_price=current_price + ) + logger.debug(f"Stored COB RL prediction {prediction_id} for {symbol}") + + # Create prediction object + prediction = Prediction( + model_name=f"COB_RL_{model.model_name}" if hasattr(model, 'model_name') else "COB_RL", + symbol=symbol, + signal=action, + confidence=confidence, + reasoning=f"COB RL model prediction based on order book imbalance", + features=cob_state.tolist() if isinstance(cob_state, np.ndarray) else [], + metadata={ + 'action_idx': action_idx, + 'cob_state_size': len(cob_state) if cob_state is not None else 0 + } + ) + + return prediction + + except Exception as e: + logger.error(f"Error getting COB RL prediction for {symbol}: {e}") + return None + + async def _get_generic_prediction(self, model, symbol: str) -> Optional[Prediction]: + """Get prediction from generic model interface""" + try: + # Placeholder for generic model prediction + logger.debug(f"Getting generic prediction from {model} for {symbol}") + return None + except Exception as e: + logger.error(f"Error getting generic prediction for {symbol}: {e}") + return None + + def _get_rl_state(self, symbol: str) -> Optional[np.ndarray]: + """Build RL state vector for DQN agent""" + try: + # Use data provider to get comprehensive RL state + if hasattr(self.data_provider, 'get_dqn_state_for_inference'): + symbols_timeframes = [(symbol, '1m'), (symbol, '5m'), (symbol, '1h')] + state = self.data_provider.get_dqn_state_for_inference(symbols_timeframes, target_size=100) + if state is not None: + return state + + # Fallback: build basic state from market data + market_features = [] + + # Get latest price data + latest_data = self.data_provider.get_latest_data(symbol) + if latest_data and 'close' in latest_data: + current_price = float(latest_data['close']) + market_features.extend([ + current_price, + latest_data.get('volume', 0.0), + latest_data.get('high', current_price) - latest_data.get('low', current_price), # Range + latest_data.get('open', current_price) + ]) + else: + market_features.extend([4300.0, 100.0, 10.0, 4295.0]) # Default values + + # Pad to standard size + while len(market_features) < 100: + market_features.append(0.0) + + return np.array(market_features[:100], dtype=np.float32) + + except Exception as e: + logger.debug(f"Error building RL state for {symbol}: {e}") + return None + + def _get_cob_state(self, symbol: str) -> Optional[np.ndarray]: + """Build COB state vector for COB RL agent""" + try: + # Get COB data from integration + if hasattr(self, 'cob_integration') and self.cob_integration: + cob_snapshot = self.cob_integration.get_cob_snapshot(symbol) + if cob_snapshot: + # Extract features from COB snapshot + features = [] + + # Add bid/ask imbalance + bid_volume = sum([level['volume'] for level in cob_snapshot.get('bids', [])]) + ask_volume = sum([level['volume'] for level in cob_snapshot.get('asks', [])]) + if bid_volume + ask_volume > 0: + imbalance = (bid_volume - ask_volume) / (bid_volume + ask_volume) + else: + imbalance = 0.0 + features.append(imbalance) + + # Add spread + if cob_snapshot.get('bids') and cob_snapshot.get('asks'): + spread = cob_snapshot['asks'][0]['price'] - cob_snapshot['bids'][0]['price'] + features.append(spread) + else: + features.append(0.0) + + # Pad to standard size + while len(features) < 50: + features.append(0.0) + + return np.array(features[:50], dtype=np.float32) + + # Fallback state + return np.zeros(50, dtype=np.float32) + + except Exception as e: + logger.debug(f"Error building COB state for {symbol}: {e}") + return None + + def _combine_predictions(self, symbol: str, price: float, predictions: List[Prediction], + timestamp: datetime) -> TradingDecision: # Call act_with_confidence and handle different return formats result = model.model.act_with_confidence(state) @@ -1728,7 +1747,7 @@ class TradingOrchestrator: ) if needs_refresh: - result = load_best_checkpoint(model_name) + result = load_best_checkpoint(model_name) self._checkpoint_cache[model_name] = result self._checkpoint_cache_time[model_name] = current_time @@ -1869,14 +1888,14 @@ class TradingOrchestrator: logger.warning("EnhancedRealtimeTrainingSystem not available - training disabled") self.training_enabled = False return - + # Initialize enhanced training system directly (no external training_integration module needed) try: from NN.training.enhanced_realtime_training import EnhancedRealtimeTrainingSystem - self.enhanced_training_system = EnhancedRealtimeTrainingSystem( - orchestrator=self, - data_provider=self.data_provider, + self.enhanced_training_system = EnhancedRealtimeTrainingSystem( + orchestrator=self, + data_provider=self.data_provider, dashboard=None ) diff --git a/core/training_integration.py b/core/training_integration.py index ea1419a..55f7dee 100644 --- a/core/training_integration.py +++ b/core/training_integration.py @@ -13,7 +13,7 @@ import logging from datetime import datetime from typing import Dict, List, Any, Optional import numpy as np -from utils.reward_calculator import RewardCalculator +from core.reward_calculator import RewardCalculator import threading import time diff --git a/data/predictions.db b/data/predictions.db index 4c63324..d4b67b0 100644 Binary files a/data/predictions.db and b/data/predictions.db differ diff --git a/web/clean_dashboard.py b/web/clean_dashboard.py index b0a35b4..8950467 100644 --- a/web/clean_dashboard.py +++ b/web/clean_dashboard.py @@ -345,18 +345,61 @@ class CleanTradingDashboard: 'timestamp': datetime.now().isoformat() }) - @self.app.server.route('/api/predictions/stats', methods=['GET']) - def get_prediction_stats(): - """Get model prediction statistics""" + @self.app.server.route('/api/predictions/recent', methods=['GET']) + def get_recent_predictions(): + """Get recent predictions with their outcomes""" try: if (hasattr(self.orchestrator, 'enhanced_training_system') and self.orchestrator.enhanced_training_system): - stats = self.orchestrator.enhanced_training_system.get_model_performance_stats() - return jsonify(stats) + + # Get predictions from database + from core.prediction_database import get_prediction_db + db = get_prediction_db() + + # Get recent predictions (last 24 hours) + predictions = [] + + # Mock data for now - replace with actual database query + import sqlite3 + try: + with sqlite3.connect(db.db_path) as conn: + cursor = conn.cursor() + cursor.execute(""" + SELECT model_name, symbol, prediction_type, confidence, + timestamp, price_at_prediction, outcome_timestamp, + actual_price_change, reward, is_correct + FROM predictions + ORDER BY timestamp DESC + LIMIT 50 + """) + + for row in cursor.fetchall(): + predictions.append({ + 'model_name': row[0], + 'symbol': row[1], + 'prediction_type': row[2], + 'confidence': row[3], + 'timestamp': row[4], + 'price_at_prediction': row[5], + 'outcome_timestamp': row[6], + 'actual_price_change': row[7], + 'reward': row[8], + 'is_correct': row[9], + 'is_resolved': row[6] is not None + }) + except Exception as e: + logger.debug(f"Error fetching predictions from database: {e}") + + return jsonify({ + 'predictions': predictions, + 'total_predictions': len(predictions), + 'active_predictions': len([p for p in predictions if not p['is_resolved']]), + 'timestamp': datetime.now().isoformat() + }) else: return jsonify({"error": "Training system not available"}), 503 except Exception as e: - logger.error(f"Error getting prediction stats: {e}") + logger.error(f"Error getting recent predictions: {e}") return jsonify({"error": str(e)}), 500 def _get_ohlcv_data_with_indicators(self, symbol: str, timeframe: str, limit: int = 300): @@ -980,6 +1023,135 @@ class CleanTradingDashboard: logger.error(f"Training status error: {e}") return 'Error', 'badge bg-danger small' + @self.app.callback( + [Output('total-predictions-count', 'children'), + Output('pending-predictions-count', 'children'), + Output('active-models-count', 'children'), + Output('total-rewards-sum', 'children'), + Output('prediction-timeline-chart', 'figure'), + Output('model-performance-chart', 'figure')], + [Input('interval-component', 'n_intervals')] + ) + def update_prediction_tracking(n_intervals): + """Update prediction tracking charts and metrics""" + try: + if (hasattr(self.orchestrator, 'enhanced_training_system') and + self.orchestrator.enhanced_training_system): + + # Get prediction data + stats = self.orchestrator.enhanced_training_system.get_model_performance_stats() + models = stats.get('models', []) + total_active = stats.get('total_active_predictions', 0) + + # Calculate totals + total_predictions = sum(m.get('total_predictions', 0) for m in models) + total_rewards = sum(m.get('total_reward', 0) for m in models) + active_models = len(models) + + # Create timeline chart (simplified) + timeline_fig = { + 'data': [], + 'layout': { + 'title': 'Recent Predictions Timeline', + 'xaxis': {'title': 'Time'}, + 'yaxis': {'title': 'Confidence'}, + 'template': 'plotly_dark', + 'height': 300, + 'showlegend': False + } + } + + # Add empty annotation if no data + if not models: + timeline_fig['layout']['annotations'] = [{ + 'text': 'No prediction data yet', + 'xref': 'paper', 'yref': 'paper', + 'x': 0.5, 'y': 0.5, + 'showarrow': False, + 'font': {'size': 16, 'color': 'gray'} + }] + + # Create performance chart + performance_fig = { + 'data': [], + 'layout': { + 'title': 'Model Performance', + 'template': 'plotly_dark', + 'height': 300, + 'showlegend': True + } + } + + if models: + model_names = [m.get('model_name', 'Unknown') for m in models] + accuracies = [m.get('accuracy', 0) * 100 for m in models] + rewards = [m.get('total_reward', 0) for m in models] + + # Add accuracy bars + performance_fig['data'].append({ + 'x': model_names, + 'y': accuracies, + 'type': 'bar', + 'name': 'Accuracy (%)', + 'marker': {'color': 'lightblue'} + }) + + performance_fig['layout']['xaxis'] = {'title': 'Model'} + performance_fig['layout']['yaxis'] = {'title': 'Accuracy (%)'} + else: + performance_fig['layout']['annotations'] = [{ + 'text': 'No model data yet', + 'xref': 'paper', 'yref': 'paper', + 'x': 0.5, 'y': 0.5, + 'showarrow': False, + 'font': {'size': 16, 'color': 'gray'} + }] + + return ( + str(total_predictions), + str(total_active), + str(active_models), + f"{total_rewards:.1f}", + timeline_fig, + performance_fig + ) + else: + # Training system not available + empty_fig = { + 'data': [], + 'layout': { + 'template': 'plotly_dark', + 'height': 300, + 'annotations': [{ + 'text': 'Training system not available', + 'xref': 'paper', 'yref': 'paper', + 'x': 0.5, 'y': 0.5, + 'showarrow': False, + 'font': {'size': 16, 'color': 'red'} + }] + } + } + + return "N/A", "N/A", "N/A", "N/A", empty_fig, empty_fig + + except Exception as e: + logger.error(f"Error updating prediction tracking: {e}") + error_fig = { + 'data': [], + 'layout': { + 'template': 'plotly_dark', + 'height': 300, + 'annotations': [{ + 'text': f'Error: {str(e)}', + 'xref': 'paper', 'yref': 'paper', + 'x': 0.5, 'y': 0.5, + 'showarrow': False, + 'font': {'size': 14, 'color': 'red'} + }] + } + } + return "Error", "Error", "Error", "Error", error_fig, error_fig + @self.app.callback( [Output('eth-cob-content', 'children'), Output('btc-cob-content', 'children')], diff --git a/web/layout_manager.py b/web/layout_manager.py index eaf5add..f596540 100644 --- a/web/layout_manager.py +++ b/web/layout_manager.py @@ -19,13 +19,72 @@ class DashboardLayoutManager: return html.Div([ self._create_header(), self._create_interval_component(), - self._create_main_content() + self._create_main_content(), + self._create_prediction_tracking_section() # NEW: Prediction tracking ], className="container-fluid", style={ "backgroundColor": "#111827", "minHeight": "100vh", "color": "#f8f9fa" }) + def _create_prediction_tracking_section(self): + """Create prediction tracking and model performance section""" + return html.Div([ + html.Div([ + html.Div([ + html.H6([ + html.I(className="fas fa-brain me-2"), + "🧠 Model Predictions & Performance Tracking" + ], className="text-light mb-3"), + + # Summary cards row + html.Div([ + html.Div([ + html.Div([ + html.H6("0", id="total-predictions-count", className="mb-0 text-primary"), + html.Small("Total Predictions", className="text-light") + ], className="card-body text-center p-2 bg-dark") + ], className="card col-md-3 mx-1 bg-dark border-secondary"), + + html.Div([ + html.Div([ + html.H6("0", id="pending-predictions-count", className="mb-0 text-warning"), + html.Small("Pending Resolution", className="text-light") + ], className="card-body text-center p-2 bg-dark") + ], className="card col-md-3 mx-1 bg-dark border-secondary"), + + html.Div([ + html.Div([ + html.H6("0", id="active-models-count", className="mb-0 text-info"), + html.Small("Active Models", className="text-light") + ], className="card-body text-center p-2 bg-dark") + ], className="card col-md-3 mx-1 bg-dark border-secondary"), + + html.Div([ + html.Div([ + html.H6("0.0", id="total-rewards-sum", className="mb-0 text-success"), + html.Small("Total Rewards", className="text-light") + ], className="card-body text-center p-2 bg-dark") + ], className="card col-md-3 mx-1 bg-dark border-secondary") + ], className="row mb-3"), + + # Charts row + html.Div([ + html.Div([ + html.H6("Recent Predictions Timeline", className="mb-2 text-light"), + dcc.Graph(id="prediction-timeline-chart", style={"height": "300px"}) + ], className="col-md-6"), + + html.Div([ + html.H6("Model Performance", className="mb-2 text-light"), + dcc.Graph(id="model-performance-chart", style={"height": "300px"}) + ], className="col-md-6") + ], className="row") + + ], className="p-3") + ], className="card bg-dark border-secondary mb-3") + ], className="mt-3") + def _create_header(self): """Create the dashboard header""" trading_mode = "SIMULATION" if (not self.trading_executor or diff --git a/web/prediction_chart.py b/web/prediction_chart.py new file mode 100644 index 0000000..3b14a91 --- /dev/null +++ b/web/prediction_chart.py @@ -0,0 +1,352 @@ +#!/usr/bin/env python3 +""" +Prediction Chart Component - Visualizes model predictions and their outcomes +""" + +import dash +from dash import dcc, html, dash_table +import plotly.graph_objs as go +import plotly.express as px +import pandas as pd +from datetime import datetime, timedelta +from typing import Dict, List, Any, Optional +import logging + +logger = logging.getLogger(__name__) + +class PredictionChartComponent: + """Component for visualizing prediction tracking and outcomes""" + + def __init__(self): + self.colors = { + 'BUY': '#28a745', # Green + 'SELL': '#dc3545', # Red + 'HOLD': '#6c757d', # Gray + 'reward': '#28a745', # Green for positive rewards + 'penalty': '#dc3545' # Red for negative rewards + } + + def create_prediction_timeline_chart(self, predictions_data: List[Dict[str, Any]]) -> dcc.Graph: + """Create a timeline chart showing predictions and their outcomes""" + try: + if not predictions_data: + # Empty chart + fig = go.Figure() + fig.add_annotation( + text="No prediction data available", + xref="paper", yref="paper", + x=0.5, y=0.5, xanchor='center', yanchor='middle', + showarrow=False, font=dict(size=16, color="gray") + ) + fig.update_layout( + title="Model Predictions Timeline", + xaxis_title="Time", + yaxis_title="Confidence", + height=300 + ) + return dcc.Graph(figure=fig, id="prediction-timeline") + + # Convert to DataFrame + df = pd.DataFrame(predictions_data) + df['timestamp'] = pd.to_datetime(df['timestamp']) + + # Create the plot + fig = go.Figure() + + # Add prediction points + for prediction_type in ['BUY', 'SELL', 'HOLD']: + type_data = df[df['prediction_type'] == prediction_type] + if not type_data.empty: + # Different markers for resolved vs pending + resolved_data = type_data[type_data['is_resolved'] == True] + pending_data = type_data[type_data['is_resolved'] == False] + + if not resolved_data.empty: + # Resolved predictions + colors = [self.colors['reward'] if r > 0 else self.colors['penalty'] + for r in resolved_data['reward']] + fig.add_trace(go.Scatter( + x=resolved_data['timestamp'], + y=resolved_data['confidence'], + mode='markers', + marker=dict( + size=10, + color=colors, + symbol='circle', + line=dict(width=2, color=self.colors[prediction_type]) + ), + name=f'{prediction_type} (Resolved)', + text=[f"Model: {m}
Confidence: {c:.3f}
Reward: {r:.2f}" + for m, c, r in zip(resolved_data['model_name'], + resolved_data['confidence'], + resolved_data['reward'])], + hovertemplate='%{text}' + )) + + if not pending_data.empty: + # Pending predictions + fig.add_trace(go.Scatter( + x=pending_data['timestamp'], + y=pending_data['confidence'], + mode='markers', + marker=dict( + size=8, + color=self.colors[prediction_type], + symbol='circle-open', + line=dict(width=2) + ), + name=f'{prediction_type} (Pending)', + text=[f"Model: {m}
Confidence: {c:.3f}
Status: Pending" + for m, c in zip(pending_data['model_name'], + pending_data['confidence'])], + hovertemplate='%{text}' + )) + + # Update layout + fig.update_layout( + title="Model Predictions Timeline", + xaxis_title="Time", + yaxis_title="Confidence", + yaxis=dict(range=[0, 1]), + height=400, + showlegend=True, + legend=dict(x=0.02, y=0.98), + hovermode='closest' + ) + + return dcc.Graph(figure=fig, id="prediction-timeline") + + except Exception as e: + logger.error(f"Error creating prediction timeline chart: {e}") + # Return empty chart on error + fig = go.Figure() + fig.add_annotation(text=f"Error: {str(e)}", x=0.5, y=0.5) + return dcc.Graph(figure=fig, id="prediction-timeline") + + def create_model_performance_chart(self, model_stats: List[Dict[str, Any]]) -> dcc.Graph: + """Create a bar chart showing model performance metrics""" + try: + if not model_stats: + fig = go.Figure() + fig.add_annotation( + text="No model performance data available", + xref="paper", yref="paper", + x=0.5, y=0.5, xanchor='center', yanchor='middle', + showarrow=False, font=dict(size=16, color="gray") + ) + fig.update_layout( + title="Model Performance Comparison", + height=300 + ) + return dcc.Graph(figure=fig, id="model-performance") + + # Extract data + model_names = [stats['model_name'] for stats in model_stats] + accuracies = [stats['accuracy'] * 100 for stats in model_stats] # Convert to percentage + total_rewards = [stats['total_reward'] for stats in model_stats] + total_predictions = [stats['total_predictions'] for stats in model_stats] + + # Create subplots + fig = go.Figure() + + # Add accuracy bars + fig.add_trace(go.Bar( + x=model_names, + y=accuracies, + name='Accuracy (%)', + marker_color='lightblue', + yaxis='y', + text=[f"{a:.1f}%" for a in accuracies], + textposition='auto' + )) + + # Add total reward on secondary y-axis + fig.add_trace(go.Scatter( + x=model_names, + y=total_rewards, + mode='markers+text', + name='Total Reward', + marker=dict( + size=12, + color='orange', + symbol='diamond' + ), + yaxis='y2', + text=[f"{r:.1f}" for r in total_rewards], + textposition='top center' + )) + + # Update layout + fig.update_layout( + title="Model Performance Comparison", + xaxis_title="Model", + yaxis=dict( + title="Accuracy (%)", + side="left", + range=[0, 100] + ), + yaxis2=dict( + title="Total Reward", + side="right", + overlaying="y" + ), + height=400, + showlegend=True, + legend=dict(x=0.02, y=0.98) + ) + + return dcc.Graph(figure=fig, id="model-performance") + + except Exception as e: + logger.error(f"Error creating model performance chart: {e}") + fig = go.Figure() + fig.add_annotation(text=f"Error: {str(e)}", x=0.5, y=0.5) + return dcc.Graph(figure=fig, id="model-performance") + + def create_prediction_table(self, recent_predictions: List[Dict[str, Any]]) -> dash_table.DataTable: + """Create a table showing recent predictions""" + try: + if not recent_predictions: + return dash_table.DataTable( + id="prediction-table", + columns=[ + {"name": "Model", "id": "model_name"}, + {"name": "Symbol", "id": "symbol"}, + {"name": "Prediction", "id": "prediction_type"}, + {"name": "Confidence", "id": "confidence"}, + {"name": "Status", "id": "status"}, + {"name": "Reward", "id": "reward"} + ], + data=[], + style_cell={'textAlign': 'center'}, + style_header={'backgroundColor': 'rgb(230, 230, 230)', 'fontWeight': 'bold'}, + page_size=10 + ) + + # Format data for table + table_data = [] + for pred in recent_predictions[-20:]: # Show last 20 predictions + table_data.append({ + 'model_name': pred.get('model_name', 'Unknown'), + 'symbol': pred.get('symbol', 'N/A'), + 'prediction_type': pred.get('prediction_type', 'N/A'), + 'confidence': f"{pred.get('confidence', 0):.3f}", + 'status': 'Resolved' if pred.get('is_resolved', False) else 'Pending', + 'reward': f"{pred.get('reward', 0):.2f}" if pred.get('is_resolved', False) else 'Pending' + }) + + return dash_table.DataTable( + id="prediction-table", + columns=[ + {"name": "Model", "id": "model_name"}, + {"name": "Symbol", "id": "symbol"}, + {"name": "Prediction", "id": "prediction_type"}, + {"name": "Confidence", "id": "confidence"}, + {"name": "Status", "id": "status"}, + {"name": "Reward", "id": "reward"} + ], + data=table_data, + style_cell={'textAlign': 'center', 'fontSize': '12px'}, + style_header={'backgroundColor': 'rgb(230, 230, 230)', 'fontWeight': 'bold'}, + style_data_conditional=[ + { + 'if': {'filter_query': '{status} = Resolved and {reward} > 0'}, + 'backgroundColor': 'rgba(40, 167, 69, 0.1)', + 'color': 'black', + }, + { + 'if': {'filter_query': '{status} = Resolved and {reward} < 0'}, + 'backgroundColor': 'rgba(220, 53, 69, 0.1)', + 'color': 'black', + }, + { + 'if': {'filter_query': '{status} = Pending'}, + 'backgroundColor': 'rgba(108, 117, 125, 0.1)', + 'color': 'black', + } + ], + page_size=10, + sort_action="native" + ) + + except Exception as e: + logger.error(f"Error creating prediction table: {e}") + return dash_table.DataTable( + id="prediction-table", + columns=[{"name": "Error", "id": "error"}], + data=[{"error": str(e)}] + ) + + def create_prediction_panel(self, prediction_stats: Dict[str, Any]) -> html.Div: + """Create a complete prediction tracking panel""" + try: + predictions_data = prediction_stats.get('predictions', []) + model_stats = prediction_stats.get('models', []) + + return html.Div([ + html.H4("📊 Prediction Tracking & Performance", className="mb-3"), + + # Summary cards + html.Div([ + html.Div([ + html.H6(f"{prediction_stats.get('total_predictions', 0)}", className="mb-0"), + html.Small("Total Predictions", className="text-muted") + ], className="card-body text-center"), + ], className="card col-md-3 mx-1"), + + html.Div([ + html.Div([ + html.H6(f"{prediction_stats.get('active_predictions', 0)}", className="mb-0"), + html.Small("Pending Resolution", className="text-muted") + ], className="card-body text-center"), + ], className="card col-md-3 mx-1"), + + html.Div([ + html.Div([ + html.H6(f"{len(model_stats)}", className="mb-0"), + html.Small("Active Models", className="text-muted") + ], className="card-body text-center"), + ], className="card col-md-3 mx-1"), + + html.Div([ + html.Div([ + html.H6(f"{sum(s.get('total_reward', 0) for s in model_stats):.1f}", className="mb-0"), + html.Small("Total Rewards", className="text-muted") + ], className="card-body text-center"), + ], className="card col-md-3 mx-1") + + ], className="row mb-4"), + + # Charts + html.Div([ + html.Div([ + self.create_prediction_timeline_chart(predictions_data) + ], className="col-md-6"), + + html.Div([ + self.create_model_performance_chart(model_stats) + ], className="col-md-6") + ], className="row mb-4"), + + # Recent predictions table + html.Div([ + html.H5("Recent Predictions", className="mb-2"), + self.create_prediction_table(predictions_data) + ], className="mb-3") + + except Exception as e: + logger.error(f"Error creating prediction panel: {e}") + return html.Div([ + html.H4("📊 Prediction Tracking & Performance"), + html.P(f"Error loading prediction data: {str(e)}", className="text-danger") + ]) + +# Global instance +_prediction_chart = None + +def get_prediction_chart() -> PredictionChartComponent: + """Get global prediction chart component""" + global _prediction_chart + if _prediction_chart is None: + _prediction_chart = PredictionChartComponent() + return _prediction_chart