""" config_schema.py — env 설정 키 → 테이블 분류 (전략별 config_* + 공통 env_config) 테이블 명명: config_{전략} (config_scalp, config_short, …) 공통(API·MM·인프라): env_config 기존 ENV_CONFIG_KEYS 전체는 get_merged_env_snapshot() 으로 flat dict 유지 (하위 호환). """ from __future__ import annotations from typing import Dict, Iterable, List, Tuple # (테이블명, 전략 ID — 로그/문서용) CONFIG_TABLE_NAMES: Tuple[str, ...] = ( "env_config", "config_scalp", "config_short", "config_momentum", "config_us_momentum", "config_breakout", "config_range_break", "config_updow", "config_dbband", "config_dart", ) STRATEGY_ID_TO_TABLE: Dict[str, str] = { "SCALP": "config_scalp", "SHORT": "config_short", "MOMENTUM": "config_momentum", "US_MOMENTUM": "config_us_momentum", "BREAKOUT": "config_breakout", "RANGE_BREAK": "config_range_break", "UPDOW": "config_updow", "DBBAND": "config_dbband", "DART": "config_dart", } # 전략 ON/OFF · MM alias — 접두어 규칙 예외 없이 전략 테이블에만 저장 _EXPLICIT_KEY_TABLE: Dict[str, str] = { "STRATEGY_SCALP_ENABLED": "config_scalp", "STRATEGY_SHORT_ENABLED": "config_short", "STRATEGY_MOMENTUM_ENABLED": "config_momentum", "STRATEGY_US_MOMENTUM_ENABLED": "config_us_momentum", "STRATEGY_BREAKOUT_ENABLED": "config_breakout", "STRATEGY_RANGE_BREAK_ENABLED": "config_range_break", "STRATEGY_UPDOW_ENABLED": "config_updow", "STRATEGY_DBBAND_ENABLED": "config_dbband", "STRATEGY_DART_ENABLED": "config_dart", # 해외 WS 소켓 기동 — 인프라(공통 env_config). 전략 테이블과 분리. "OVERSEAS_WS_ENABLED": "env_config", "KIS_SCALP_MM_CHANNEL": "config_scalp", "KIS_SHORT_MM_CHANNEL": "config_short", "KIS_MOMENTUM_MM_CHANNEL": "config_momentum", "KIS_BREAKOUT_MM_CHANNEL": "config_breakout", "KIS_RANGE_BREAK_MM_CHANNEL": "config_range_break", "KIS_UPDOW_MM_CHANNEL": "config_updow", "KIS_DBBAND_MM_CHANNEL": "config_dbband", "KIS_DART_MM_CHANNEL": "config_dart", } # 꼬리 오케스트레이터(SHORT strategy_id) — TAIL_* 와 분리 _SHORT_ORCHESTRATOR_KEYS = frozenset({ "SHORT_GAP_FILL_LIMIT", "SHORT_LIVE_BACKTEST_ALIGN", "SHORT_LIVE_SIGNAL_LOOKBACK_BARS", "SHORT_UNIVERSE_SOURCE", "RANK_SHORT_SORT", "RANK_SHORT_LIMIT", "CONDITION_SHORT_NAME", "CONDITION_SHORT_SEQ", "SHORT_CAND_LIMIT", "KIS_SHORT_MM_CHANNEL", }) # 익절 호가 — 실행 공통이지만 SHORT 튜닝과 함께 둠 _SHORT_EXEC_KEYS = frozenset({ "SELL_USE_ORDERBOOK_ON_PROFIT", "SELL_ORDERBOOK_BID_LEVELS", "SELL_ORDERBOOK_DEPTH_MULT", }) def classify_config_key(key: str) -> str: """설정 키가 들어갈 테이블명 반환.""" k = (key or "").strip() if not k: return "env_config" explicit = _EXPLICIT_KEY_TABLE.get(k) if explicit: return explicit if k in _SHORT_ORCHESTRATOR_KEYS or k in _SHORT_EXEC_KEYS: return "config_short" if k.startswith("UPDOW_") or k == "MIN_CANDLE_LEN_UPDOW": return "config_updow" if k.startswith("DBBAND_"): return "config_dbband" # DART_API_KEY 만 글로벌 시크릿(env_config), 나머지 DART_* → config_dart if k == "DART_API_KEY": return "env_config" if k.startswith("DART_"): return "config_dart" if k.startswith("BREAKOUT_"): return "config_breakout" if k.startswith("RANGE_BREAK_"): return "config_range_break" # US_MOMENTUM_* 는 MOMENTUM_* 보다 먼저 (접두 접힘 방지) if k.startswith("US_MOMENTUM_") or k == "STRATEGY_US_MOMENTUM_ENABLED": return "config_us_momentum" if k.startswith("MOMENTUM_"): return "config_momentum" if k.startswith("SCALP_"): return "config_scalp" if k.startswith("SHORT_") or k.startswith("TAIL_") or k.startswith("SHOULDER"): return "config_short" if k.startswith("STOP_ATR") or k.startswith("TARGET_ATR"): return "config_short" if k.startswith("RANK_UPDOW_") or k.startswith("CONDITION_UPDOW"): return "config_updow" if k.startswith("RANK_DBBAND_") or k.startswith("CONDITION_DBBAND"): return "config_dbband" if k.startswith("RANK_BREAKOUT_") or k.startswith("CONDITION_BREAKOUT"): return "config_breakout" if k.startswith("RANK_RANGE_BREAK_") or k.startswith("CONDITION_RANGE_BREAK"): return "config_range_break" if k.startswith("RANK_MOMENTUM_") or k.startswith("CONDITION_MOMENTUM"): return "config_momentum" if k.startswith("RANK_SCALP_") or k.startswith("CONDITION_SCALP"): return "config_scalp" if k.startswith("RANK_SHORT_") or k.startswith("CONDITION_SHORT"): return "config_short" return "env_config" def split_env_keys(all_keys: Iterable[str]) -> Dict[str, Tuple[str, ...]]: """키 목록을 테이블별 튜플로 분류.""" buckets: Dict[str, List[str]] = {t: [] for t in CONFIG_TABLE_NAMES} for key in all_keys: tbl = classify_config_key(key) if tbl not in buckets: tbl = "env_config" buckets[tbl].append(key) return {t: tuple(buckets[t]) for t in CONFIG_TABLE_NAMES} def build_config_schema(all_keys: Tuple[str, ...]) -> Dict[str, Tuple[str, ...]]: """ENV_CONFIG_KEYS 로부터 테이블별 키 튜플 생성.""" return split_env_keys(all_keys)