""" Chart-related callbacks for the dashboard. """ from dash import Output, Input from datetime import datetime from utils.logger import get_logger from components.charts import ( create_strategy_chart, create_chart_with_indicators, create_error_chart, get_market_statistics ) from components.charts.config import get_all_example_strategies from database.connection import DatabaseManager from dash import html logger = get_logger("default_logger") def register_chart_callbacks(app): """Register chart-related callbacks.""" @app.callback( Output('price-chart', 'figure'), [Input('symbol-dropdown', 'value'), Input('timeframe-dropdown', 'value'), Input('overlay-indicators-checklist', 'value'), Input('subplot-indicators-checklist', 'value'), Input('strategy-dropdown', 'value'), Input('interval-component', 'n_intervals')] ) def update_price_chart(symbol, timeframe, overlay_indicators, subplot_indicators, selected_strategy, n_intervals): """Update the price chart with latest market data and selected indicators.""" try: # If a strategy is selected, use strategy chart if selected_strategy and selected_strategy != 'basic': fig = create_strategy_chart(symbol, timeframe, selected_strategy) logger.debug(f"Chart callback: Created strategy chart for {symbol} ({timeframe}) with strategy: {selected_strategy}") else: # Create chart with dynamically selected indicators fig = create_chart_with_indicators( symbol=symbol, timeframe=timeframe, overlay_indicators=overlay_indicators or [], subplot_indicators=subplot_indicators or [], days_back=7 ) indicator_count = len(overlay_indicators or []) + len(subplot_indicators or []) logger.debug(f"Chart callback: Created dynamic chart for {symbol} ({timeframe}) with {indicator_count} indicators") return fig except Exception as e: logger.error(f"Error updating price chart: {e}") return create_error_chart(f"Error loading chart: {str(e)}") # Strategy selection callback - automatically load strategy indicators @app.callback( [Output('overlay-indicators-checklist', 'value'), Output('subplot-indicators-checklist', 'value')], [Input('strategy-dropdown', 'value')] ) def update_indicators_from_strategy(selected_strategy): """Update indicator selections when a strategy is chosen.""" if not selected_strategy or selected_strategy == 'basic': return [], [] try: # Get strategy configuration all_strategies = get_all_example_strategies() if selected_strategy in all_strategies: strategy_example = all_strategies[selected_strategy] config = strategy_example.config # Extract overlay and subplot indicators from strategy overlay_indicators = config.overlay_indicators or [] # Extract subplot indicators from subplot configs subplot_indicators = [] for subplot_config in config.subplot_configs or []: subplot_indicators.extend(subplot_config.indicators or []) logger.debug(f"Chart callback: Loaded strategy {selected_strategy}: {len(overlay_indicators)} overlays, {len(subplot_indicators)} subplots") return overlay_indicators, subplot_indicators else: logger.warning(f"Chart callback: Strategy {selected_strategy} not found") return [], [] except Exception as e: logger.error(f"Chart callback: Error loading strategy indicators: {e}") return [], [] # Enhanced market statistics callback with comprehensive analysis @app.callback( Output('market-stats', 'children'), [Input('symbol-dropdown', 'value'), Input('timeframe-dropdown', 'value'), Input('interval-component', 'n_intervals')] ) def update_market_stats(symbol, timeframe, n_intervals): """Update comprehensive market statistics with analysis.""" try: # Import analysis classes from dashboard.components.data_analysis import VolumeAnalyzer, PriceMovementAnalyzer # Get basic market statistics basic_stats = get_market_statistics(symbol, timeframe) # Create analyzers for comprehensive analysis volume_analyzer = VolumeAnalyzer() price_analyzer = PriceMovementAnalyzer() # Get analysis for 7 days volume_analysis = volume_analyzer.get_volume_statistics(symbol, timeframe, 7) price_analysis = price_analyzer.get_price_movement_statistics(symbol, timeframe, 7) # Create enhanced statistics layout return html.Div([ html.H3("📊 Enhanced Market Statistics"), # Basic Market Data html.Div([ html.H4("💹 Current Market Data", style={'color': '#2c3e50', 'margin-bottom': '10px'}), html.Div([ html.Div([ html.Strong(f"{key}: "), html.Span(value, style={ 'color': '#27ae60' if '+' in str(value) else '#e74c3c' if '-' in str(value) else '#2c3e50', 'font-weight': 'bold' }) ], style={'margin': '5px 0'}) for key, value in basic_stats.items() ]) ], style={'border': '1px solid #bdc3c7', 'padding': '15px', 'margin': '10px 0', 'border-radius': '5px', 'background-color': '#f8f9fa'}), # Volume Analysis Section create_volume_analysis_section(volume_analysis), # Price Movement Analysis Section create_price_movement_section(price_analysis), # Additional Market Insights html.Div([ html.H4("🔍 Market Insights", style={'color': '#2c3e50', 'margin-bottom': '10px'}), html.Div([ html.P(f"📈 Analysis Period: 7 days | Timeframe: {timeframe}", style={'margin': '5px 0'}), html.P(f"🎯 Symbol: {symbol}", style={'margin': '5px 0'}), html.P("💡 Statistics update automatically with chart changes", style={'margin': '5px 0', 'font-style': 'italic'}) ]) ], style={'border': '1px solid #3498db', 'padding': '15px', 'margin': '10px 0', 'border-radius': '5px', 'background-color': '#ebf3fd'}) ]) except Exception as e: logger.error(f"Chart callback: Error updating enhanced market stats: {e}") return html.Div([ html.H3("Market Statistics"), html.P(f"Error loading statistics: {str(e)}", style={'color': '#e74c3c'}) ]) def create_volume_analysis_section(volume_stats): """Create volume analysis section for market statistics.""" if not volume_stats or volume_stats.get('total_volume', 0) == 0: return html.Div([ html.H4("📊 Volume Analysis", style={'color': '#2c3e50', 'margin-bottom': '10px'}), html.P("No volume data available for analysis", style={'color': '#e74c3c'}) ], style={'border': '1px solid #e74c3c', 'padding': '15px', 'margin': '10px 0', 'border-radius': '5px', 'background-color': '#fdeded'}) return html.Div([ html.H4("📊 Volume Analysis (7 days)", style={'color': '#2c3e50', 'margin-bottom': '10px'}), html.Div([ html.Div([ html.Strong("Total Volume: "), html.Span(f"{volume_stats.get('total_volume', 0):,.2f}", style={'color': '#27ae60'}) ], style={'margin': '5px 0'}), html.Div([ html.Strong("Average Volume: "), html.Span(f"{volume_stats.get('average_volume', 0):,.2f}", style={'color': '#2c3e50'}) ], style={'margin': '5px 0'}), html.Div([ html.Strong("Volume Trend: "), html.Span( volume_stats.get('volume_trend', 'Neutral'), style={'color': '#27ae60' if volume_stats.get('volume_trend') == 'Increasing' else '#e74c3c' if volume_stats.get('volume_trend') == 'Decreasing' else '#f39c12'} ) ], style={'margin': '5px 0'}), html.Div([ html.Strong("High Volume Periods: "), html.Span(f"{volume_stats.get('high_volume_periods', 0)}", style={'color': '#2c3e50'}) ], style={'margin': '5px 0'}) ]) ], style={'border': '1px solid #27ae60', 'padding': '15px', 'margin': '10px 0', 'border-radius': '5px', 'background-color': '#eafaf1'}) def create_price_movement_section(price_stats): """Create price movement analysis section for market statistics.""" if not price_stats or price_stats.get('total_returns') is None: return html.Div([ html.H4("📈 Price Movement Analysis", style={'color': '#2c3e50', 'margin-bottom': '10px'}), html.P("No price movement data available for analysis", style={'color': '#e74c3c'}) ], style={'border': '1px solid #e74c3c', 'padding': '15px', 'margin': '10px 0', 'border-radius': '5px', 'background-color': '#fdeded'}) return html.Div([ html.H4("📈 Price Movement Analysis (7 days)", style={'color': '#2c3e50', 'margin-bottom': '10px'}), html.Div([ html.Div([ html.Strong("Total Return: "), html.Span( f"{price_stats.get('total_returns', 0):+.2f}%", style={'color': '#27ae60' if price_stats.get('total_returns', 0) >= 0 else '#e74c3c'} ) ], style={'margin': '5px 0'}), html.Div([ html.Strong("Volatility: "), html.Span(f"{price_stats.get('volatility', 0):.2f}%", style={'color': '#2c3e50'}) ], style={'margin': '5px 0'}), html.Div([ html.Strong("Bullish Periods: "), html.Span(f"{price_stats.get('bullish_periods', 0)}", style={'color': '#27ae60'}) ], style={'margin': '5px 0'}), html.Div([ html.Strong("Bearish Periods: "), html.Span(f"{price_stats.get('bearish_periods', 0)}", style={'color': '#e74c3c'}) ], style={'margin': '5px 0'}), html.Div([ html.Strong("Trend Strength: "), html.Span( price_stats.get('trend_strength', 'Neutral'), style={'color': '#27ae60' if 'Strong' in str(price_stats.get('trend_strength', '')) else '#f39c12'} ) ], style={'margin': '5px 0'}) ]) ], style={'border': '1px solid #3498db', 'padding': '15px', 'margin': '10px 0', 'border-radius': '5px', 'background-color': '#ebf3fd'}) logger.info("Chart callback: Chart callbacks registered successfully")