2025-06-04 13:30:16 +08:00
|
|
|
"""
|
|
|
|
|
Indicator modal component for creating and editing indicators.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from dash import html, dcc
|
2025-06-06 13:33:59 +08:00
|
|
|
import dash_bootstrap_components as dbc
|
2025-06-11 18:36:34 +08:00
|
|
|
from utils.timeframe_utils import load_timeframe_options
|
2025-06-11 18:52:02 +08:00
|
|
|
from config.indicators.config_utils import get_indicator_dropdown_options, generate_parameter_fields_config
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_dynamic_parameter_fields(indicator_type: str) -> html.Div:
|
|
|
|
|
"""Create parameter input fields dynamically based on indicator configuration.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
indicator_type (str): The indicator type (e.g., 'sma', 'ema')
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
html.Div: Div containing the parameter input fields
|
|
|
|
|
"""
|
|
|
|
|
fields_config = generate_parameter_fields_config(indicator_type)
|
|
|
|
|
|
|
|
|
|
if not fields_config:
|
|
|
|
|
return html.Div(
|
|
|
|
|
html.P("No parameters available for this indicator", className="text-muted fst-italic"),
|
|
|
|
|
id=f'{indicator_type}-parameters',
|
|
|
|
|
style={'display': 'none'},
|
|
|
|
|
className="mb-3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
field_elements = []
|
|
|
|
|
|
|
|
|
|
# Handle single parameter (like SMA, EMA, RSI)
|
|
|
|
|
if len(fields_config) == 1:
|
|
|
|
|
param_name, field_config = next(iter(fields_config.items()))
|
|
|
|
|
field_elements.append(
|
|
|
|
|
html.Div([
|
|
|
|
|
dbc.Label(f"{field_config['label']}:"),
|
|
|
|
|
dcc.Input(
|
|
|
|
|
id=field_config['input_id'],
|
|
|
|
|
type='number',
|
|
|
|
|
value=field_config['default'],
|
|
|
|
|
min=field_config.get('min'),
|
|
|
|
|
max=field_config.get('max'),
|
|
|
|
|
step=field_config.get('step', 1 if field_config['type'] == 'int' else 0.1)
|
|
|
|
|
),
|
|
|
|
|
dbc.FormText(field_config['description']) if field_config['description'] else None
|
|
|
|
|
])
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
# Handle multiple parameters (like MACD, Bollinger Bands)
|
|
|
|
|
rows = []
|
|
|
|
|
params_per_row = min(len(fields_config), 4) # Max 4 parameters per row
|
|
|
|
|
|
|
|
|
|
param_items = list(fields_config.items())
|
|
|
|
|
for i in range(0, len(param_items), params_per_row):
|
|
|
|
|
row_params = param_items[i:i + params_per_row]
|
|
|
|
|
cols = []
|
|
|
|
|
|
|
|
|
|
for param_name, field_config in row_params:
|
|
|
|
|
col = dbc.Col([
|
|
|
|
|
dbc.Label(f"{field_config['label']}:"),
|
|
|
|
|
dcc.Input(
|
|
|
|
|
id=field_config['input_id'],
|
|
|
|
|
type='number',
|
|
|
|
|
value=field_config['default'],
|
|
|
|
|
min=field_config.get('min'),
|
|
|
|
|
max=field_config.get('max'),
|
|
|
|
|
step=field_config.get('step', 1 if field_config['type'] == 'int' else 0.1)
|
|
|
|
|
)
|
|
|
|
|
], width=12 // len(row_params))
|
|
|
|
|
cols.append(col)
|
|
|
|
|
|
|
|
|
|
rows.append(dbc.Row(cols))
|
|
|
|
|
|
|
|
|
|
field_elements.extend(rows)
|
|
|
|
|
|
|
|
|
|
# Add description for multi-parameter indicators
|
|
|
|
|
if any(config['description'] for config in fields_config.values()):
|
|
|
|
|
descriptions = [f"{config['label']}: {config['description']}"
|
|
|
|
|
for config in fields_config.values() if config['description']]
|
|
|
|
|
field_elements.append(
|
|
|
|
|
dbc.FormText("; ".join(descriptions))
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return html.Div(
|
|
|
|
|
field_elements,
|
|
|
|
|
id=f'{indicator_type}-parameters',
|
|
|
|
|
style={'display': 'none'},
|
|
|
|
|
className="mb-3"
|
|
|
|
|
)
|
2025-06-04 13:30:16 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_indicator_modal():
|
|
|
|
|
"""Create the indicator modal dialog for adding/editing indicators."""
|
|
|
|
|
return html.Div([
|
2025-06-06 13:33:59 +08:00
|
|
|
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',
|
2025-06-11 18:52:02 +08:00
|
|
|
options=get_indicator_dropdown_options(),
|
2025-06-06 13:33:59 +08:00
|
|
|
placeholder='Select indicator type',
|
|
|
|
|
), width=12)
|
|
|
|
|
], className="mb-3"),
|
2025-06-06 15:06:17 +08:00
|
|
|
dbc.Row([
|
|
|
|
|
dbc.Col(dbc.Label("Timeframe (Optional):"), width=12),
|
|
|
|
|
dbc.Col(dcc.Dropdown(
|
|
|
|
|
id='indicator-timeframe-dropdown',
|
2025-06-11 18:36:34 +08:00
|
|
|
options=[{'label': 'Chart Timeframe', 'value': ''}] + load_timeframe_options(),
|
2025-06-06 15:06:17 +08:00
|
|
|
value='',
|
|
|
|
|
placeholder='Defaults to chart timeframe'
|
|
|
|
|
), width=12),
|
|
|
|
|
], className="mb-3"),
|
2025-06-06 13:33:59 +08:00
|
|
|
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.)
|
2025-06-11 18:52:02 +08:00
|
|
|
create_dynamic_parameter_fields('sma'),
|
|
|
|
|
create_dynamic_parameter_fields('ema'),
|
|
|
|
|
create_dynamic_parameter_fields('rsi'),
|
|
|
|
|
create_dynamic_parameter_fields('macd'),
|
|
|
|
|
create_dynamic_parameter_fields('bollinger_bands'),
|
2025-06-06 13:33:59 +08:00
|
|
|
|
|
|
|
|
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),
|
|
|
|
|
])
|
|
|
|
|
|
2025-06-11 18:52:02 +08:00
|
|
|
|