""" Symbol Configuration Manager ============================ Manages per-symbol configuration overrides for Freqtrade strategies. MIGRATION NOTE: This module maps to per_symbol section in config/config.yaml Usage in strategy: from helpers.symbol_config import SymbolConfigManager class MyStrategy(IStrategy): def __init__(self, config): super().__init__(config) self.symbol_mgr = SymbolConfigManager.from_freqtrade_config(config) def populate_entry_trend(self, dataframe, metadata): symbol_cfg = self.symbol_mgr.get_config(metadata['pair']) # Use symbol_cfg['rsi_long_max'], etc. """ from typing import Dict, Any, Optional, List import logging logger = logging.getLogger(__name__) class SymbolConfigManager: """ Manager for per-symbol configuration overrides. MIGRATION FROM config/config.yaml > per_symbol section: Each symbol can have custom settings for: - Entry method selection (use_trend_entry, use_breakout_entry) - Indicator parameters (breakout_n, etc.) - Filter thresholds (min_ma_distance_pct, rsi_long_max) - Debug mode (debug_always_entry) """ # Default configuration applied to all symbols DEFAULT_CONFIG: Dict[str, Any] = { # Entry methods "use_trend_entry": True, "use_breakout_entry": True, "entry_method": "TREND", # Legacy single method # Indicator parameters "breakout_n": 20, "breakout_buffer_pct": 0.0, # Filter thresholds (None = use global setting) "min_ma_distance_pct": None, "rsi_long_max": None, # Debug "debug_always_entry": False, } def __init__(self, per_symbol_config: Optional[Dict[str, Dict[str, Any]]] = None): """ Initialize with per-symbol configurations. Args: per_symbol_config: Dict mapping pair names to config overrides Example: {"SOL/USDT": {"use_trend_entry": False}} """ self._per_symbol = per_symbol_config or {} # Normalize pair names (convert SOLUSDT to SOL/USDT format if needed) self._normalized = {} for pair, cfg in self._per_symbol.items(): normalized = self._normalize_pair_name(pair) self._normalized[normalized] = cfg logger.info(f"[SYMBOL_CONFIG] Loaded {len(self._normalized)} per-symbol configs") @classmethod def from_freqtrade_config(cls, config: Dict[str, Any]) -> 'SymbolConfigManager': """ Create manager from Freqtrade config. Looks for 'per_symbol' key in config, falling back to empty dict. Args: config: Freqtrade configuration dict Returns: SymbolConfigManager instance """ per_symbol = config.get('per_symbol', {}) return cls(per_symbol) @classmethod def from_yaml_config(cls, yaml_config: Dict[str, Any]) -> 'SymbolConfigManager': """ Create manager from original YAML config format. MIGRATION: Reads from config/config.yaml format Args: yaml_config: Loaded YAML configuration dict Returns: SymbolConfigManager instance """ per_symbol = yaml_config.get('per_symbol', {}) # Convert from YAML format (nested indicators) to flat format converted = {} for symbol, symbol_cfg in per_symbol.items(): flat_cfg = {} # Extract indicator settings indicators = symbol_cfg.get('indicators', {}) flat_cfg.update(indicators) # Extract trade settings trade = symbol_cfg.get('trade', {}) flat_cfg.update(trade) converted[symbol] = flat_cfg return cls(converted) def _normalize_pair_name(self, pair: str) -> str: """ Normalize pair name to Freqtrade format (BASE/QUOTE). Converts: SOLUSDT -> SOL/USDT Keeps: SOL/USDT -> SOL/USDT Args: pair: Pair name in any format Returns: Normalized pair name """ # Already in correct format if '/' in pair: return pair.upper() # Common quote currencies quotes = ['USDT', 'USDC', 'USD', 'BTC', 'ETH', 'BNB', 'EUR'] for quote in quotes: if pair.upper().endswith(quote): base = pair.upper()[:-len(quote)] return f"{base}/{quote}" # Couldn't normalize, return as-is logger.warning(f"[SYMBOL_CONFIG] Could not normalize pair name: {pair}") return pair.upper() def get_config(self, pair: str) -> Dict[str, Any]: """ Get configuration for a specific pair. Merges default config with per-symbol overrides. Args: pair: Trading pair (e.g., "SOL/USDT") Returns: Complete configuration dict for the pair """ normalized = self._normalize_pair_name(pair) # Start with defaults config = dict(self.DEFAULT_CONFIG) # Apply per-symbol overrides if normalized in self._normalized: symbol_overrides = self._normalized[normalized] for key, value in symbol_overrides.items(): if value is not None: # Only override if explicitly set config[key] = value return config def get_entry_methods(self, pair: str) -> List[str]: """ Get enabled entry methods for a pair. MIGRATION: Maps to entry_methods/entry_method in config.yaml Args: pair: Trading pair Returns: List of enabled entry methods (e.g., ["TREND", "BREAKOUT_N"]) """ cfg = self.get_config(pair) methods = [] if cfg.get('use_trend_entry', True): methods.append('TREND') if cfg.get('use_breakout_entry', True): methods.append('BREAKOUT_N') # Fallback to legacy single method if not methods: legacy = cfg.get('entry_method', 'TREND') methods.append(legacy.upper()) return methods def is_debug_mode(self, pair: str) -> bool: """ Check if debug mode is enabled for a pair. MIGRATION: Maps to debug_always_entry in config.yaml Args: pair: Trading pair Returns: True if debug mode is enabled """ cfg = self.get_config(pair) return cfg.get('debug_always_entry', False) def get_filter_thresholds(self, pair: str) -> Dict[str, Optional[float]]: """ Get filter thresholds for a pair. MIGRATION: Maps to min_ma_distance_pct, rsi_long_max in config.yaml Args: pair: Trading pair Returns: Dict with filter thresholds (None means use global default) """ cfg = self.get_config(pair) return { 'min_ma_distance_pct': cfg.get('min_ma_distance_pct'), 'rsi_long_max': cfg.get('rsi_long_max'), } def list_configured_pairs(self) -> List[str]: """ List all pairs with custom configurations. Returns: List of pair names with overrides """ return list(self._normalized.keys()) def __repr__(self) -> str: return f"SymbolConfigManager({len(self._normalized)} pairs configured)" # ========================================================================== # PRE-CONFIGURED SYMBOL CONFIGS (from your config.yaml) # ========================================================================== # These match your per_symbol section in config/config.yaml # You can import and use these directly or modify as needed PRECONFIGURED_SYMBOLS: Dict[str, Dict[str, Any]] = { # SOL/USDT: Use only BREAKOUT_N method "SOL/USDT": { "use_trend_entry": False, "use_breakout_entry": True, "entry_method": "BREAKOUT_N", "breakout_n": 20, "breakout_buffer_pct": 0.0, "debug_always_entry": False, }, # DOGE/USDT: Debug mode enabled (for testing) "DOGE/USDT": { "use_trend_entry": True, "use_breakout_entry": True, "debug_always_entry": True, # Set to False for production }, # PEPE/USDT: Stricter filters for meme coin "PEPE/USDT": { "use_trend_entry": True, "use_breakout_entry": True, "debug_always_entry": True, # Set to False for production "min_ma_distance_pct": 0.01, # 1% minimum MA distance "rsi_long_max": 50, # Block entry if RSI > 50 }, # SHIB/USDT: Even stricter filters for meme coin "SHIB/USDT": { "use_trend_entry": True, "use_breakout_entry": True, "debug_always_entry": True, # Set to False for production "min_ma_distance_pct": 0.01, # 1% minimum MA distance "rsi_long_max": 45, # Block entry if RSI > 45 }, } def get_default_symbol_manager() -> SymbolConfigManager: """ Get a SymbolConfigManager with pre-configured symbols. Returns: SymbolConfigManager with your original per_symbol settings """ return SymbolConfigManager(PRECONFIGURED_SYMBOLS)