Removed Mantine for UI, and used bootstrap for simplicity

This commit is contained in:
Vasily.onl
2025-06-06 13:33:59 +08:00
parent 38cbf9cd2f
commit 58a754414a
12 changed files with 682 additions and 1284 deletions

View File

@@ -3,6 +3,7 @@ Chart control components for the market data layout.
"""
from dash import html, dcc
import dash_bootstrap_components as dbc
from utils.logger import get_logger
logger = get_logger("default_logger")
@@ -10,216 +11,124 @@ logger = get_logger("default_logger")
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
return dbc.Card([
dbc.CardHeader(html.H5("🎯 Chart Configuration")),
dbc.CardBody([
dbc.Button(" Add New Indicator", id="add-indicator-btn-visible", color="primary", className="mb-3"),
html.Div([
html.Label("Overlay Indicators:", style={'font-weight': 'bold', 'margin-bottom': '10px', 'display': 'block'}),
html.Div([
# Hidden checklist for callback compatibility
html.Label("Strategy Template:", className="form-label"),
dcc.Dropdown(
id='strategy-dropdown',
options=strategy_options,
value=None,
placeholder="Select a strategy template (optional)",
)
], className="mb-3"),
dbc.Row([
dbc.Col([
html.Label("Overlay Indicators:", className="form-label"),
dcc.Checklist(
id='overlay-indicators-checklist',
options=overlay_options,
value=[], # Start with no indicators selected
style={'display': 'none'} # Hide the basic checklist
value=[],
style={'display': 'none'}
),
# 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
html.Div(id='overlay-indicators-list')
], width=6),
dbc.Col([
html.Label("Subplot Indicators:", className="form-label"),
dcc.Checklist(
id='subplot-indicators-checklist',
options=subplot_options,
value=[], # Start with no indicators selected
style={'display': 'none'} # Hide the basic checklist
value=[],
style={'display': 'none'}
),
# 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'})
html.Div(id='subplot-indicators-list')
], width=6)
])
])
], style={
'border': '1px solid #bdc3c7',
'border-radius': '8px',
'padding': '15px',
'background-color': '#f8f9fa',
'margin-bottom': '20px'
})
], className="mb-4")
def create_auto_update_control():
"""Create the auto-update control section."""
return html.Div([
dcc.Checklist(
dbc.Checkbox(
id='auto-update-checkbox',
options=[{'label': ' Auto-update charts', 'value': 'auto'}],
value=['auto'],
style={'margin-bottom': '10px'}
label='Auto-update charts',
value=True,
),
html.Div(id='update-status', style={'font-size': '12px', 'color': '#7f8c8d'})
])
], className="mb-3")
def create_time_range_controls():
"""Create the time range control panel."""
return html.Div([
html.H5("⏰ Time Range Controls", style={'color': '#2c3e50', 'margin-bottom': '15px'}),
# Quick Select Dropdown
html.Div([
html.Label("Quick Select:", style={'font-weight': 'bold', 'margin-bottom': '5px', 'display': 'block'}),
dcc.Dropdown(
id='time-range-quick-select',
options=[
{'label': '🕐 Last 1 Hour', 'value': '1h'},
{'label': '🕐 Last 4 Hours', 'value': '4h'},
{'label': '🕐 Last 6 Hours', 'value': '6h'},
{'label': '🕐 Last 12 Hours', 'value': '12h'},
{'label': '📅 Last 1 Day', 'value': '1d'},
{'label': '📅 Last 3 Days', 'value': '3d'},
{'label': '📅 Last 7 Days', 'value': '7d'},
{'label': '📅 Last 30 Days', 'value': '30d'},
{'label': '📅 Custom Range', 'value': 'custom'},
{'label': '🔴 Real-time', 'value': 'realtime'}
],
value='7d',
placeholder="Select time range",
style={'margin-bottom': '15px'}
)
]),
# Custom Date Range Picker
html.Div([
html.Label("Custom Date Range:", style={'font-weight': 'bold', 'margin-bottom': '5px', 'display': 'block'}),
return dbc.Card([
dbc.CardHeader(html.H5("⏰ Time Range Controls")),
dbc.CardBody([
html.Div([
dcc.DatePickerRange(
id='custom-date-range',
display_format='YYYY-MM-DD',
style={'display': 'inline-block', 'margin-right': '10px'}
),
html.Button(
"Clear",
id="clear-date-range-btn",
className="btn btn-sm btn-outline-secondary",
style={
'display': 'inline-block',
'vertical-align': 'top',
'margin-top': '7px',
'padding': '5px 10px',
'font-size': '12px'
}
html.Label("Quick Select:", className="form-label"),
dcc.Dropdown(
id='time-range-quick-select',
options=[
{'label': '🕐 Last 1 Hour', 'value': '1h'},
{'label': '🕐 Last 4 Hours', 'value': '4h'},
{'label': '🕐 Last 6 Hours', 'value': '6h'},
{'label': '🕐 Last 12 Hours', 'value': '12h'},
{'label': '📅 Last 1 Day', 'value': '1d'},
{'label': '📅 Last 3 Days', 'value': '3d'},
{'label': '📅 Last 7 Days', 'value': '7d'},
{'label': '📅 Last 30 Days', 'value': '30d'},
{'label': '📅 Custom Range', 'value': 'custom'},
{'label': '🔴 Real-time', 'value': 'realtime'}
],
value='7d',
placeholder="Select time range",
)
], style={'margin-bottom': '15px'})
]),
# Analysis Mode Toggle
html.Div([
html.Label("Analysis Mode:", style={'font-weight': 'bold', 'margin-bottom': '5px', 'display': 'block'}),
dcc.RadioItems(
id='analysis-mode-toggle',
options=[
{'label': '🔴 Real-time Updates', 'value': 'realtime'},
{'label': '🔒 Analysis Mode (Locked)', 'value': 'locked'}
],
value='realtime',
inline=True,
style={'margin-bottom': '10px'}
)
]),
# Time Range Status
html.Div(id='time-range-status',
style={'font-size': '12px', 'color': '#7f8c8d', 'font-style': 'italic'})
], style={
'border': '1px solid #bdc3c7',
'border-radius': '8px',
'padding': '15px',
'background-color': '#f0f8ff',
'margin-bottom': '20px'
})
], className="mb-3"),
html.Div([
html.Label("Custom Date Range:", className="form-label"),
dbc.InputGroup([
dcc.DatePickerRange(
id='custom-date-range',
display_format='YYYY-MM-DD',
),
dbc.Button("Clear", id="clear-date-range-btn", color="secondary", outline=True, size="sm")
])
], className="mb-3"),
html.Div([
html.Label("Analysis Mode:", className="form-label"),
dbc.RadioItems(
id='analysis-mode-toggle',
options=[
{'label': '🔴 Real-time Updates', 'value': 'realtime'},
{'label': '🔒 Analysis Mode (Locked)', 'value': 'locked'}
],
value='realtime',
inline=True,
)
]),
html.Div(id='time-range-status', className="text-muted fst-italic mt-2")
])
], className="mb-4")
def create_export_controls():
"""Create the data export control panel."""
return html.Div([
html.H5("💾 Data Export", style={'color': '#2c3e50', 'margin-bottom': '15px'}),
html.Button(
"Export to CSV",
id="export-csv-btn",
className="btn btn-primary",
style={
'background-color': '#28a745',
'color': 'white',
'border': 'none',
'padding': '8px 16px',
'border-radius': '4px',
'cursor': 'pointer',
'margin-right': '10px'
}
),
html.Button(
"Export to JSON",
id="export-json-btn",
className="btn btn-primary",
style={
'background-color': '#17a2b8',
'color': 'white',
'border': 'none',
'padding': '8px 16px',
'border-radius': '4px',
'cursor': 'pointer'
}
),
dcc.Download(id="download-chart-data")
], style={
'border': '1px solid #bdc3c7',
'border-radius': '8px',
'padding': '15px',
'background-color': '#f8f9fa',
'margin-bottom': '20px'
})
return dbc.Card([
dbc.CardHeader(html.H5("💾 Data Export")),
dbc.CardBody([
dbc.ButtonGroup([
dbc.Button("Export to CSV", id="export-csv-btn", color="primary"),
dbc.Button("Export to JSON", id="export-json-btn", color="secondary"),
]),
dcc.Download(id="download-chart-data")
])
], className="mb-4")

View File

@@ -3,7 +3,7 @@ Data analysis components for comprehensive market data analysis.
"""
from dash import html, dcc
import dash_mantine_components as dmc
import dash_bootstrap_components as dbc
import plotly.graph_objects as go
import plotly.express as px
from plotly.subplots import make_subplots
@@ -466,7 +466,7 @@ def create_data_analysis_panel():
]),
dcc.Tab(label="Price Movement", value="price-movement", children=[
html.Div(id='price-movement-content', children=[
dmc.Alert("Select a symbol and timeframe to view price movement analysis.", color="blue")
dbc.Alert("Select a symbol and timeframe to view price movement analysis.", color="primary")
])
]),
],
@@ -492,150 +492,70 @@ def format_number(value: float, decimals: int = 2) -> str:
def create_volume_stats_display(stats: Dict[str, Any]) -> html.Div:
"""Create volume statistics display."""
if 'error' in stats:
return dmc.Alert(
return dbc.Alert(
"Error loading volume statistics",
title="Volume Analysis Error",
color="red"
color="danger",
dismissable=True
)
return dmc.SimpleGrid([
dmc.Paper([
dmc.Group([
dmc.ThemeIcon("📊", size="lg", color="blue"),
dmc.Stack([
dmc.Text("Total Volume", size="sm", c="dimmed"),
dmc.Text(format_number(stats['total_volume']), fw=700, size="lg")
], gap="xs")
])
], p="md", shadow="sm"),
dmc.Paper([
dmc.Group([
dmc.ThemeIcon("📈", size="lg", color="green"),
dmc.Stack([
dmc.Text("Average Volume", size="sm", c="dimmed"),
dmc.Text(format_number(stats['avg_volume']), fw=700, size="lg")
], gap="xs")
])
], p="md", shadow="sm"),
dmc.Paper([
dmc.Group([
dmc.ThemeIcon("🎯", size="lg", color="orange"),
dmc.Stack([
dmc.Text("Volume Trend", size="sm", c="dimmed"),
dmc.Text(stats['volume_trend'], fw=700, size="lg",
c="green" if stats['volume_trend'] == "Increasing" else "red")
], gap="xs")
])
], p="md", shadow="sm"),
dmc.Paper([
dmc.Group([
dmc.ThemeIcon("", size="lg", color="red"),
dmc.Stack([
dmc.Text("High Volume Periods", size="sm", c="dimmed"),
dmc.Text(str(stats['high_volume_periods']), fw=700, size="lg")
], gap="xs")
])
], p="md", shadow="sm"),
dmc.Paper([
dmc.Group([
dmc.ThemeIcon("🔗", size="lg", color="purple"),
dmc.Stack([
dmc.Text("Volume-Price Correlation", size="sm", c="dimmed"),
dmc.Text(f"{stats['volume_price_correlation']:.3f}", fw=700, size="lg")
], gap="xs")
])
], p="md", shadow="sm"),
dmc.Paper([
dmc.Group([
dmc.ThemeIcon("💱", size="lg", color="teal"),
dmc.Stack([
dmc.Text("Avg Trade Size", size="sm", c="dimmed"),
dmc.Text(format_number(stats['avg_trade_size']), fw=700, size="lg")
], gap="xs")
])
], p="md", shadow="sm")
], cols=3, spacing="md", style={'margin-top': '20px'})
def create_stat_card(icon, title, value, color="primary"):
return dbc.Col(dbc.Card(dbc.CardBody([
html.Div([
html.Div(icon, className="display-6"),
html.Div([
html.P(title, className="card-title mb-1 text-muted"),
html.H4(value, className=f"card-text fw-bold text-{color}")
], className="ms-3")
], className="d-flex align-items-center")
])), width=4, className="mb-3")
return dbc.Row([
create_stat_card("📊", "Total Volume", format_number(stats['total_volume'])),
create_stat_card("📈", "Average Volume", format_number(stats['avg_volume'])),
create_stat_card("🎯", "Volume Trend", stats['volume_trend'],
"success" if stats['volume_trend'] == "Increasing" else "danger"),
create_stat_card("", "High Volume Periods", str(stats['high_volume_periods'])),
create_stat_card("🔗", "Volume-Price Correlation", f"{stats['volume_price_correlation']:.3f}"),
create_stat_card("💱", "Avg Trade Size", format_number(stats['avg_trade_size']))
], className="mt-3")
def create_price_stats_display(stats: Dict[str, Any]) -> html.Div:
"""Create price movement statistics display."""
if 'error' in stats:
return dmc.Alert(
return dbc.Alert(
"Error loading price statistics",
title="Price Analysis Error",
color="red"
color="danger",
dismissable=True
)
return dmc.SimpleGrid([
dmc.Paper([
dmc.Group([
dmc.ThemeIcon("💰", size="lg", color="blue"),
dmc.Stack([
dmc.Text("Current Price", size="sm", c="dimmed"),
dmc.Text(f"${stats['current_price']:.2f}", fw=700, size="lg")
], gap="xs")
])
], p="md", shadow="sm"),
dmc.Paper([
dmc.Group([
dmc.ThemeIcon("📈", size="lg", color="green" if stats['period_return'] >= 0 else "red"),
dmc.Stack([
dmc.Text("Period Return", size="sm", c="dimmed"),
dmc.Text(f"{stats['period_return']:+.2f}%", fw=700, size="lg",
c="green" if stats['period_return'] >= 0 else "red")
], gap="xs")
])
], p="md", shadow="sm"),
dmc.Paper([
dmc.Group([
dmc.ThemeIcon("📊", size="lg", color="orange"),
dmc.Stack([
dmc.Text("Volatility", size="sm", c="dimmed"),
dmc.Text(f"{stats['volatility']:.2f}%", fw=700, size="lg")
], gap="xs")
])
], p="md", shadow="sm"),
dmc.Paper([
dmc.Group([
dmc.ThemeIcon("🎯", size="lg", color="purple"),
dmc.Stack([
dmc.Text("Bullish Ratio", size="sm", c="dimmed"),
dmc.Text(f"{stats['bullish_ratio']:.1f}%", fw=700, size="lg")
], gap="xs")
])
], p="md", shadow="sm"),
dmc.Paper([
dmc.Group([
dmc.ThemeIcon("", size="lg", color="teal"),
dmc.Stack([
dmc.Text("Momentum", size="sm", c="dimmed"),
dmc.Text(f"{stats['momentum']:+.2f}%", fw=700, size="lg",
c="green" if stats['momentum'] >= 0 else "red")
], gap="xs")
])
], p="md", shadow="sm"),
dmc.Paper([
dmc.Group([
dmc.ThemeIcon("📉", size="lg", color="red"),
dmc.Stack([
dmc.Text("Max Loss", size="sm", c="dimmed"),
dmc.Text(f"{stats['max_loss']:.2f}%", fw=700, size="lg", c="red")
], gap="xs")
])
], p="md", shadow="sm")
], cols=3, spacing="md", style={'margin-top': '20px'})
def create_stat_card(icon, title, value, color="primary"):
text_color = "text-dark"
if color == "success":
text_color = "text-success"
elif color == "danger":
text_color = "text-danger"
return dbc.Col(dbc.Card(dbc.CardBody([
html.Div([
html.Div(icon, className="display-6"),
html.Div([
html.P(title, className="card-title mb-1 text-muted"),
html.H4(value, className=f"card-text fw-bold {text_color}")
], className="ms-3")
], className="d-flex align-items-center")
])), width=4, className="mb-3")
return dbc.Row([
create_stat_card("💰", "Current Price", f"${stats['current_price']:.2f}"),
create_stat_card("📈", "Period Return", f"{stats['period_return']:+.2f}%",
"success" if stats['period_return'] >= 0 else "danger"),
create_stat_card("📊", "Volatility", f"{stats['volatility']:.2f}%", color="warning"),
create_stat_card("🎯", "Bullish Ratio", f"{stats['bullish_ratio']:.1f}%"),
create_stat_card("", "Momentum", f"{stats['momentum']:+.2f}%",
"success" if stats['momentum'] >= 0 else "danger"),
create_stat_card("📉", "Max Loss", f"{stats['max_loss']:.2f}%", "danger")
], className="mt-3")
def get_market_statistics(df: pd.DataFrame, symbol: str, timeframe: str) -> html.Div:
@@ -660,14 +580,14 @@ def get_market_statistics(df: pd.DataFrame, symbol: str, timeframe: str) -> html
time_status = f"📅 Analysis Range: {start_date} to {end_date} (~{days_back} days)"
return html.Div([
html.H3("📊 Enhanced Market Statistics"),
html.H3("📊 Enhanced Market Statistics", className="mb-3"),
html.P(
time_status,
style={'font-weight': 'bold', 'margin-bottom': '15px', 'color': '#4A4A4A', 'text-align': 'center', 'font-size': '1.1em'}
className="lead text-center text-muted mb-4"
),
create_price_stats_display(price_stats),
create_volume_stats_display(volume_stats)
])
except Exception as e:
logger.error(f"Error in get_market_statistics: {e}", exc_info=True)
return html.Div(f"Error generating statistics display: {e}", style={'color': 'red'})
return dbc.Alert(f"Error generating statistics display: {e}", color="danger")

View File

@@ -3,281 +3,118 @@ Indicator modal component for creating and editing indicators.
"""
from dash import html, dcc
import dash_bootstrap_components as dbc
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
dcc.Store(id='edit-indicator-store', data=None),
dbc.Modal([
dbc.ModalHeader(dbc.ModalTitle("📊 Add New Indicator", id="modal-title")),
dbc.ModalBody([
# Basic Settings
html.H5("Basic Settings"),
dbc.Row([
dbc.Col(dbc.Label("Indicator Name:"), width=12),
dbc.Col(dcc.Input(id='indicator-name-input', type='text', placeholder='e.g., "SMA 30 Custom"', className="w-100"), width=12)
], className="mb-3"),
dbc.Row([
dbc.Col(dbc.Label("Indicator Type:"), width=12),
dbc.Col(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',
), width=12)
], className="mb-3"),
dbc.Row([
dbc.Col(dbc.Label("Description (Optional):"), width=12),
dbc.Col(dcc.Textarea(
id='indicator-description-input',
placeholder='Brief description of this indicator configuration...',
style={'width': '100%', 'height': '60px'}
), width=12)
], className="mb-3"),
html.Hr(),
# Parameters Section
html.H5("Parameters"),
html.Div(
id='indicator-parameters-message',
children=[html.P("Select an indicator type to configure parameters", className="text-muted fst-italic")]
),
# Parameter fields (SMA, EMA, etc.)
create_parameter_fields(),
html.Hr(),
# Styling Section
html.H5("Styling"),
dbc.Row([
dbc.Col([
dbc.Label("Color:"),
dcc.Input(id='indicator-color-input', type='text', value='#007bff', className="w-100")
], width=6),
dbc.Col([
dbc.Label("Line Width:"),
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)})
], width=6)
], className="mb-3"),
]),
dbc.ModalFooter([
html.Div(id='save-indicator-feedback', className="me-auto"),
dbc.Button("Cancel", id="cancel-indicator-btn", color="secondary"),
dbc.Button("Save Indicator", id="save-indicator-btn", color="primary")
])
], id='indicator-modal', size="lg", is_open=False),
])
def create_parameter_fields():
"""Helper function to create parameter input fields for all indicator types."""
return html.Div([
# SMA Parameters
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={'display': 'flex', 'justify-content': 'flex-end', 'margin-top': '20px', 'border-top': '1px solid #eee', 'padding-top': '15px'})
], style={
'background': 'white',
'padding': '20px',
'border-radius': '8px',
'width': '600px',
'box-shadow': '0 4px 8px rgba(0,0,0,0.1)'
})
], id='indicator-modal-content', style={
'display': 'none',
'position': 'fixed',
'z-index': '1001',
'left': '0',
'top': '0',
'width': '100%',
'height': '100%',
'visibility': 'hidden'
})
dbc.Label("Period:"),
dcc.Input(id='sma-period-input', type='number', value=20, min=1, max=200),
dbc.FormText("Number of periods for Simple Moving Average calculation")
], id='sma-parameters', style={'display': 'none'}, className="mb-3"),
# EMA Parameters
html.Div([
dbc.Label("Period:"),
dcc.Input(id='ema-period-input', type='number', value=12, min=1, max=200),
dbc.FormText("Number of periods for Exponential Moving Average calculation")
], id='ema-parameters', style={'display': 'none'}, className="mb-3"),
# RSI Parameters
html.Div([
dbc.Label("Period:"),
dcc.Input(id='rsi-period-input', type='number', value=14, min=2, max=50),
dbc.FormText("Number of periods for RSI calculation (typically 14)")
], id='rsi-parameters', style={'display': 'none'}, className="mb-3"),
# MACD Parameters
html.Div([
dbc.Row([
dbc.Col([dbc.Label("Fast Period:"), dcc.Input(id='macd-fast-period-input', type='number', value=12)], width=4),
dbc.Col([dbc.Label("Slow Period:"), dcc.Input(id='macd-slow-period-input', type='number', value=26)], width=4),
dbc.Col([dbc.Label("Signal Period:"), dcc.Input(id='macd-signal-period-input', type='number', value=9)], width=4),
]),
dbc.FormText("MACD periods: Fast EMA, Slow EMA, and Signal line")
], id='macd-parameters', style={'display': 'none'}, className="mb-3"),
# Bollinger Bands Parameters
html.Div([
dbc.Row([
dbc.Col([dbc.Label("Period:"), dcc.Input(id='bb-period-input', type='number', value=20)], width=6),
dbc.Col([dbc.Label("Standard Deviation:"), dcc.Input(id='bb-stddev-input', type='number', value=2.0, step=0.1)], width=6),
]),
dbc.FormText("Period for middle line (SMA) and standard deviation multiplier")
], id='bb-parameters', style={'display': 'none'}, className="mb-3")
])