#!/usr/bin/env python3 """ kis_trader/backtest/param_search_dart.py — DART Optuna/Grid 축 """ from __future__ import annotations from typing import Any, Dict, List from database import TradeDB from kis_trader.backtest.dart_backtest_common import run_dart_backtest_web_aligned from kis_trader.engine import dart_engine as de from kis_trader.utils.env import get_env_bool def _dart_grids() -> Dict[str, Dict[str, List[Any]]]: """mode → 축. 실매 기본값이 각 축에 포함.""" d = de.get_dart_defaults_from_db() rsi_os = float(d.get("rsi_oversold", 30)) rsi_rc = float(d.get("rsi_reclaim", 35)) sl = float(d.get("sl_pct", 0.02)) tp = float(d.get("tp_pct", 0.04)) vol = float(d.get("vol_mult", 1.5)) win = int(d.get("event_window_bars", 120)) return { "fast": { "rsi_oversold": sorted({rsi_os, 25.0, 30.0, 35.0}), "rsi_reclaim": sorted({rsi_rc, 32.0, 35.0, 40.0}), "sl_pct": sorted({sl, 0.015, 0.02, 0.025}), "tp_pct": sorted({tp, 0.03, 0.04, 0.05}), "vol_mult": sorted({vol, 1.2, 1.5, 2.0}), "event_window_bars": sorted({win, 60, 120, 180}), }, "coarse": { "rsi_oversold": [20.0, 25.0, 30.0, 35.0], "rsi_reclaim": [30.0, 35.0, 40.0, 45.0], "sl_pct": [0.015, 0.02, 0.03], "tp_pct": [0.03, 0.04, 0.06], "vol_mult": [1.0, 1.5, 2.0, 2.5], "event_window_bars": [60, 90, 120, 180], "trail_pct": [0.01, 0.015, 0.02], "trail_arm_pct": [0.015, 0.02, 0.03], }, "fine": { "rsi_oversold": sorted({rsi_os, rsi_os - 2, rsi_os + 2, 28.0, 30.0}), "rsi_reclaim": sorted({rsi_rc, rsi_rc - 2, rsi_rc + 2, 35.0, 38.0}), "sl_pct": sorted({sl, 0.018, 0.02, 0.022}), "tp_pct": sorted({tp, 0.035, 0.04, 0.045}), "vol_mult": sorted({vol, 1.3, 1.5, 1.8}), "event_window_bars": sorted({win, 90, 120, 150}), }, } def apply_params_to_db(params: Dict[str, Any]) -> None: """Optuna --apply-best 전용. 포트폴리오 키 제외.""" if not get_env_bool("DART_TRADE_ENABLED", False): # 적용은 허용하되 매매 스위치는 사용자 것 유지 pass mapping = { "rsi_oversold": "DART_RSI_OVERSOLD", "rsi_reclaim": "DART_RSI_RECLAIM", "sl_pct": "DART_STOP_LOSS_PCT", "tp_pct": "DART_TAKE_PROFIT_PCT", "vol_mult": "DART_VOL_MULT", "event_window_bars": "DART_EVENT_WINDOW_BARS", "trail_pct": "DART_TRAIL_PCT", "trail_arm_pct": "DART_TRAIL_ARM_PCT", "rsi_period": "DART_RSI_PERIOD", "max_hold_bars": "DART_MAX_HOLD_BARS", } patch = {} for k, env_k in mapping.items(): if k in params: patch[env_k] = str(params[k]) if not patch: return db = TradeDB() try: db.insert_env_snapshot(patch) finally: db.close() def evaluate_dart_param_combo( params: Dict[str, Any], *, start: str, end: str, env_row: Dict[str, Any], ) -> Dict[str, Any]: base = de.get_dart_defaults_from_db(env_row=env_row) base.update(params) return run_dart_backtest_web_aligned( start=start, end=end, params=base, env_row=env_row, )