#!/usr/bin/env python3 """ optuna_scalping_tpe_space.py — 스캘핑 Optuna 연속(TPE) 탐색 공간 기존 ``_scalp_grids()`` categorical 유지. ``use_macd_cross=False``, ``skip_hts=False``. """ from __future__ import annotations from typing import Any, Dict, List import optuna from kis_trader.backtest.optuna_tpe_common import ( ORDERBOOK_TPE_AXIS_KEYS, WHIPSAW_TPE_AXIS_KEYS, r1, r2, r3, suggest_orderbook_entry_tpe, suggest_whipsaw_tpe, ) # 매수 시간창(time_*) 은 TPE 제외 — 운영 DB 고정값 사용(과적합·apply 후 진입 잠금 방지). # 호가·휩쏘: 본 trial 엔진 평가 (사후「필터후」근사 대체). _SCALP_BASE_KEYS: List[str] = [ "rsi_period", "rsi_oversold", "rsi_overbought", "sl_pct", "tp_pct", "tp_max_pct", "drop_rate", "shoulder_min_high", "shoulder_cut_pct", "cooldown_min", "high_chase_thr", "max_daily_chg", "min_price", "vol_mult", "use_defense_filters", "require_reversal_candle", "max_loss_krw", "min_drop_pct_for_loss_cut", "min_margin", "min_hold_sec", "max_daily", ] SCALP_TPE_AXIS_KEYS: List[str] = ( list(_SCALP_BASE_KEYS) + list(ORDERBOOK_TPE_AXIS_KEYS) + list(WHIPSAW_TPE_AXIS_KEYS) ) def scalp_tpe_axis_keys() -> List[str]: return list(SCALP_TPE_AXIS_KEYS) def suggest_scalp_params_tpe(trial: optuna.Trial) -> Dict[str, Any]: combo: Dict[str, Any] = {} combo["rsi_period"] = trial.suggest_int("rsi_period", 3, 14) combo["rsi_oversold"] = r1(trial.suggest_float("rsi_oversold", 15.0, 30.0, step=0.5)) combo["rsi_overbought"] = r1(trial.suggest_float("rsi_overbought", 65.0, 85.0, step=0.5)) if combo["rsi_oversold"] >= combo["rsi_overbought"]: raise optuna.TrialPruned("rsi oversold >= overbought") combo["sl_pct"] = r2(trial.suggest_float("sl_pct", 1.0, 6.0, step=0.1)) # 확장: 1.5→1.0, 4.5→6.0 combo["tp_pct"] = r2(trial.suggest_float("tp_pct", 0.5, 8.0, step=0.1)) # 확장: 1.5→0.5, 4.0→8.0 combo["tp_max_pct"] = r2(trial.suggest_float("tp_max_pct", 1.0, 12.0, step=0.5)) # 확장: 2.0→1.0, 8.0→12.0 if combo["tp_max_pct"] + 1e-9 < combo["tp_pct"]: raise optuna.TrialPruned("tp_max < tp") combo["drop_rate"] = r2(trial.suggest_float("drop_rate", 0.5, 10.0, step=0.1)) # 확장: 1.0→0.5, 6.0→10.0 combo["shoulder_min_high"] = r2( trial.suggest_float("shoulder_min_high", 0.3, 4.0, step=0.1), ) combo["shoulder_cut_pct"] = r2( trial.suggest_float("shoulder_cut_pct", 0.05, 0.6, step=0.05), ) combo["cooldown_min"] = trial.suggest_int("cooldown_min", 0, 15) combo["high_chase_thr"] = r3( trial.suggest_float("high_chase_thr", 0.95, 1.0, step=0.005), ) combo["max_daily_chg"] = r1(trial.suggest_float("max_daily_chg", 5.0, 60.0, step=1.0)) # 확장: 10→5, 50→60 combo["min_price"] = trial.suggest_int("min_price", 1000, 8000, step=500) combo["vol_mult"] = r2(trial.suggest_float("vol_mult", 0.0, 2.0, step=0.1)) combo["use_defense_filters"] = trial.suggest_categorical( "use_defense_filters", [False, True], ) combo["require_reversal_candle"] = trial.suggest_categorical( "require_reversal_candle", [False, True], ) combo["max_loss_krw"] = trial.suggest_int("max_loss_krw", 50000, 300000, step=25000) combo["min_drop_pct_for_loss_cut"] = r3( trial.suggest_float("min_drop_pct_for_loss_cut", 0.005, 0.03, step=0.001), ) combo["min_margin"] = r2(trial.suggest_float("min_margin", 0.05, 0.5, step=0.05)) combo["min_hold_sec"] = trial.suggest_int("min_hold_sec", 0, 300, step=10) # 확장: 120→300초 combo["max_daily"] = trial.suggest_int("max_daily", 5, 150, step=5) # 확장: 10→5, 100→150 combo["use_macd_cross"] = False combo["skip_hts_scan_dupes"] = False combo.update(suggest_orderbook_entry_tpe(trial)) combo.update(suggest_whipsaw_tpe(trial)) return combo