#!/usr/bin/env python3 """ kis_trader/backtest/param_search_cli_common.py — 포트폴리오 파라서치 공통 CLI ============================================================================= 꼬리·스캘핑·모멘텀·돌파는 동일한 run 옵션 패턴을 쓴다 (하락매수 UPDOW 제외). 예시 (전략 파일만 바꿈): python3 kis_trader/backtest/param_search_momentum.py \\ --start 2026-05-01 --end 2026-05-31 \\ --slot-money 200000 --max-stocks 20 --total-budget 2000000 \\ --time-start 830 --time-end 1530 \\ --mode fast """ from __future__ import annotations import argparse from typing import Any, Dict, Optional # 파라서치 공통 필터 기본값 (정렬은 각 스크립트 total_pnl 유지) MIN_WIN_RATE_DEFAULT = 40.0 MIN_PF_DEFAULT = 1.0 def combo_passes_search_filters( *, win_rate: float, pf: float, min_win_rate: float, min_pf: float, ) -> bool: """승률·PF 하한 — 워커·머지 단계 공통.""" if float(win_rate) < float(min_win_rate): return False if float(pf) < float(min_pf): return False return True def add_search_filter_cli_args(parser: argparse.ArgumentParser) -> None: """승률·PF 하한 CLI (--min_win_rate, --min_pf).""" parser.add_argument( "--min_win_rate", default=MIN_WIN_RATE_DEFAULT, type=float, help="승률 하한(%%). Grid 기본 40 / Optuna CLI 는 set_defaults(0) 로 덮음", ) parser.add_argument( "--min_pf", default=MIN_PF_DEFAULT, type=float, help="Profit Factor 하한. Grid 기본 1.0 / Optuna CLI 는 set_defaults(0) 로 덮음", ) def add_portfolio_cli_args(parser: argparse.ArgumentParser) -> None: """1회투자·동시보유·총한도·매매시간 — tail/scalp/momentum/breakout 공통.""" parser.add_argument( "--time-start", type=int, default=None, dest="time_start", help="매수 시작 HHMM (미지정 시 DB·전략 기본값)", ) parser.add_argument( "--time-end", type=int, default=None, dest="time_end", help="매수 종료 HHMM (미지정 시 DB·전략 기본값)", ) parser.add_argument( "--slot-money", type=float, default=None, dest="slot_money", help="1회투자금(원). 미지정 시 DB 전략별 SLOT_*", ) parser.add_argument( "--max-stocks", type=int, default=None, dest="max_stocks", help="동시보유 종목 수. 미지정 시 DB 전략 MAX_*_STOCKS", ) parser.add_argument( "--total-budget", type=float, default=None, dest="total_budget", help="총 운용한도(원). 0/미지정 시 동시×1회투자", ) def apply_session_to_fixed( fixed: Dict[str, Any], *, time_start_hm: Optional[int] = None, time_end_hm: Optional[int] = None, ) -> None: """CLI 매매시간 → 백테 base fixed dict (in-place).""" if time_start_hm is not None: fixed["time_start_hm"] = int(time_start_hm) if time_end_hm is not None: fixed["time_end_hm"] = int(time_end_hm) def search_json_meta( portfolio: Dict[str, Any], fixed: Dict[str, Any], ) -> Dict[str, Any]: """JSON 저장·--apply meta — 포트폴리오 + 매매시간.""" return { "slot_money": portfolio.get("slot_money"), "max_stocks": portfolio.get("max_stocks"), "total_budget_krw": portfolio.get("total_budget_krw"), "time_start_hm": fixed.get("time_start_hm"), "time_end_hm": fixed.get("time_end_hm"), "portfolio": dict(portfolio), } def format_session_hm(fixed: Dict[str, Any]) -> str: ts = int(fixed.get("time_start_hm") or 930) te = int(fixed.get("time_end_hm") or 1530) return f"{ts:04d}-{te:04d}"