""" Charting Module This module provides comprehensive charting and visualization capabilities for stock analysis with interactive dashboards using Plotly. """ import pandas as pd import numpy as np import plotly.graph_objects as go import plotly.express as px from plotly.subplots import make_subplots import streamlit as st from typing import Dict, List, Optional, Tuple import warnings warnings.filterwarnings('ignore') class StockChartGenerator: """Enhanced stock chart generator with interactive dashboards""" def __init__(self): self.color_scheme = { 'primary': '#1f77b4', 'secondary': '#ff7f0e', 'success': '#2ca02c', 'danger': '#d62728', 'warning': '#ff7f0e', 'info': '#17a2b8', 'background': '#f8f9fa' } def create_price_chart(self, data: pd.DataFrame, symbol: str, analysis: Dict = None) -> go.Figure: """ Create comprehensive price chart with technical indicators Args: data: Stock price data symbol: Stock symbol analysis: Technical analysis results Returns: Plotly figure object """ if data.empty: return self._create_empty_chart("No data available") # Create subplots fig = make_subplots( rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.05, subplot_titles=(f'{symbol} Price Chart', 'Volume', 'Technical Indicators'), row_heights=[0.6, 0.2, 0.2] ) # Main price chart (candlestick) fig.add_trace( go.Candlestick( x=data.index, open=data['Open'], high=data['High'], low=data['Low'], close=data['Close'], name='Price', increasing_line_color=self.color_scheme['success'], decreasing_line_color=self.color_scheme['danger'] ), row=1, col=1 ) # Add moving averages if available if 'SMA_20' in data.columns: fig.add_trace( go.Scatter( x=data.index, y=data['SMA_20'], mode='lines', name='SMA 20', line=dict(color=self.color_scheme['primary'], width=1) ), row=1, col=1 ) if 'SMA_50' in data.columns: fig.add_trace( go.Scatter( x=data.index, y=data['SMA_50'], mode='lines', name='SMA 50', line=dict(color=self.color_scheme['secondary'], width=1) ), row=1, col=1 ) # Volume chart colors = ['red' if close < open else 'green' for close, open in zip(data['Close'], data['Open'])] fig.add_trace( go.Bar( x=data.index, y=data['Volume'], name='Volume', marker_color=colors, opacity=0.7 ), row=2, col=1 ) # Technical indicators (RSI if available in analysis) if analysis and 'rsi' in analysis: # Create RSI line (simplified - would need full RSI calculation for time series) rsi_value = analysis['rsi'] rsi_line = [rsi_value] * len(data) fig.add_trace( go.Scatter( x=data.index, y=rsi_line, mode='lines', name=f'RSI ({rsi_value:.1f})', line=dict(color=self.color_scheme['info'], width=2) ), row=3, col=1 ) # Add RSI reference lines fig.add_hline(y=70, line_dash="dash", line_color="red", opacity=0.5, row=3, col=1) fig.add_hline(y=30, line_dash="dash", line_color="green", opacity=0.5, row=3, col=1) # Update layout fig.update_layout( title=f'{symbol} Stock Analysis Dashboard', xaxis_title='Date', yaxis_title='Price ($)', template='plotly_white', height=800, showlegend=True, hovermode='x unified' ) # Remove rangeslider for cleaner look fig.update_layout(xaxis_rangeslider_visible=False) return fig def create_performance_chart(self, data: pd.DataFrame, symbol: str, analysis: Dict) -> go.Figure: """ Create performance analysis chart Args: data: Stock price data symbol: Stock symbol analysis: Analysis results with performance metrics Returns: Plotly figure object """ if data.empty: return self._create_empty_chart("No data available for performance analysis") # Calculate cumulative returns daily_returns = data['Close'].pct_change().fillna(0) cumulative_returns = (1 + daily_returns).cumprod() - 1 fig = go.Figure() # Cumulative returns line fig.add_trace( go.Scatter( x=data.index, y=cumulative_returns * 100, mode='lines', name='Cumulative Returns (%)', line=dict(color=self.color_scheme['primary'], width=2), fill='tonexty', fillcolor='rgba(31, 119, 180, 0.1)' ) ) # Add benchmark line (0% return) fig.add_hline(y=0, line_dash="dash", line_color="gray", opacity=0.5) # Add performance annotations if analysis: total_return = analysis.get('total_return_pct', 0) fig.add_annotation( x=data.index[-1], y=total_return, text=f"Total Return: {total_return:.1f}%", showarrow=True, arrowhead=2, arrowcolor=self.color_scheme['primary'], bgcolor="white", bordercolor=self.color_scheme['primary'] ) fig.update_layout( title=f'{symbol} Performance Analysis', xaxis_title='Date', yaxis_title='Cumulative Returns (%)', template='plotly_white', height=500, hovermode='x' ) return fig def create_risk_analysis_chart(self, analysis: Dict, symbol: str) -> go.Figure: """ Create risk analysis visualization Args: analysis: Analysis results with risk metrics symbol: Stock symbol Returns: Plotly figure object """ if not analysis or 'error' in analysis: return self._create_empty_chart("No risk data available") # Prepare risk metrics risk_metrics = { 'Volatility (Annual)': analysis.get('volatility_annualized', 0), 'Max Drawdown': abs(analysis.get('max_drawdown', 0)), 'VaR 95%': abs(analysis.get('var_95', 0)), 'VaR 99%': abs(analysis.get('var_99', 0)) } # Create radar chart for risk metrics categories = list(risk_metrics.keys()) values = list(risk_metrics.values()) fig = go.Figure() fig.add_trace(go.Scatterpolar( r=values, theta=categories, fill='toself', name=f'{symbol} Risk Profile', line_color=self.color_scheme['danger'], fillcolor='rgba(214, 39, 40, 0.1)' )) fig.update_layout( polar=dict( radialaxis=dict( visible=True, range=[0, max(values) * 1.2] if values else [0, 100] ) ), title=f'{symbol} Risk Analysis Chart', template='plotly_white', height=500 ) return fig def create_comparison_chart(self, data_dict: Dict[str, pd.DataFrame], symbols: List[str]) -> go.Figure: """ Create comparison chart for multiple stocks Args: data_dict: Dictionary of stock data {symbol: dataframe} symbols: List of stock symbols to compare Returns: Plotly figure object """ fig = go.Figure() colors = [self.color_scheme['primary'], self.color_scheme['secondary'], self.color_scheme['success'], self.color_scheme['danger']] for i, symbol in enumerate(symbols): if symbol in data_dict and not data_dict[symbol].empty: data = data_dict[symbol] # Normalize prices to start at 100 for comparison normalized_prices = (data['Close'] / data['Close'].iloc[0]) * 100 fig.add_trace( go.Scatter( x=data.index, y=normalized_prices, mode='lines', name=symbol, line=dict(color=colors[i % len(colors)], width=2) ) ) fig.update_layout( title='Stock Price Comparison (Normalized to 100)', xaxis_title='Date', yaxis_title='Normalized Price', template='plotly_white', height=600, hovermode='x unified' ) return fig def create_sector_analysis_chart(self, sector_data: Dict) -> go.Figure: """ Create sector analysis visualization Args: sector_data: Dictionary with sector analysis data Returns: Plotly figure object """ # This would typically show sector performance, P/E ratios, etc. # For now, create a placeholder fig = go.Figure() fig.add_annotation( x=0.5, y=0.5, text="Sector Analysis
Coming Soon", showarrow=False, font=dict(size=20), xref="paper", yref="paper" ) fig.update_layout( title='Sector Analysis Dashboard', template='plotly_white', height=400, showticklabels=False ) return fig def create_trading_signals_chart(self, data: pd.DataFrame, analysis: Dict, trading_decision: Dict, symbol: str) -> go.Figure: """ Create trading signals visualization Args: data: Stock price data analysis: Technical analysis results trading_decision: Trading recommendation symbol: Stock symbol Returns: Plotly figure object """ if data.empty: return self._create_empty_chart("No data available for trading signals") fig = go.Figure() # Price line fig.add_trace( go.Scatter( x=data.index, y=data['Close'], mode='lines', name='Price', line=dict(color=self.color_scheme['primary'], width=2) ) ) # Add trading signal recommendation = trading_decision.get('recommendation', 'HOLD') current_price = data['Close'].iloc[-1] signal_color = { 'BUY': self.color_scheme['success'], 'SELL': self.color_scheme['danger'], 'HOLD': self.color_scheme['warning'] }.get(recommendation, self.color_scheme['info']) fig.add_trace( go.Scatter( x=[data.index[-1]], y=[current_price], mode='markers', name=f'{recommendation} Signal', marker=dict( color=signal_color, size=15, symbol='triangle-up' if recommendation == 'BUY' else 'triangle-down' if recommendation == 'SELL' else 'circle' ) ) ) # Add price target if available price_target = trading_decision.get('price_target') if price_target: fig.add_hline( y=price_target, line_dash="dash", line_color=self.color_scheme['success'], annotation_text=f"Target: ${price_target:.2f}" ) # Add stop loss if available stop_loss = trading_decision.get('stop_loss') if stop_loss: fig.add_hline( y=stop_loss, line_dash="dash", line_color=self.color_scheme['danger'], annotation_text=f"Stop Loss: ${stop_loss:.2f}" ) fig.update_layout( title=f'{symbol} Trading Signals', xaxis_title='Date', yaxis_title='Price ($)', template='plotly_white', height=500, hovermode='x' ) return fig def create_dashboard_summary(self, symbol: str, analysis: Dict, trading_decision: Dict, sharia_compliance: Dict) -> Dict: """ Create summary metrics for dashboard display Args: symbol: Stock symbol analysis: Technical analysis results trading_decision: Trading recommendation sharia_compliance: Sharia compliance results Returns: Dictionary with summary metrics """ summary = { 'symbol': symbol, 'current_price': analysis.get('current_price', 0), 'total_return': analysis.get('total_return_pct', 0), 'volatility': analysis.get('volatility_annualized', 0), 'trading_recommendation': trading_decision.get('recommendation', 'HOLD'), 'trading_confidence': trading_decision.get('confidence', 0) * 100, 'sharia_ruling': sharia_compliance.get('ruling', 'UNCERTAIN'), 'sharia_confidence': sharia_compliance.get('confidence', 0) * 100, 'risk_level': trading_decision.get('risk_level', 'medium'), 'trend_direction': analysis.get('trend_direction', 'unknown'), 'rsi': analysis.get('rsi', 50), 'max_drawdown': analysis.get('max_drawdown', 0) } return summary def _create_empty_chart(self, message: str) -> go.Figure: """Create an empty chart with a message""" fig = go.Figure() fig.add_annotation( x=0.5, y=0.5, text=message, showarrow=False, font=dict(size=16), xref="paper", yref="paper" ) fig.update_layout( template='plotly_white', height=400, showticklabels=False ) return fig # Global instance for easy import chart_generator = StockChartGenerator() # Convenience functions def create_price_chart(data: pd.DataFrame, symbol: str, analysis: Dict = None) -> go.Figure: """Convenience function to create price chart""" return chart_generator.create_price_chart(data, symbol, analysis) def create_performance_chart(data: pd.DataFrame, symbol: str, analysis: Dict) -> go.Figure: """Convenience function to create performance chart""" return chart_generator.create_performance_chart(data, symbol, analysis) def create_trading_signals_chart(data: pd.DataFrame, analysis: Dict, trading_decision: Dict, symbol: str) -> go.Figure: """Convenience function to create trading signals chart""" return chart_generator.create_trading_signals_chart(data, analysis, trading_decision, symbol)