From cdee9f04d69c7e0679c39b2ea18d79aa84380fa6 Mon Sep 17 00:00:00 2001 From: "Vasily.onl" Date: Wed, 4 Jun 2025 15:30:50 +0800 Subject: [PATCH] Remove main application file `app.py` and update dependencies for modular dashboard architecture - Deleted `app.py`, consolidating the main application logic into a modular structure for improved maintainability. - Added `dash-mantine-components` dependency to enhance UI component capabilities. - Updated `pyproject.toml` and `uv.lock` to reflect the new dependency. - Adjusted imports in `components/__init__.py` and `chart_controls.py` to align with the new modular design. - Cleaned up unused parameter controls in the market data layout to streamline the user interface. --- app.py | 1523 ------------------------ dashboard/components/__init__.py | 5 +- dashboard/components/chart_controls.py | 99 -- dashboard/layouts/market_data.py | 5 - pyproject.toml | 1 + tasks/3.4. Chart layers.md | 24 +- uv.lock | 14 + 7 files changed, 29 insertions(+), 1642 deletions(-) delete mode 100644 app.py diff --git a/app.py b/app.py deleted file mode 100644 index b3728a3..0000000 --- a/app.py +++ /dev/null @@ -1,1523 +0,0 @@ -#!/usr/bin/env python3 -""" -Main Dash application for the Crypto Trading Bot Dashboard. -Provides real-time visualization and bot management interface. -""" - -import sys -from pathlib import Path - -# Add project root to path -project_root = Path(__file__).parent -sys.path.insert(0, str(project_root)) - -# Suppress SQLAlchemy logging to reduce verbosity -import logging -logging.getLogger('sqlalchemy').setLevel(logging.WARNING) -logging.getLogger('sqlalchemy.engine').setLevel(logging.WARNING) -logging.getLogger('sqlalchemy.pool').setLevel(logging.WARNING) -logging.getLogger('sqlalchemy.dialects').setLevel(logging.WARNING) -logging.getLogger('sqlalchemy.orm').setLevel(logging.WARNING) - -import dash -from dash import dcc, html, Input, Output, State, callback -import plotly.graph_objects as go -from datetime import datetime, timedelta -import pandas as pd - -# Import project modules -from config.settings import app as app_settings, dashboard as dashboard_settings -from utils.logger import get_logger -from database.connection import DatabaseManager -from components.charts import ( - create_candlestick_chart, get_market_statistics, - get_supported_symbols, get_supported_timeframes, - create_data_status_indicator, check_data_availability, - create_error_chart, create_strategy_chart, create_chart_with_indicators -) -from components.charts.config import ( - get_available_strategy_names, - get_all_example_strategies, - get_overlay_indicators, - get_subplot_indicators, - get_all_default_indicators, - get_indicators_by_category -) -from components.charts.indicator_manager import get_indicator_manager -from components.charts.indicator_defaults import ensure_default_indicators - -# Initialize logger -logger = get_logger("dashboard_app") - -# Create the app instance at module level -app = dash.Dash( - __name__, - title="Crypto Trading Bot Dashboard", - update_title="Loading...", - suppress_callback_exceptions=True -) - -# Configure app -app.server.secret_key = "crypto-bot-dashboard-secret-key-2024" - -logger.info("Initializing Crypto Trading Bot Dashboard") - -# Define basic layout -app.layout = html.Div([ - # Header - html.Div([ - html.H1("🚀 Crypto Trading Bot Dashboard", - style={'margin': '0', 'color': '#2c3e50'}), - html.P("Real-time monitoring and bot management", - style={'margin': '5px 0 0 0', 'color': '#7f8c8d'}) - ], style={ - 'padding': '20px', - 'background-color': '#ecf0f1', - 'border-bottom': '2px solid #bdc3c7' - }), - - # Navigation tabs - dcc.Tabs(id="main-tabs", value='market-data', children=[ - dcc.Tab(label='📊 Market Data', value='market-data'), - dcc.Tab(label='🤖 Bot Management', value='bot-management'), - dcc.Tab(label='📈 Performance', value='performance'), - dcc.Tab(label='⚙️ System Health', value='system-health'), - ], style={'margin': '10px 20px'}), - - # Main content area - html.Div(id='tab-content', style={'padding': '20px'}), - - # Auto-refresh interval for real-time updates - dcc.Interval( - id='interval-component', - interval=5000, # Update every 5 seconds - n_intervals=0 - ), - - # Store components for data sharing between callbacks - dcc.Store(id='market-data-store'), - dcc.Store(id='bot-status-store'), - - # Hidden button for callback compatibility (real button is in market data layout) - html.Button(id='add-indicator-btn', style={'display': 'none'}), - - # Add Indicator Modal - html.Div([ - dcc.Store(id='edit-indicator-store', data=None), # Store for edit mode - explicitly start with None - - # Modal Background - html.Div( - id='indicator-modal-background', - style={ - 'display': 'none', - 'position': 'fixed', - 'z-index': '1000', - 'left': '0', - 'top': '0', - 'width': '100%', - 'height': '100%', - 'background-color': 'rgba(0,0,0,0.5)', - 'visibility': 'hidden' - } - ), - - # Modal Content - html.Div([ - html.Div([ - # Modal Header - html.Div([ - html.H4("📊 Add New Indicator", id="modal-title", style={'margin': '0', 'color': '#2c3e50'}), - html.Button( - "✕", - id="close-modal-btn", - style={ - 'background': 'none', - 'border': 'none', - 'font-size': '24px', - 'cursor': 'pointer', - 'color': '#999', - 'float': 'right' - } - ) - ], style={'display': 'flex', 'justify-content': 'space-between', 'align-items': 'center', 'margin-bottom': '20px', 'border-bottom': '1px solid #eee', 'padding-bottom': '10px'}), - - # Modal Body - html.Div([ - # Basic Settings - html.Div([ - html.H5("Basic Settings", style={'color': '#2c3e50', 'margin-bottom': '15px'}), - - # Indicator Name - html.Div([ - html.Label("Indicator Name:", style={'font-weight': 'bold', 'margin-bottom': '5px'}), - dcc.Input( - id='indicator-name-input', - type='text', - placeholder='e.g., "SMA 30 Custom"', - style={'width': '100%', 'padding': '8px', 'margin-bottom': '10px', 'border': '1px solid #ddd', 'border-radius': '4px'} - ) - ]), - - # Indicator Type - html.Div([ - html.Label("Indicator Type:", style={'font-weight': 'bold', 'margin-bottom': '5px'}), - dcc.Dropdown( - id='indicator-type-dropdown', - options=[ - {'label': 'Simple Moving Average (SMA)', 'value': 'sma'}, - {'label': 'Exponential Moving Average (EMA)', 'value': 'ema'}, - {'label': 'Relative Strength Index (RSI)', 'value': 'rsi'}, - {'label': 'MACD', 'value': 'macd'}, - {'label': 'Bollinger Bands', 'value': 'bollinger_bands'} - ], - placeholder='Select indicator type', - style={'margin-bottom': '10px'} - ) - ]), - - # Description - html.Div([ - html.Label("Description (Optional):", style={'font-weight': 'bold', 'margin-bottom': '5px'}), - dcc.Textarea( - id='indicator-description-input', - placeholder='Brief description of this indicator configuration...', - style={'width': '100%', 'height': '60px', 'padding': '8px', 'margin-bottom': '15px', 'border': '1px solid #ddd', 'border-radius': '4px', 'resize': 'vertical'} - ) - ]) - ], style={'margin-bottom': '20px'}), - - # Parameters Section - html.Div([ - html.H5("Parameters", style={'color': '#2c3e50', 'margin-bottom': '15px'}), - - # Default message - html.Div( - id='indicator-parameters-message', - children=[html.P("Select an indicator type to configure parameters", style={'color': '#7f8c8d', 'font-style': 'italic'})], - style={'display': 'block'} - ), - - # SMA Parameters (hidden by default) - html.Div([ - html.Label("Period:", style={'font-weight': 'bold', 'margin-bottom': '5px'}), - dcc.Input( - id='sma-period-input', - type='number', - value=20, - min=1, max=200, - style={'width': '100px', 'padding': '8px', 'border': '1px solid #ddd', 'border-radius': '4px'} - ), - html.P("Number of periods for Simple Moving Average calculation", style={'color': '#7f8c8d', 'font-size': '12px', 'margin-top': '5px'}) - ], id='sma-parameters', style={'display': 'none', 'margin-bottom': '10px'}), - - # EMA Parameters (hidden by default) - html.Div([ - html.Label("Period:", style={'font-weight': 'bold', 'margin-bottom': '5px'}), - dcc.Input( - id='ema-period-input', - type='number', - value=12, - min=1, max=200, - style={'width': '100px', 'padding': '8px', 'border': '1px solid #ddd', 'border-radius': '4px'} - ), - html.P("Number of periods for Exponential Moving Average calculation", style={'color': '#7f8c8d', 'font-size': '12px', 'margin-top': '5px'}) - ], id='ema-parameters', style={'display': 'none', 'margin-bottom': '10px'}), - - # RSI Parameters (hidden by default) - html.Div([ - html.Label("Period:", style={'font-weight': 'bold', 'margin-bottom': '5px'}), - dcc.Input( - id='rsi-period-input', - type='number', - value=14, - min=2, max=50, - style={'width': '100px', 'padding': '8px', 'border': '1px solid #ddd', 'border-radius': '4px'} - ), - html.P("Number of periods for RSI calculation (typically 14)", style={'color': '#7f8c8d', 'font-size': '12px', 'margin-top': '5px'}) - ], id='rsi-parameters', style={'display': 'none', 'margin-bottom': '10px'}), - - # MACD Parameters (hidden by default) - html.Div([ - html.Div([ - html.Label("Fast Period:", style={'font-weight': 'bold', 'margin-bottom': '5px'}), - dcc.Input( - id='macd-fast-period-input', - type='number', - value=12, - min=2, max=50, - style={'width': '80px', 'padding': '8px', 'border': '1px solid #ddd', 'border-radius': '4px'} - ) - ], style={'margin-bottom': '10px'}), - html.Div([ - html.Label("Slow Period:", style={'font-weight': 'bold', 'margin-bottom': '5px'}), - dcc.Input( - id='macd-slow-period-input', - type='number', - value=26, - min=5, max=100, - style={'width': '80px', 'padding': '8px', 'border': '1px solid #ddd', 'border-radius': '4px'} - ) - ], style={'margin-bottom': '10px'}), - html.Div([ - html.Label("Signal Period:", style={'font-weight': 'bold', 'margin-bottom': '5px'}), - dcc.Input( - id='macd-signal-period-input', - type='number', - value=9, - min=2, max=30, - style={'width': '80px', 'padding': '8px', 'border': '1px solid #ddd', 'border-radius': '4px'} - ) - ]), - html.P("MACD periods: Fast EMA, Slow EMA, and Signal line", style={'color': '#7f8c8d', 'font-size': '12px', 'margin-top': '5px'}) - ], id='macd-parameters', style={'display': 'none', 'margin-bottom': '10px'}), - - # Bollinger Bands Parameters (hidden by default) - html.Div([ - html.Div([ - html.Label("Period:", style={'font-weight': 'bold', 'margin-bottom': '5px'}), - dcc.Input( - id='bb-period-input', - type='number', - value=20, - min=5, max=100, - style={'width': '80px', 'padding': '8px', 'border': '1px solid #ddd', 'border-radius': '4px'} - ) - ], style={'margin-bottom': '10px'}), - html.Div([ - html.Label("Standard Deviation:", style={'font-weight': 'bold', 'margin-bottom': '5px'}), - dcc.Input( - id='bb-stddev-input', - type='number', - value=2.0, - min=0.5, max=5.0, step=0.1, - style={'width': '80px', 'padding': '8px', 'border': '1px solid #ddd', 'border-radius': '4px'} - ) - ]), - html.P("Period for middle line (SMA) and standard deviation multiplier", style={'color': '#7f8c8d', 'font-size': '12px', 'margin-top': '5px'}) - ], id='bb-parameters', style={'display': 'none', 'margin-bottom': '10px'}) - - ], style={'margin-bottom': '20px'}), - - # Styling Section - html.Div([ - html.H5("Styling", style={'color': '#2c3e50', 'margin-bottom': '15px'}), - - html.Div([ - # Color Picker - html.Div([ - html.Label("Color:", style={'font-weight': 'bold', 'margin-bottom': '5px'}), - dcc.Input( - id='indicator-color-input', - type='text', - value='#007bff', - style={'width': '100px', 'padding': '8px', 'margin-bottom': '10px', 'border': '1px solid #ddd', 'border-radius': '4px'} - ) - ], style={'width': '48%', 'display': 'inline-block', 'margin-right': '4%'}), - - # Line Width - html.Div([ - html.Label("Line Width:", style={'font-weight': 'bold', 'margin-bottom': '5px'}), - dcc.Slider( - id='indicator-line-width-slider', - min=1, max=5, step=1, value=2, - marks={i: str(i) for i in range(1, 6)}, - tooltip={'placement': 'bottom', 'always_visible': True} - ) - ], style={'width': '48%', 'display': 'inline-block'}) - ]) - ], style={'margin-bottom': '20px'}) - ]), - - # Modal Footer - html.Div([ - html.Button( - "Cancel", - id="cancel-indicator-btn", - style={ - 'background-color': '#6c757d', - 'color': 'white', - 'border': 'none', - 'padding': '10px 20px', - 'border-radius': '4px', - 'cursor': 'pointer', - 'margin-right': '10px' - } - ), - html.Button( - "Save Indicator", - id="save-indicator-btn", - style={ - 'background-color': '#28a745', - 'color': 'white', - 'border': 'none', - 'padding': '10px 20px', - 'border-radius': '4px', - 'cursor': 'pointer', - 'font-weight': 'bold' - } - ), - html.Div(id='save-indicator-feedback', style={'margin-top': '10px'}) - ], style={'text-align': 'right', 'border-top': '1px solid #eee', 'padding-top': '15px'}) - - ], style={ - 'background-color': 'white', - 'margin': '5% auto', - 'padding': '30px', - 'border-radius': '8px', - 'box-shadow': '0 4px 6px rgba(0, 0, 0, 0.1)', - 'width': '600px', - 'max-width': '90%', - 'max-height': '80%', - 'overflow-y': 'auto' - }) - ], - id='indicator-modal', - style={ - 'display': 'none', - 'position': 'fixed', - 'z-index': '1001', - 'left': '0', - 'top': '0', - 'width': '100%', - 'height': '100%', - 'visibility': 'hidden' - }) - ]) -]) - -def get_market_data_layout(): - """Create the market data visualization layout with indicator controls.""" - # Get available symbols and timeframes from database - symbols = get_supported_symbols() - timeframes = get_supported_timeframes() - - # Create dropdown options - symbol_options = [{'label': symbol, 'value': symbol} for symbol in symbols] - timeframe_options = [ - {'label': '1 Minute', 'value': '1m'}, - {'label': '5 Minutes', 'value': '5m'}, - {'label': '15 Minutes', 'value': '15m'}, - {'label': '1 Hour', 'value': '1h'}, - {'label': '4 Hours', 'value': '4h'}, - {'label': '1 Day', 'value': '1d'}, - ] - - # Filter timeframe options to only show those available in database - available_timeframes = [tf for tf in ['1m', '5m', '15m', '1h', '4h', '1d'] if tf in timeframes] - if not available_timeframes: - available_timeframes = ['1h'] # Default fallback - - timeframe_options = [opt for opt in timeframe_options if opt['value'] in available_timeframes] - - # Get available strategies and indicators - try: - strategy_names = get_available_strategy_names() - strategy_options = [{'label': name.replace('_', ' ').title(), 'value': name} for name in strategy_names] - - # Get user indicators from the new indicator manager - indicator_manager = get_indicator_manager() - - # Ensure default indicators exist - ensure_default_indicators() - - # Get indicators by display type - overlay_indicators = indicator_manager.get_indicators_by_type('overlay') - subplot_indicators = indicator_manager.get_indicators_by_type('subplot') - - # Create checkbox options for overlay indicators - overlay_options = [] - for indicator in overlay_indicators: - display_name = f"{indicator.name} ({indicator.type.upper()})" - overlay_options.append({'label': display_name, 'value': indicator.id}) - - # Create checkbox options for subplot indicators - subplot_options = [] - for indicator in subplot_indicators: - display_name = f"{indicator.name} ({indicator.type.upper()})" - subplot_options.append({'label': display_name, 'value': indicator.id}) - - except Exception as e: - logger.warning(f"Error loading indicator options: {e}") - strategy_options = [{'label': 'Basic Chart', 'value': 'basic'}] - overlay_options = [] - subplot_options = [] - - # Chart Configuration Panel with Add/Edit UI - chart_config_panel = html.Div([ - html.H5("🎯 Chart Configuration", style={'color': '#2c3e50', 'margin-bottom': '15px'}), - - # Add New Indicator Button - html.Div([ - html.Button( - "➕ Add New Indicator", - id="add-indicator-btn-visible", - className="btn btn-primary", - style={ - 'background-color': '#007bff', - 'color': 'white', - 'border': 'none', - 'padding': '8px 16px', - 'border-radius': '4px', - 'cursor': 'pointer', - 'margin-bottom': '15px', - 'font-weight': 'bold' - } - ) - ]), - - # Strategy Selection - html.Div([ - html.Label("Strategy Template:", style={'font-weight': 'bold', 'margin-bottom': '5px'}), - dcc.Dropdown( - id='strategy-dropdown', - options=strategy_options, - value=None, - placeholder="Select a strategy template (optional)", - style={'margin-bottom': '15px'} - ) - ]), - - # Indicator Controls with Edit Buttons - html.Div([ - # Overlay Indicators - html.Div([ - html.Label("Overlay Indicators:", style={'font-weight': 'bold', 'margin-bottom': '10px', 'display': 'block'}), - html.Div([ - # Hidden checklist for callback compatibility - dcc.Checklist( - id='overlay-indicators-checklist', - options=overlay_options, - value=[], # Start with no indicators selected - style={'display': 'none'} # Hide the basic checklist - ), - # Custom indicator list with edit buttons - html.Div(id='overlay-indicators-list', children=[ - # This will be populated dynamically - ]) - ]) - ], style={'width': '48%', 'display': 'inline-block', 'margin-right': '4%', 'vertical-align': 'top'}), - - # Subplot Indicators - html.Div([ - html.Label("Subplot Indicators:", style={'font-weight': 'bold', 'margin-bottom': '10px', 'display': 'block'}), - html.Div([ - # Hidden checklist for callback compatibility - dcc.Checklist( - id='subplot-indicators-checklist', - options=subplot_options, - value=[], # Start with no indicators selected - style={'display': 'none'} # Hide the basic checklist - ), - # Custom indicator list with edit buttons - html.Div(id='subplot-indicators-list', children=[ - # This will be populated dynamically - ]) - ]) - ], style={'width': '48%', 'display': 'inline-block', 'vertical-align': 'top'}) - ]) - ], style={ - 'border': '1px solid #bdc3c7', - 'border-radius': '8px', - 'padding': '15px', - 'background-color': '#f8f9fa', - 'margin-bottom': '20px' - }) - - # Parameter Controls Section - parameter_controls = html.Div([ - html.H5("📊 Indicator Parameters", style={'color': '#2c3e50', 'margin-bottom': '15px'}), - - # SMA/EMA Period Controls - html.Div([ - html.Label("Moving Average Period:", style={'font-weight': 'bold', 'margin-bottom': '5px'}), - dcc.Slider( - id='ma-period-slider', - min=5, max=200, step=5, value=20, - marks={i: str(i) for i in [5, 20, 50, 100, 200]}, - tooltip={'placement': 'bottom', 'always_visible': True} - ) - ], style={'margin-bottom': '20px'}), - - # RSI Period Control - html.Div([ - html.Label("RSI Period:", style={'font-weight': 'bold', 'margin-bottom': '5px'}), - dcc.Slider( - id='rsi-period-slider', - min=7, max=30, step=1, value=14, - marks={i: str(i) for i in [7, 14, 21, 30]}, - tooltip={'placement': 'bottom', 'always_visible': True} - ) - ], style={'margin-bottom': '20px'}), - - # MACD Parameters - html.Div([ - html.Label("MACD Parameters:", style={'font-weight': 'bold', 'margin-bottom': '10px'}), - html.Div([ - html.Div([ - html.Label("Fast:", style={'font-size': '12px'}), - dcc.Input( - id='macd-fast-input', - type='number', - value=12, - min=5, max=50, - style={'width': '60px', 'margin-left': '5px'} - ) - ], style={'display': 'inline-block', 'margin-right': '15px'}), - html.Div([ - html.Label("Slow:", style={'font-size': '12px'}), - dcc.Input( - id='macd-slow-input', - type='number', - value=26, - min=10, max=100, - style={'width': '60px', 'margin-left': '5px'} - ) - ], style={'display': 'inline-block', 'margin-right': '15px'}), - html.Div([ - html.Label("Signal:", style={'font-size': '12px'}), - dcc.Input( - id='macd-signal-input', - type='number', - value=9, - min=3, max=20, - style={'width': '60px', 'margin-left': '5px'} - ) - ], style={'display': 'inline-block'}) - ]) - ], style={'margin-bottom': '20px'}), - - # Bollinger Bands Parameters - html.Div([ - html.Label("Bollinger Bands:", style={'font-weight': 'bold', 'margin-bottom': '10px'}), - html.Div([ - html.Div([ - html.Label("Period:", style={'font-size': '12px'}), - dcc.Input( - id='bb-period-input', - type='number', - value=20, - min=5, max=50, - style={'width': '60px', 'margin-left': '5px'} - ) - ], style={'display': 'inline-block', 'margin-right': '15px'}), - html.Div([ - html.Label("Std Dev:", style={'font-size': '12px'}), - dcc.Input( - id='bb-stddev-input', - type='number', - value=2.0, - min=1.0, max=3.0, step=0.1, - style={'width': '70px', 'margin-left': '5px'} - ) - ], style={'display': 'inline-block'}) - ]) - ]) - ], style={ - 'border': '1px solid #bdc3c7', - 'border-radius': '8px', - 'padding': '15px', - 'background-color': '#f8f9fa', - 'margin-bottom': '20px' - }) - - # Auto-update control - auto_update_control = html.Div([ - dcc.Checklist( - id='auto-update-checkbox', - options=[{'label': ' Auto-update charts', 'value': 'auto'}], - value=['auto'], - style={'margin-bottom': '10px'} - ), - html.Div(id='update-status', style={'font-size': '12px', 'color': '#7f8c8d'}) - ]) - - return html.Div([ - # Title and basic controls - html.H3("💹 Market Data Visualization", style={'color': '#2c3e50', 'margin-bottom': '20px'}), - - # Main chart controls - html.Div([ - html.Div([ - html.Label("Symbol:", style={'font-weight': 'bold'}), - dcc.Dropdown( - id='symbol-dropdown', - options=symbol_options, - value=symbols[0] if symbols else 'BTC-USDT', - clearable=False, - style={'margin-bottom': '10px'} - ) - ], style={'width': '48%', 'display': 'inline-block'}), - html.Div([ - html.Label("Timeframe:", style={'font-weight': 'bold'}), - dcc.Dropdown( - id='timeframe-dropdown', - options=timeframe_options, - value='1h', - clearable=False, - style={'margin-bottom': '10px'} - ) - ], style={'width': '48%', 'float': 'right', 'display': 'inline-block'}) - ], style={'margin-bottom': '20px'}), - - # Chart Configuration Panel - chart_config_panel, - - # Parameter Controls Section - parameter_controls, - - # Auto-update control - auto_update_control, - - # Chart - dcc.Graph(id='price-chart'), - - # Market statistics - html.Div(id='market-stats', style={'margin-top': '20px'}) - ]) - -def get_bot_management_layout(): - """Create the bot management layout.""" - return html.Div([ - html.H2("🤖 Bot Management", style={'color': '#2c3e50'}), - html.P("Bot management interface will be implemented in Phase 4.0"), - - # Placeholder for bot list - html.Div([ - html.H3("Active Bots"), - html.Div(id='bot-list', children=[ - html.P("No bots currently running", style={'color': '#7f8c8d'}) - ]) - ], style={'margin': '20px 0'}) - ]) - -def get_performance_layout(): - """Create the performance monitoring layout.""" - return html.Div([ - html.H2("📈 Performance Analytics", style={'color': '#2c3e50'}), - html.P("Performance analytics will be implemented in Phase 6.0"), - - # Placeholder for performance metrics - html.Div([ - html.H3("Portfolio Performance"), - html.P("Portfolio tracking coming soon", style={'color': '#7f8c8d'}) - ], style={'margin': '20px 0'}) - ]) - -def get_system_health_layout(): - """Create the system health monitoring layout.""" - return html.Div([ - html.H2("⚙️ System Health", style={'color': '#2c3e50'}), - - # Database status - html.Div([ - html.H3("Database Status"), - html.Div(id='database-status') - ], style={'margin': '20px 0'}), - - # Data collection status - html.Div([ - html.H3("Data Collection Status"), - html.Div(id='collection-status') - ], style={'margin': '20px 0'}), - - # Redis status - html.Div([ - html.H3("Redis Status"), - html.Div(id='redis-status') - ], style={'margin': '20px 0'}) - ]) - -# Tab switching callback -@app.callback( - Output('tab-content', 'children'), - Input('main-tabs', 'value') -) -def render_tab_content(active_tab): - """Render content based on selected tab.""" - if active_tab == 'market-data': - return get_market_data_layout() - elif active_tab == 'bot-management': - return get_bot_management_layout() - elif active_tab == 'performance': - return get_performance_layout() - elif active_tab == 'system-health': - return get_system_health_layout() - else: - return html.Div("Tab not found") - -# Market data chart callback -@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"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"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"Loaded strategy {selected_strategy}: {len(overlay_indicators)} overlays, {len(subplot_indicators)} subplots") - return overlay_indicators, subplot_indicators - else: - logger.warning(f"Strategy {selected_strategy} not found") - return [], [] - - except Exception as e: - logger.error(f"Error loading strategy indicators: {e}") - return [], [] - -# Market statistics callback -@app.callback( - Output('market-stats', 'children'), - [Input('symbol-dropdown', 'value'), - Input('interval-component', 'n_intervals')] -) -def update_market_stats(symbol, n_intervals): - """Update market statistics.""" - try: - # Get real market statistics from database - stats = get_market_statistics(symbol) - - return html.Div([ - html.H3("Market Statistics"), - 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'}) - ], style={'margin': '5px 0'}) for key, value in stats.items() - ]) - ]) - - except Exception as e: - logger.error(f"Error updating market stats: {e}") - return html.Div("Error loading market statistics") - -# System health callbacks -@app.callback( - Output('database-status', 'children'), - Input('interval-component', 'n_intervals') -) -def update_database_status(n_intervals): - """Update database connection status.""" - try: - db_manager = DatabaseManager() - - # Test database connection - with db_manager.get_session() as session: - # Simple query to test connection - result = session.execute("SELECT 1").fetchone() - - if result: - return html.Div([ - html.Span("🟢 Connected", style={'color': '#27ae60', 'font-weight': 'bold'}), - html.P(f"Last checked: {datetime.now().strftime('%H:%M:%S')}", - style={'margin': '5px 0', 'color': '#7f8c8d'}) - ]) - else: - return html.Div([ - html.Span("🔴 Connection Error", style={'color': '#e74c3c', 'font-weight': 'bold'}) - ]) - - except Exception as e: - logger.error(f"Database status check failed: {e}") - return html.Div([ - html.Span("🔴 Connection Failed", style={'color': '#e74c3c', 'font-weight': 'bold'}), - html.P(f"Error: {str(e)}", style={'color': '#7f8c8d', 'font-size': '12px'}) - ]) - -@app.callback( - Output('data-status', 'children'), - [Input('symbol-dropdown', 'value'), - Input('timeframe-dropdown', 'value'), - Input('interval-component', 'n_intervals')] -) -def update_data_status(symbol, timeframe, n_intervals): - """Update data collection status.""" - try: - # Check real data availability - status = check_data_availability(symbol, timeframe) - - return html.Div([ - html.H3("Data Collection Status"), - html.Div([ - html.Div( - create_data_status_indicator(symbol, timeframe), - style={'margin': '10px 0'} - ), - html.P(f"Checking data for {symbol} {timeframe}", - style={'color': '#7f8c8d', 'margin': '5px 0', 'font-style': 'italic'}) - ], style={'background-color': '#f8f9fa', 'padding': '15px', 'border-radius': '5px'}) - ]) - - except Exception as e: - logger.error(f"Error updating data status: {e}") - return html.Div([ - html.H3("Data Collection Status"), - html.Div([ - html.Span("🔴 Status Check Failed", style={'color': '#e74c3c', 'font-weight': 'bold'}), - html.P(f"Error: {str(e)}", style={'color': '#7f8c8d', 'margin': '5px 0'}) - ]) - ]) - -# Modal control callbacks -@app.callback( - [Output('indicator-modal', 'style'), - Output('indicator-modal-background', 'style')], - [Input('add-indicator-btn', 'n_clicks'), - Input('close-modal-btn', 'n_clicks'), - Input('cancel-indicator-btn', 'n_clicks'), - Input('edit-indicator-store', 'data')] -) -def toggle_indicator_modal(add_clicks, close_clicks, cancel_clicks, edit_data): - """Toggle the visibility of the add indicator modal.""" - - # Default hidden styles - hidden_modal_style = { - 'display': 'none', - 'position': 'fixed', - 'z-index': '1001', - 'left': '0', - 'top': '0', - 'width': '100%', - 'height': '100%', - 'visibility': 'hidden' - } - - hidden_background_style = { - 'display': 'none', - 'position': 'fixed', - 'z-index': '1000', - 'left': '0', - 'top': '0', - 'width': '100%', - 'height': '100%', - 'background-color': 'rgba(0,0,0,0.5)', - 'visibility': 'hidden' - } - - # Visible styles - visible_modal_style = { - 'display': 'block', - 'position': 'fixed', - 'z-index': '1001', - 'left': '0', - 'top': '0', - 'width': '100%', - 'height': '100%', - 'visibility': 'visible' - } - - visible_background_style = { - 'display': 'block', - 'position': 'fixed', - 'z-index': '1000', - 'left': '0', - 'top': '0', - 'width': '100%', - 'height': '100%', - 'background-color': 'rgba(0,0,0,0.5)', - 'visibility': 'visible' - } - - ctx = dash.callback_context - - # If no trigger or initial load, return hidden - if not ctx.triggered: - return [hidden_modal_style, hidden_background_style] - - triggered_id = ctx.triggered[0]['prop_id'].split('.')[0] - - # Only open modal if explicitly requested - should_open = False - - # Check if add button was clicked (and has a click count > 0) - if triggered_id == 'add-indicator-btn' and add_clicks and add_clicks > 0: - should_open = True - - # Check if edit button triggered and should open modal - elif triggered_id == 'edit-indicator-store' and edit_data and edit_data.get('open_modal') and edit_data.get('mode') == 'edit': - should_open = True - - # Check if close/cancel buttons were clicked - elif triggered_id in ['close-modal-btn', 'cancel-indicator-btn']: - should_open = False - - # Default: don't open - else: - should_open = False - - if should_open: - return [visible_modal_style, visible_background_style] - else: - return [hidden_modal_style, hidden_background_style] - -# Sync visible button clicks to hidden button -@app.callback( - Output('add-indicator-btn', 'n_clicks'), - Input('add-indicator-btn-visible', 'n_clicks'), - prevent_initial_call=True -) -def sync_add_button_clicks(visible_clicks): - """Sync clicks from visible button to hidden button.""" - return visible_clicks or 0 - -# Update parameter fields based on indicator type -@app.callback( - [Output('indicator-parameters-message', 'style'), - Output('sma-parameters', 'style'), - Output('ema-parameters', 'style'), - Output('rsi-parameters', 'style'), - Output('macd-parameters', 'style'), - Output('bb-parameters', 'style')], - Input('indicator-type-dropdown', 'value'), - prevent_initial_call=True -) -def update_parameter_fields(indicator_type): - """Show/hide parameter input fields based on selected indicator type.""" - # Default styles - hidden_style = {'display': 'none', 'margin-bottom': '10px'} - visible_style = {'display': 'block', 'margin-bottom': '10px'} - - # Default message visibility - message_style = {'display': 'block'} if not indicator_type else {'display': 'none'} - - # Initialize all as hidden - sma_style = hidden_style - ema_style = hidden_style - rsi_style = hidden_style - macd_style = hidden_style - bb_style = hidden_style - - # Show the relevant parameter section - if indicator_type == 'sma': - sma_style = visible_style - elif indicator_type == 'ema': - ema_style = visible_style - elif indicator_type == 'rsi': - rsi_style = visible_style - elif indicator_type == 'macd': - macd_style = visible_style - elif indicator_type == 'bollinger_bands': - bb_style = visible_style - - return message_style, sma_style, ema_style, rsi_style, macd_style, bb_style - -# Save indicator callback -@app.callback( - [Output('save-indicator-feedback', 'children'), - Output('overlay-indicators-checklist', 'options'), - Output('subplot-indicators-checklist', 'options')], - Input('save-indicator-btn', 'n_clicks'), - [State('indicator-name-input', 'value'), - State('indicator-type-dropdown', 'value'), - State('indicator-description-input', 'value'), - State('indicator-color-input', 'value'), - State('indicator-line-width-slider', 'value'), - # SMA parameters - State('sma-period-input', 'value'), - # EMA parameters - State('ema-period-input', 'value'), - # RSI parameters - State('rsi-period-input', 'value'), - # MACD parameters - State('macd-fast-period-input', 'value'), - State('macd-slow-period-input', 'value'), - State('macd-signal-period-input', 'value'), - # Bollinger Bands parameters - State('bb-period-input', 'value'), - State('bb-stddev-input', 'value'), - # Edit mode data - State('edit-indicator-store', 'data')], - prevent_initial_call=True -) -def save_new_indicator(n_clicks, name, indicator_type, description, color, line_width, - sma_period, ema_period, rsi_period, - macd_fast, macd_slow, macd_signal, - bb_period, bb_stddev, edit_data): - """Save a new indicator or update an existing one.""" - if not n_clicks or not name or not indicator_type: - return "", dash.no_update, dash.no_update - - try: - # Get indicator manager - from components.charts.indicator_manager import get_indicator_manager - manager = get_indicator_manager() - - # Collect parameters based on indicator type and actual input values - parameters = {} - - if indicator_type == 'sma': - parameters = {'period': sma_period or 20} - elif indicator_type == 'ema': - parameters = {'period': ema_period or 12} - elif indicator_type == 'rsi': - parameters = {'period': rsi_period or 14} - elif indicator_type == 'macd': - parameters = { - 'fast_period': macd_fast or 12, - 'slow_period': macd_slow or 26, - 'signal_period': macd_signal or 9 - } - elif indicator_type == 'bollinger_bands': - parameters = { - 'period': bb_period or 20, - 'std_dev': bb_stddev or 2.0 - } - - # Check if this is an edit operation - is_edit = edit_data and edit_data.get('mode') == 'edit' - - if is_edit: - # Update existing indicator - indicator_id = edit_data.get('indicator_id') - success = manager.update_indicator( - indicator_id, - name=name, - description=description or "", - parameters=parameters, - styling={'color': color or "#007bff", 'line_width': line_width or 2} - ) - - if success: - success_msg = html.Div([ - html.Span("✅ ", style={'color': '#28a745'}), - html.Span(f"Indicator '{name}' updated successfully!", style={'color': '#28a745'}) - ]) - else: - error_msg = html.Div([ - html.Span("❌ ", style={'color': '#dc3545'}), - html.Span("Failed to update indicator. Please try again.", style={'color': '#dc3545'}) - ]) - return error_msg, dash.no_update, dash.no_update - else: - # Create new indicator - new_indicator = manager.create_indicator( - name=name, - indicator_type=indicator_type, - parameters=parameters, - description=description or "", - color=color or "#007bff" - ) - - if not new_indicator: - error_msg = html.Div([ - html.Span("❌ ", style={'color': '#dc3545'}), - html.Span("Failed to save indicator. Please try again.", style={'color': '#dc3545'}) - ]) - return error_msg, dash.no_update, dash.no_update - - success_msg = html.Div([ - html.Span("✅ ", style={'color': '#28a745'}), - html.Span(f"Indicator '{name}' saved successfully!", style={'color': '#28a745'}) - ]) - - # Refresh the indicator options - overlay_indicators = manager.get_indicators_by_type('overlay') - subplot_indicators = manager.get_indicators_by_type('subplot') - - overlay_options = [] - for indicator in overlay_indicators: - display_name = f"{indicator.name} ({indicator.type.upper()})" - overlay_options.append({'label': display_name, 'value': indicator.id}) - - subplot_options = [] - for indicator in subplot_indicators: - display_name = f"{indicator.name} ({indicator.type.upper()})" - subplot_options.append({'label': display_name, 'value': indicator.id}) - - return success_msg, overlay_options, subplot_options - - except Exception as e: - logger.error(f"Error saving indicator: {e}") - error_msg = html.Div([ - html.Span("❌ ", style={'color': '#dc3545'}), - html.Span(f"Error: {str(e)}", style={'color': '#dc3545'}) - ]) - return error_msg, dash.no_update, dash.no_update - -# Update custom indicator lists with edit/delete buttons -@app.callback( - [Output('overlay-indicators-list', 'children'), - Output('subplot-indicators-list', 'children')], - [Input('overlay-indicators-checklist', 'options'), - Input('subplot-indicators-checklist', 'options'), - Input('overlay-indicators-checklist', 'value'), - Input('subplot-indicators-checklist', 'value')] -) -def update_custom_indicator_lists(overlay_options, subplot_options, overlay_values, subplot_values): - """Create custom indicator lists with edit and delete buttons.""" - - def create_indicator_item(option, is_checked): - """Create a single indicator item with checkbox and buttons.""" - indicator_id = option['value'] - indicator_name = option['label'] - - return html.Div([ - # Checkbox and name - html.Div([ - dcc.Checklist( - options=[{'label': '', 'value': indicator_id}], - value=[indicator_id] if is_checked else [], - id={'type': 'indicator-checkbox', 'index': indicator_id}, - style={'display': 'inline-block', 'margin-right': '8px'} - ), - html.Span(indicator_name, style={'display': 'inline-block', 'vertical-align': 'top'}) - ], style={'display': 'inline-block', 'width': '70%'}), - - # Edit and Delete buttons - html.Div([ - html.Button( - "✏️", - id={'type': 'edit-indicator-btn', 'index': indicator_id}, - title="Edit indicator", - style={ - 'background': 'none', - 'border': 'none', - 'cursor': 'pointer', - 'margin-left': '5px', - 'font-size': '14px', - 'color': '#007bff' - } - ), - html.Button( - "🗑️", - id={'type': 'delete-indicator-btn', 'index': indicator_id}, - title="Delete indicator", - style={ - 'background': 'none', - 'border': 'none', - 'cursor': 'pointer', - 'margin-left': '5px', - 'font-size': '14px', - 'color': '#dc3545' - } - ) - ], style={'display': 'inline-block', 'width': '30%', 'text-align': 'right'}) - ], style={ - 'display': 'block', - 'padding': '5px 0', - 'border-bottom': '1px solid #f0f0f0', - 'margin-bottom': '5px' - }) - - # Create overlay indicators list - overlay_list = [] - for option in overlay_options: - is_checked = option['value'] in (overlay_values or []) - overlay_list.append(create_indicator_item(option, is_checked)) - - # Create subplot indicators list - subplot_list = [] - for option in subplot_options: - is_checked = option['value'] in (subplot_values or []) - subplot_list.append(create_indicator_item(option, is_checked)) - - return overlay_list, subplot_list - -# Sync individual indicator checkboxes with main checklist -@app.callback( - Output('overlay-indicators-checklist', 'value', allow_duplicate=True), - [Input({'type': 'indicator-checkbox', 'index': dash.ALL}, 'value')], - [State('overlay-indicators-checklist', 'options')], - prevent_initial_call=True -) -def sync_overlay_indicators(checkbox_values, overlay_options): - """Sync individual indicator checkboxes with main overlay checklist.""" - if not checkbox_values or not overlay_options: - return [] - - selected_indicators = [] - overlay_ids = [opt['value'] for opt in overlay_options] - - # Flatten the checkbox values and filter for overlay indicators - for values in checkbox_values: - if values: # values is a list, check if not empty - for indicator_id in values: - if indicator_id in overlay_ids: - selected_indicators.append(indicator_id) - - # Remove duplicates - return list(set(selected_indicators)) - -@app.callback( - Output('subplot-indicators-checklist', 'value', allow_duplicate=True), - [Input({'type': 'indicator-checkbox', 'index': dash.ALL}, 'value')], - [State('subplot-indicators-checklist', 'options')], - prevent_initial_call=True -) -def sync_subplot_indicators(checkbox_values, subplot_options): - """Sync individual indicator checkboxes with main subplot checklist.""" - if not checkbox_values or not subplot_options: - return [] - - selected_indicators = [] - subplot_ids = [opt['value'] for opt in subplot_options] - - # Flatten the checkbox values and filter for subplot indicators - for values in checkbox_values: - if values: # values is a list, check if not empty - for indicator_id in values: - if indicator_id in subplot_ids: - selected_indicators.append(indicator_id) - - # Remove duplicates - return list(set(selected_indicators)) - -# Handle delete indicator -@app.callback( - [Output('save-indicator-feedback', 'children', allow_duplicate=True), - Output('overlay-indicators-checklist', 'options', allow_duplicate=True), - Output('subplot-indicators-checklist', 'options', allow_duplicate=True)], - [Input({'type': 'delete-indicator-btn', 'index': dash.ALL}, 'n_clicks')], - [State({'type': 'delete-indicator-btn', 'index': dash.ALL}, 'id')], - prevent_initial_call=True -) -def delete_indicator(delete_clicks, button_ids): - """Delete an indicator when delete button is clicked.""" - ctx = dash.callback_context - if not ctx.triggered or not any(delete_clicks): - return dash.no_update, dash.no_update, dash.no_update - - # Find which button was clicked - triggered_id = ctx.triggered[0]['prop_id'] - import json - button_info = json.loads(triggered_id.split('.')[0]) - indicator_id = button_info['index'] - - try: - # Get indicator manager and delete the indicator - from components.charts.indicator_manager import get_indicator_manager - manager = get_indicator_manager() - - # Load indicator to get its name before deletion - indicator = manager.load_indicator(indicator_id) - indicator_name = indicator.name if indicator else indicator_id - - if manager.delete_indicator(indicator_id): - # Refresh the indicator options - overlay_indicators = manager.get_indicators_by_type('overlay') - subplot_indicators = manager.get_indicators_by_type('subplot') - - overlay_options = [] - for indicator in overlay_indicators: - display_name = f"{indicator.name} ({indicator.type.upper()})" - overlay_options.append({'label': display_name, 'value': indicator.id}) - - subplot_options = [] - for indicator in subplot_indicators: - display_name = f"{indicator.name} ({indicator.type.upper()})" - subplot_options.append({'label': display_name, 'value': indicator.id}) - - success_msg = html.Div([ - html.Span("🗑️ ", style={'color': '#dc3545'}), - html.Span(f"Indicator '{indicator_name}' deleted successfully!", style={'color': '#dc3545'}) - ]) - - return success_msg, overlay_options, subplot_options - else: - error_msg = html.Div([ - html.Span("❌ ", style={'color': '#dc3545'}), - html.Span("Failed to delete indicator.", style={'color': '#dc3545'}) - ]) - return error_msg, dash.no_update, dash.no_update - - except Exception as e: - logger.error(f"Error deleting indicator: {e}") - error_msg = html.Div([ - html.Span("❌ ", style={'color': '#dc3545'}), - html.Span(f"Error: {str(e)}", style={'color': '#dc3545'}) - ]) - return error_msg, dash.no_update, dash.no_update - -# Handle edit indicator - open modal with existing data -@app.callback( - [Output('modal-title', 'children'), - Output('indicator-name-input', 'value'), - Output('indicator-type-dropdown', 'value'), - Output('indicator-description-input', 'value'), - Output('indicator-color-input', 'value'), - Output('edit-indicator-store', 'data'), - # Add parameter field outputs - Output('sma-period-input', 'value'), - Output('ema-period-input', 'value'), - Output('rsi-period-input', 'value'), - Output('macd-fast-period-input', 'value'), - Output('macd-slow-period-input', 'value'), - Output('macd-signal-period-input', 'value'), - Output('bb-period-input', 'value'), - Output('bb-stddev-input', 'value')], - [Input({'type': 'edit-indicator-btn', 'index': dash.ALL}, 'n_clicks')], - [State({'type': 'edit-indicator-btn', 'index': dash.ALL}, 'id')], - prevent_initial_call=True -) -def edit_indicator(edit_clicks, button_ids): - """Load indicator data for editing.""" - ctx = dash.callback_context - if not ctx.triggered or not any(edit_clicks): - return dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update - - # Find which button was clicked - triggered_id = ctx.triggered[0]['prop_id'] - import json - button_info = json.loads(triggered_id.split('.')[0]) - indicator_id = button_info['index'] - - try: - # Load the indicator data - from components.charts.indicator_manager import get_indicator_manager - manager = get_indicator_manager() - indicator = manager.load_indicator(indicator_id) - - if indicator: - # Store indicator ID for update - edit_data = {'indicator_id': indicator_id, 'mode': 'edit', 'open_modal': True} - - # Extract parameter values based on indicator type - params = indicator.parameters - - # Default parameter values - sma_period = 20 - ema_period = 12 - rsi_period = 14 - macd_fast = 12 - macd_slow = 26 - macd_signal = 9 - bb_period = 20 - bb_stddev = 2.0 - - # Update with actual saved values - if indicator.type == 'sma': - sma_period = params.get('period', 20) - elif indicator.type == 'ema': - ema_period = params.get('period', 12) - elif indicator.type == 'rsi': - rsi_period = params.get('period', 14) - elif indicator.type == 'macd': - macd_fast = params.get('fast_period', 12) - macd_slow = params.get('slow_period', 26) - macd_signal = params.get('signal_period', 9) - elif indicator.type == 'bollinger_bands': - bb_period = params.get('period', 20) - bb_stddev = params.get('std_dev', 2.0) - - return ( - "✏️ Edit Indicator", - indicator.name, - indicator.type, - indicator.description, - indicator.styling.color, - edit_data, - sma_period, - ema_period, - rsi_period, - macd_fast, - macd_slow, - macd_signal, - bb_period, - bb_stddev - ) - else: - return dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update - - except Exception as e: - logger.error(f"Error loading indicator for edit: {e}") - return dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update - -# Reset modal form when closed -@app.callback( - [Output('indicator-name-input', 'value', allow_duplicate=True), - Output('indicator-type-dropdown', 'value', allow_duplicate=True), - Output('indicator-description-input', 'value', allow_duplicate=True), - Output('indicator-color-input', 'value', allow_duplicate=True), - Output('indicator-line-width-slider', 'value'), - Output('modal-title', 'children', allow_duplicate=True), - Output('edit-indicator-store', 'data', allow_duplicate=True), - # Add parameter field resets - Output('sma-period-input', 'value', allow_duplicate=True), - Output('ema-period-input', 'value', allow_duplicate=True), - Output('rsi-period-input', 'value', allow_duplicate=True), - Output('macd-fast-period-input', 'value', allow_duplicate=True), - Output('macd-slow-period-input', 'value', allow_duplicate=True), - Output('macd-signal-period-input', 'value', allow_duplicate=True), - Output('bb-period-input', 'value', allow_duplicate=True), - Output('bb-stddev-input', 'value', allow_duplicate=True)], - [Input('close-modal-btn', 'n_clicks'), - Input('cancel-indicator-btn', 'n_clicks')], - prevent_initial_call=True -) -def reset_modal_form(close_clicks, cancel_clicks): - """Reset the modal form when it's closed.""" - if close_clicks or cancel_clicks: - return "", None, "", "#007bff", 2, "📊 Add New Indicator", None, 20, 12, 14, 12, 26, 9, 20, 2.0 - return dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update - -def main(): - """Main function to run the dashboard.""" - try: - logger.info("Starting Crypto Trading Bot Dashboard") - logger.info(f"Dashboard will be available at: http://{dashboard_settings.host}:{dashboard_settings.port}") - - # Run the app - app.run( - host=dashboard_settings.host, - port=dashboard_settings.port, - debug=dashboard_settings.debug - ) - - except Exception as e: - logger.error(f"Failed to start dashboard: {e}") - sys.exit(1) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/dashboard/components/__init__.py b/dashboard/components/__init__.py index 35660fe..01325dd 100644 --- a/dashboard/components/__init__.py +++ b/dashboard/components/__init__.py @@ -2,11 +2,10 @@ Reusable UI components for the dashboard. """ +from .chart_controls import create_chart_config_panel from .indicator_modal import create_indicator_modal -from .chart_controls import create_chart_config_panel, create_parameter_controls __all__ = [ - 'create_indicator_modal', 'create_chart_config_panel', - 'create_parameter_controls' + 'create_indicator_modal' ] \ No newline at end of file diff --git a/dashboard/components/chart_controls.py b/dashboard/components/chart_controls.py index d1cbf67..05702fe 100644 --- a/dashboard/components/chart_controls.py +++ b/dashboard/components/chart_controls.py @@ -91,105 +91,6 @@ def create_chart_config_panel(strategy_options, overlay_options, subplot_options }) -def create_parameter_controls(): - """Create the parameter controls section for indicator configuration.""" - return html.Div([ - html.H5("📊 Indicator Parameters", style={'color': '#2c3e50', 'margin-bottom': '15px'}), - - # SMA/EMA Period Controls - html.Div([ - html.Label("Moving Average Period:", style={'font-weight': 'bold', 'margin-bottom': '5px'}), - dcc.Slider( - id='ma-period-slider', - min=5, max=200, step=5, value=20, - marks={i: str(i) for i in [5, 20, 50, 100, 200]}, - tooltip={'placement': 'bottom', 'always_visible': True} - ) - ], style={'margin-bottom': '20px'}), - - # RSI Period Control - html.Div([ - html.Label("RSI Period:", style={'font-weight': 'bold', 'margin-bottom': '5px'}), - dcc.Slider( - id='rsi-period-slider', - min=7, max=30, step=1, value=14, - marks={i: str(i) for i in [7, 14, 21, 30]}, - tooltip={'placement': 'bottom', 'always_visible': True} - ) - ], style={'margin-bottom': '20px'}), - - # MACD Parameters - html.Div([ - html.Label("MACD Parameters:", style={'font-weight': 'bold', 'margin-bottom': '10px'}), - html.Div([ - html.Div([ - html.Label("Fast:", style={'font-size': '12px'}), - dcc.Input( - id='macd-fast-input', - type='number', - value=12, - min=5, max=50, - style={'width': '60px', 'margin-left': '5px'} - ) - ], style={'display': 'inline-block', 'margin-right': '15px'}), - html.Div([ - html.Label("Slow:", style={'font-size': '12px'}), - dcc.Input( - id='macd-slow-input', - type='number', - value=26, - min=10, max=100, - style={'width': '60px', 'margin-left': '5px'} - ) - ], style={'display': 'inline-block', 'margin-right': '15px'}), - html.Div([ - html.Label("Signal:", style={'font-size': '12px'}), - dcc.Input( - id='macd-signal-input', - type='number', - value=9, - min=3, max=20, - style={'width': '60px', 'margin-left': '5px'} - ) - ], style={'display': 'inline-block'}) - ]) - ], style={'margin-bottom': '20px'}), - - # Bollinger Bands Parameters - html.Div([ - html.Label("Bollinger Bands:", style={'font-weight': 'bold', 'margin-bottom': '10px'}), - html.Div([ - html.Div([ - html.Label("Period:", style={'font-size': '12px'}), - dcc.Input( - id='bb-period-input', - type='number', - value=20, - min=5, max=50, - style={'width': '60px', 'margin-left': '5px'} - ) - ], style={'display': 'inline-block', 'margin-right': '15px'}), - html.Div([ - html.Label("Std Dev:", style={'font-size': '12px'}), - dcc.Input( - id='bb-stddev-input', - type='number', - value=2.0, - min=1.0, max=3.0, step=0.1, - style={'width': '70px', 'margin-left': '5px'} - ) - ], style={'display': 'inline-block'}) - ]) - ]) - ], style={ - 'border': '1px solid #bdc3c7', - 'border-radius': '8px', - 'padding': '15px', - 'background-color': '#f8f9fa', - 'margin-bottom': '20px' - }) - - def create_auto_update_control(): """Create the auto-update control section.""" return html.Div([ diff --git a/dashboard/layouts/market_data.py b/dashboard/layouts/market_data.py index e717e5e..756d2b0 100644 --- a/dashboard/layouts/market_data.py +++ b/dashboard/layouts/market_data.py @@ -10,7 +10,6 @@ from components.charts.indicator_manager import get_indicator_manager from components.charts.indicator_defaults import ensure_default_indicators from dashboard.components.chart_controls import ( create_chart_config_panel, - create_parameter_controls, create_auto_update_control ) @@ -76,7 +75,6 @@ def get_market_data_layout(): # Create components using the new modular functions chart_config_panel = create_chart_config_panel(strategy_options, overlay_options, subplot_options) - parameter_controls = create_parameter_controls() auto_update_control = create_auto_update_control() return html.Div([ @@ -110,9 +108,6 @@ def get_market_data_layout(): # Chart Configuration Panel chart_config_panel, - # Parameter Controls Section - parameter_controls, - # Auto-update control auto_update_control, diff --git a/pyproject.toml b/pyproject.toml index 430d78a..9fb3f86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,7 @@ requires-python = ">=3.10" dependencies = [ # Core web framework "dash>=2.14.0", + "dash-mantine-components>=0.12.0", "plotly>=5.17.0", # Database "sqlalchemy>=2.0.0", diff --git a/tasks/3.4. Chart layers.md b/tasks/3.4. Chart layers.md index 9527c1b..cc1d13a 100644 --- a/tasks/3.4. Chart layers.md +++ b/tasks/3.4. Chart layers.md @@ -75,14 +75,14 @@ Implementation of a flexible, strategy-driven chart system that supports technic - [x] 3.6 Add enhanced error handling and user guidance for missing strategies and indicators - [x] 3.7 Unit test configuration system and validation -- [x] 4.0 Dashboard Integration and UI Controls **✅ COMPLETED** +- [x] 4.0 Dashboard Integration and UI Controls - [x] 4.1 Add indicator selection checkboxes to dashboard layout - [x] 4.2 Create real-time chart updates with indicator toggling - [x] 4.3 Implement parameter adjustment controls for indicators - - [x] 4.4 Add strategy selection dropdown for predefined configurations **✅ WORKING** - - [x] 4.5 Update chart callback functions to handle new layer system **✅ COMPLETED - Modular callbacks** - - [x] 4.6 Ensure backward compatibility with existing dashboard features **✅ COMPLETED** - - [x] 4.7 Test dashboard integration with real market data **✅ COMPLETED - Confirmed working** + - [x] 4.4 Add strategy selection dropdown for predefined configurations + - [x] 4.5 Update chart callback functions to handle new layer system + - [x] 4.6 Ensure backward compatibility with existing dashboard features + - [x] 4.7 Test dashboard integration with real market data - [ ] 5.0 Signal Layer Foundation for Future Bot Integration - [ ] 5.1 Create signal layer architecture for buy/sell markers @@ -94,13 +94,13 @@ Implementation of a flexible, strategy-driven chart system that supports technic - [ ] 5.7 Create foundation tests for signal layer functionality - [ ] 6.0 Documentation **⏳ IN PROGRESS** - - [x] 6.1 Create documentation for the chart layers system **✅ COMPLETED** + - [x] 6.1 Create documentation for the chart layers system - [ ] 6.2 Add documentation to the README - - [x] 6.3 Create documentation for the ChartBuilder class **✅ COMPLETED** - - [x] 6.4 Create documentation for the ChartUtils class **✅ COMPLETED** - - [x] 6.5 Create documentation for the ChartConfig package **✅ COMPLETED** - - [x] 6.6 Create documentation how to add new indicators **✅ COMPLETED** - - [x] 6.7 Create documentation how to add new strategies **✅ COMPLETED** + - [x] 6.3 Create documentation for the ChartBuilder class + - [x] 6.4 Create documentation for the ChartUtils class + - [x] 6.5 Create documentation for the ChartConfig package + - [x] 6.6 Create documentation how to add new indicators + - [x] 6.7 Create documentation how to add new strategies ## Current Status @@ -108,7 +108,7 @@ Implementation of a flexible, strategy-driven chart system that supports technic - **1.0 Foundation Infrastructure**: Fully implemented with modular charts system - **2.0 Indicator Layer System**: Complete implementation with all indicator types - **3.0 Strategy Configuration**: Comprehensive strategy system with validation -- **4.0 Dashboard Integration**: **FULLY COMPLETED** including modular dashboard structure +- **4.0 Dashboard Integration**: Including modular dashboard structure ### 🎯 **KEY ACHIEVEMENTS** - **Strategy dropdown**: Fully functional with auto-loading of strategy indicators diff --git a/uv.lock b/uv.lock index 3d7c42b..e68db62 100644 --- a/uv.lock +++ b/uv.lock @@ -388,6 +388,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0d/20/2e7ab37ea2ef1f8b2592a2615c8b3fb041ad51f32101061d8bc6465b8b40/dash-3.0.4-py3-none-any.whl", hash = "sha256:177f8c3d1fa45555b18f2f670808eba7803c72a6b1cd6fd172fd538aca18eb1d", size = 7935680 }, ] +[[package]] +name = "dash-mantine-components" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/1e/535c8312f038ea688171435cefd8b5b03452353646e43bade5d92a8d9da0/dash_mantine_components-2.0.0.tar.gz", hash = "sha256:2e09b7f60b41483a06d270c621b5f23a1a9c9321a7f60d2e2b631cde493456cb", size = 850199 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/45/a1acd23b37af85c8b824ccb3e3e4232900725830a652b762ed0c67afec2a/dash_mantine_components-2.0.0-py3-none-any.whl", hash = "sha256:e084ba1fac9a9ad8672852047d0a97dc3cd7372677d1fa55ef8e655a664fa271", size = 1262158 }, +] + [[package]] name = "dashboard" version = "0.1.0" @@ -397,6 +409,7 @@ dependencies = [ { name = "alembic" }, { name = "click" }, { name = "dash" }, + { name = "dash-mantine-components" }, { name = "numpy" }, { name = "pandas" }, { name = "plotly" }, @@ -441,6 +454,7 @@ requires-dist = [ { name = "black", marker = "extra == 'dev'", specifier = ">=23.0.0" }, { name = "click", specifier = ">=8.0.0" }, { name = "dash", specifier = ">=2.14.0" }, + { name = "dash-mantine-components", specifier = ">=0.12.0" }, { name = "flake8", marker = "extra == 'dev'", specifier = ">=6.0.0" }, { name = "isort", marker = "extra == 'dev'", specifier = ">=5.12.0" }, { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.5.0" },