import os import json from dataclasses import dataclass, field from typing import Dict, List, Any @dataclass class ParameterDef: name: str param_type: str default: Any = None min_value: Any = None max_value: Any = None step: Any = None precision: int = 0 optimize: bool = True enum_options: List[Any] = field(default_factory=list) description: str = '' condition: str = '' def expand_values(self) -> List[Any]: if self.param_type == 'bool': return [True, False] elif self.param_type == 'enum': return self.enum_options elif self.param_type in ('int', 'double'): if self.min_value is None or self.max_value is None: return [self.default] if self.default is not None else [] values = [] current = self.min_value while current <= self.max_value: values.append(round(current, self.precision) if self.precision > 0 else int(current)) current += self.step return values return [self.default] def is_valid_value(self, value: Any) -> bool: if self.param_type == 'bool': return isinstance(value, bool) elif self.param_type == 'enum': return value in self.enum_options elif self.param_type in ('int', 'double'): if self.min_value is not None and value < self.min_value: return False if self.max_value is not None and value > self.max_value: return False return True return True @dataclass class EAConfig: ea_name: str ea_path: str parameters: Dict[str, ParameterDef] = field(default_factory=dict) search_strategy: str = 'auto' optimization_criterion: str = 'profit_factor' test_config: Dict[str, Any] = field(default_factory=dict) walk_forward: Dict[str, Any] = field(default_factory=dict) description: str = '' def get_total_combinations(self) -> int: total = 1 for param in self.parameters.values(): values = param.expand_values() total *= len(values) if values else 1 return total def estimate_search_time(self, tests_per_minute: float = 10) -> str: total = self.get_total_combinations() minutes = total / tests_per_minute if minutes < 60: return str(round(minutes, 1)) + ' minutes' elif minutes < 1440: return str(round(minutes/60, 1)) + ' hours' else: return str(round(minutes/1440, 1)) + ' days' def is_complex(self) -> bool: return len(self.parameters) > 6 or self.get_total_combinations() > 1_000_000 def load_ea_config(config_path: str) -> EAConfig: with open(config_path, 'r', encoding='utf-8') as f: config_data = json.load(f) ea_name = config_data.get('ea_name', 'Unknown') ea_path = config_data.get('ea_path', '') description = config_data.get('description', '') search_strategy = config_data.get('search_strategy', 'auto') criterion = config_data.get('optimization_criterion', 'profit_factor') test_config = config_data.get('test_config', {}) walk_forward = config_data.get('walk_forward', {}) parameters = {} for param_name, param_data in config_data.get('parameters', {}).items(): param_type = param_data.get('type', 'int') if param_type == 'enum': param = ParameterDef( name=param_name, param_type='enum', default=param_data.get('default'), enum_options=param_data.get('options', []), description=param_data.get('description', ''), condition=param_data.get('condition', '') ) else: param = ParameterDef( name=param_name, param_type=param_type, default=param_data.get('default'), min_value=param_data.get('min'), max_value=param_data.get('max'), step=param_data.get('step', 1), precision=param_data.get('precision', 0), optimize=param_data.get('optimize', True), description=param_data.get('description', ''), condition=param_data.get('condition', '') ) parameters[param_name] = param return EAConfig( ea_name=ea_name, ea_path=ea_path, parameters=parameters, search_strategy=search_strategy, optimization_criterion=criterion, test_config=test_config, walk_forward=walk_forward, description=description ) def load_all_ea_configs(configs_dir: str) -> Dict[str, EAConfig]: configs = {} if not os.path.exists(configs_dir): return configs for filename in os.listdir(configs_dir): if filename.endswith('.json'): config_path = os.path.join(configs_dir, filename) try: config = load_ea_config(config_path) configs[config.ea_name] = config except Exception as e: print('Error loading ' + config_path + ': ' + str(e)) return configs