""" Utility functions for loading and managing indicator configurations. """ import json import os import logging from typing import List, Dict, Any, Optional from dash import Output, Input, State logger = logging.getLogger(__name__) def load_indicator_templates() -> Dict[str, Dict[str, Any]]: """Load all indicator templates from the templates directory. Returns: Dict[str, Dict[str, Any]]: Dictionary mapping indicator type to template configuration """ templates = {} try: # Get the templates directory path templates_dir = os.path.join(os.path.dirname(__file__), 'templates') if not os.path.exists(templates_dir): logger.error(f"Templates directory not found at {templates_dir}") return {} # Load all JSON files from templates directory for filename in os.listdir(templates_dir): if filename.endswith('_template.json'): file_path = os.path.join(templates_dir, filename) try: with open(file_path, 'r', encoding='utf-8') as f: template = json.load(f) indicator_type = template.get('type') if indicator_type: templates[indicator_type] = template else: logger.warning(f"Template {filename} missing 'type' field") except json.JSONDecodeError as e: logger.error(f"Error decoding JSON from {filename}: {e}") except Exception as e: logger.error(f"Error loading template {filename}: {e}") except Exception as e: logger.error(f"Error loading indicator templates: {e}") return templates def get_indicator_dropdown_options() -> List[Dict[str, str]]: """Generate dropdown options for indicator types from templates. Returns: List[Dict[str, str]]: List of dropdown options with label and value """ templates = load_indicator_templates() options = [] for indicator_type, template in templates.items(): option = { 'label': template.get('name', indicator_type.upper()), 'value': indicator_type } options.append(option) # Sort by label for consistent UI options.sort(key=lambda x: x['label']) return options def get_indicator_parameter_schema(indicator_type: str) -> Optional[Dict[str, Any]]: """Get parameter schema for a specific indicator type. Args: indicator_type (str): The indicator type (e.g., 'sma', 'ema') Returns: Optional[Dict[str, Any]]: Parameter schema or None if not found """ templates = load_indicator_templates() template = templates.get(indicator_type) if template: return template.get('parameter_schema', {}) return None def get_indicator_default_parameters(indicator_type: str) -> Optional[Dict[str, Any]]: """Get default parameters for a specific indicator type. Args: indicator_type (str): The indicator type (e.g., 'sma', 'ema') Returns: Optional[Dict[str, Any]]: Default parameters or None if not found """ templates = load_indicator_templates() template = templates.get(indicator_type) if template: return template.get('default_parameters', {}) return None def get_indicator_default_styling(indicator_type: str) -> Optional[Dict[str, Any]]: """Get default styling for a specific indicator type. Args: indicator_type (str): The indicator type (e.g., 'sma', 'ema') Returns: Optional[Dict[str, Any]]: Default styling or None if not found """ templates = load_indicator_templates() template = templates.get(indicator_type) if template: return template.get('default_styling', {}) return None def generate_parameter_fields_config(indicator_type: str) -> Optional[Dict[str, Any]]: """Generate parameter field configuration for dynamic UI generation. Args: indicator_type (str): The indicator type (e.g., 'sma', 'ema') Returns: Optional[Dict[str, Any]]: Configuration for generating parameter input fields """ schema = get_indicator_parameter_schema(indicator_type) defaults = get_indicator_default_parameters(indicator_type) if not schema or not defaults: return None fields_config = {} for param_name, param_schema in schema.items(): # Skip timeframe as it's handled separately in the modal if param_name == 'timeframe': continue field_config = { 'type': param_schema.get('type', 'int'), 'label': param_name.replace('_', ' ').title(), 'default': defaults.get(param_name, param_schema.get('default')), 'description': param_schema.get('description', ''), 'input_id': f'{indicator_type}-{param_name.replace("_", "-")}-input' } # Add validation constraints if present if 'min' in param_schema: field_config['min'] = param_schema['min'] if 'max' in param_schema: field_config['max'] = param_schema['max'] if 'step' in param_schema: field_config['step'] = param_schema['step'] fields_config[param_name] = field_config return fields_config def get_parameter_field_outputs() -> List[Output]: """Generate dynamic Output components for parameter field visibility callbacks. Returns: List[Output]: List of Output components for all parameter containers """ templates = load_indicator_templates() outputs = [Output('indicator-parameters-message', 'style')] for indicator_type in templates.keys(): outputs.append(Output(f'{indicator_type}-parameters', 'style')) return outputs def get_parameter_field_states() -> List[State]: """Generate dynamic State components for parameter input fields. Returns: List[State]: List of State components for all parameter input fields """ templates = load_indicator_templates() states = [] for indicator_type, template in templates.items(): config = generate_parameter_fields_config(indicator_type) if config: for field_config in config.values(): states.append(State(field_config['input_id'], 'value')) return states def get_parameter_field_edit_outputs() -> List[Output]: """Generate dynamic Output components for parameter fields in edit mode. Returns: List[Output]: List of Output components for setting parameter values """ templates = load_indicator_templates() outputs = [] for indicator_type, template in templates.items(): config = generate_parameter_fields_config(indicator_type) if config: for field_config in config.values(): outputs.append(Output(field_config['input_id'], 'value')) return outputs def get_parameter_field_reset_outputs() -> List[Output]: """Generate dynamic Output components for resetting parameter fields. Returns: List[Output]: List of Output components for resetting parameter values """ templates = load_indicator_templates() outputs = [] for indicator_type, template in templates.items(): config = generate_parameter_fields_config(indicator_type) if config: for field_config in config.values(): outputs.append(Output(field_config['input_id'], 'value', allow_duplicate=True)) return outputs def collect_parameter_values(indicator_type: str, all_parameter_values: Dict[str, Any]) -> Dict[str, Any]: """Collect parameter values for a specific indicator type from callback arguments. Args: indicator_type (str): The indicator type all_parameter_values (Dict[str, Any]): All parameter values from callback Returns: Dict[str, Any]: Parameters specific to the indicator type """ config = generate_parameter_fields_config(indicator_type) if not config: return {} parameters = {} defaults = get_indicator_default_parameters(indicator_type) for param_name, field_config in config.items(): field_id = field_config['input_id'] value = all_parameter_values.get(field_id) default_value = defaults.get(param_name, field_config.get('default')) # Use provided value or fall back to default parameters[param_name] = value if value is not None else default_value return parameters def set_parameter_values(indicator_type: str, parameters: Dict[str, Any]) -> List[Any]: """Generate parameter values for setting in edit mode. Args: indicator_type (str): The indicator type parameters (Dict[str, Any]): Parameter values to set Returns: List[Any]: Values in the order expected by the callback outputs """ templates = load_indicator_templates() values = [] for current_type, template in templates.items(): config = generate_parameter_fields_config(current_type) if config: for param_name, field_config in config.items(): if current_type == indicator_type: # Set the actual parameter value for the matching indicator type value = parameters.get(param_name) else: # Set None for other indicator types value = None values.append(value) return values def reset_parameter_values() -> List[Any]: """Generate default parameter values for resetting the form. Returns: List[Any]: Default values in the order expected by reset callback outputs """ templates = load_indicator_templates() values = [] for indicator_type, template in templates.items(): config = generate_parameter_fields_config(indicator_type) if config: defaults = get_indicator_default_parameters(indicator_type) for param_name, field_config in config.items(): default_value = defaults.get(param_name, field_config.get('default')) values.append(default_value) return values def get_parameter_visibility_styles(selected_indicator_type: str) -> List[Dict[str, str]]: """Generate visibility styles for parameter containers. Args: selected_indicator_type (str): The currently selected indicator type Returns: List[Dict[str, str]]: Visibility styles for each parameter container """ templates = load_indicator_templates() hidden_style = {'display': 'none'} visible_style = {'display': 'block'} # First style is for the message message_style = {'display': 'block'} if not selected_indicator_type else {'display': 'none'} styles = [message_style] # Then styles for each indicator type container for indicator_type in templates.keys(): style = visible_style if indicator_type == selected_indicator_type else hidden_style styles.append(style) return styles