import os import json import yaml from typing import Dict, Any, Optional class EnsembleConfig: _instance: Optional['EnsembleConfig'] = None _config: Dict[str, Any] = {} def __new__(cls): if cls._instance is None: cls._instance = super(EnsembleConfig, cls).__new__(cls) cls._instance._load_config() return cls._instance def _load_config(self): """Load configuration from YAML file.""" config_path = os.path.join(os.path.dirname(__file__), 'ensemble_config.yaml') try: with open(config_path, 'r', encoding='utf-8') as f: self._config = yaml.safe_load(f) # print(f"✅ Loaded ensemble config from {config_path}") except Exception as e: print(f"❌ Failed to load ensemble config: {e}") self._config = {} def get(self, key: str, default: Any = None) -> Any: """Get configuration value by key (supports dot notation for nested keys).""" keys = key.split('.') value = self._config try: for k in keys: value = value[k] return value except (KeyError, TypeError): return default # Singleton accessor def get_config() -> EnsembleConfig: return EnsembleConfig() # ── Market Thresholds Loader ──────────────────────────────────────────── _market_thresholds_cache: Optional[Dict[str, Any]] = None def load_market_thresholds() -> Dict[str, Any]: """ Load market thresholds from JSON config file. Returns the full config dict with 'markets' and 'defaults' keys. Caches after first load for performance. """ global _market_thresholds_cache if _market_thresholds_cache is not None: return _market_thresholds_cache config_path = os.path.join(os.path.dirname(__file__), 'market_thresholds.json') try: with open(config_path, 'r', encoding='utf-8') as f: data = json.load(f) _market_thresholds_cache = data print(f"✅ Market thresholds loaded: {len(data.get('markets', {}))} markets (v={data.get('_meta', {}).get('version', '?')})") return data except Exception as e: print(f"❌ Failed to load market thresholds: {e} — using built-in defaults") _market_thresholds_cache = {"markets": {}, "defaults": { "calibration": 0.55, "min_conf": 55.0, "min_play_score": 68.0, "min_edge": 0.02, "odds_band_min_sample": 0.0, "odds_band_min_edge": 0.0, }} return _market_thresholds_cache def build_threshold_dict(field: str) -> Dict[str, float]: """ Build a flat {market: value} dict for a specific threshold field. Usage: calibration_map = build_threshold_dict("calibration") # → {"MS": 0.62, "DC": 0.82, ...} """ data = load_market_thresholds() markets = data.get("markets", {}) result: Dict[str, float] = {} for market, cfg in markets.items(): if field in cfg: result[market] = float(cfg[field]) return result def get_threshold_default(field: str) -> float: """Get the default fallback value for a threshold field.""" data = load_market_thresholds() defaults = data.get("defaults", {}) return float(defaults.get(field, 0.0)) if __name__ == "__main__": # Test cfg = get_config() print(f"Weights: {cfg.get('engine_weights')}") print(f"Team Weight: {cfg.get('engine_weights.team')}") print() print("--- Market Thresholds ---") for field in ["calibration", "min_conf", "min_play_score", "min_edge"]: d = build_threshold_dict(field) print(f"{field}: {d}") print(f"Default calibration: {get_threshold_default('calibration')}")