Implement modular architecture for Crypto Trading Bot Dashboard
- Introduced a new modular structure for the dashboard, enhancing maintainability and scalability. - Created main application entry point in `app_new.py`, integrating all components and callbacks. - Developed layout modules for market data, bot management, performance analytics, and system health in the `layouts` directory. - Implemented callback modules for navigation, charts, indicators, and system health in the `callbacks` directory. - Established reusable UI components in the `components` directory, including chart controls and indicator modals. - Enhanced documentation to reflect the new modular structure and provide clear usage guidelines. - Ensured all components are under 300-400 lines for better readability and maintainability.
This commit is contained in:
12
dashboard/components/__init__.py
Normal file
12
dashboard/components/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
"""
|
||||
Reusable UI components for the dashboard.
|
||||
"""
|
||||
|
||||
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'
|
||||
]
|
||||
203
dashboard/components/chart_controls.py
Normal file
203
dashboard/components/chart_controls.py
Normal file
@@ -0,0 +1,203 @@
|
||||
"""
|
||||
Chart control components for the market data layout.
|
||||
"""
|
||||
|
||||
from dash import html, dcc
|
||||
from utils.logger import get_logger
|
||||
|
||||
logger = get_logger("chart_controls")
|
||||
|
||||
|
||||
def create_chart_config_panel(strategy_options, overlay_options, subplot_options):
|
||||
"""Create the chart configuration panel with add/edit UI."""
|
||||
return 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'
|
||||
})
|
||||
|
||||
|
||||
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([
|
||||
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'})
|
||||
])
|
||||
290
dashboard/components/indicator_modal.py
Normal file
290
dashboard/components/indicator_modal.py
Normal file
@@ -0,0 +1,290 @@
|
||||
"""
|
||||
Indicator modal component for creating and editing indicators.
|
||||
"""
|
||||
|
||||
from dash import html, dcc
|
||||
|
||||
|
||||
def create_indicator_modal():
|
||||
"""Create the indicator modal dialog for adding/editing indicators."""
|
||||
return 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'
|
||||
})
|
||||
])
|
||||
Reference in New Issue
Block a user