Removed Mantine for UI, and used bootstrap for simplicity
This commit is contained in:
@@ -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")
|
||||
@@ -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")
|
||||
@@ -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")
|
||||
])
|
||||
Reference in New Issue
Block a user