#!/usr/bin/env python3 """2단계: Ground Truth 정합 매매 기법 실행 및 비교.""" from __future__ import annotations import argparse import logging import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] SRC = ROOT / "src" if str(SRC) not in sys.path: sys.path.insert(0, str(SRC)) from deepcoin.config import load_settings from deepcoin.data.intervals import interval_label from deepcoin.evaluation.report import ( build_comparison_report, render_comparison_html, save_comparison_report, ) from deepcoin.techniques.base import TechniqueParams from deepcoin.techniques.registry import list_technique_ids from deepcoin.techniques.runner import ( load_ground_truth, run_all_techniques, save_technique_result, ) def _configure_logging(verbose: bool) -> None: """로깅 레벨을 설정한다.""" level = logging.DEBUG if verbose else logging.INFO logging.basicConfig( level=level, format="%(asctime)s [%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) def main() -> int: """CLI 진입점.""" parser = argparse.ArgumentParser(description="매매 기법 실행 및 GT 정합 비교 (2단계)") parser.add_argument( "--techniques", type=str, default=None, help="실행할 기법 ID (쉼표 구분). 기본: 전체", ) parser.add_argument("--tolerance", type=int, default=None, help="GT 정합 허용 봉 수") parser.add_argument("--no-report", action="store_true", help="비교 리포트 생략") parser.add_argument("-v", "--verbose", action="store_true") args = parser.parse_args() _configure_logging(args.verbose) settings = load_settings() if not settings.ground_truth_file.exists(): logging.error("Ground Truth 파일 없음: %s — 먼저 02_ground_truth.py 실행", settings.ground_truth_file) return 1 gt_result = load_ground_truth(settings.ground_truth_file) technique_ids = None if args.techniques: technique_ids = [t.strip() for t in args.techniques.split(",") if t.strip()] params = TechniqueParams( interval_min=settings.gt_interval_min, lookback_days=settings.gt_lookback_days, min_leg_pct=settings.gt_min_leg_pct, initial_cash_krw=settings.gt_initial_cash_krw, fee_rate=settings.gt_trading_fee_rate, extra={"reversal_pct": settings.gt_zigzag_reversal_pct}, ) tolerance = args.tolerance or settings.gt_align_tolerance_bars logging.info( "기법 실행: %s %s, %s일, tolerance=%s봉", settings.symbol, interval_label(params.interval_min), params.lookback_days, tolerance, ) results = run_all_techniques( db_path=settings.db_path, symbol=settings.symbol, params=params, gt_result=gt_result, tolerance_bars=tolerance, technique_ids=technique_ids, ) saved_paths: list[Path] = [] for result in results: path = save_technique_result(result, settings.techniques_dir) saved_paths.append(path) align = result.alignment or {} legs = align.get("legs", {}) print( f" [{result.technique_id}] {result.technique_name}: " f"레그 {result.summary.get('leg_count', 0)}개, " f"수익 {result.pnl.get('total_return_pct', 0):+.1f}%, " f"GT정합 score={align.get('score', 0)*100:.1f} " f"(leg recall {legs.get('leg_recall', 0)*100:.0f}%)" ) print(f"\n=== 2단계 기법 실행 완료 ({len(results)}개) ===") print(f"저장: {settings.techniques_dir}/") for path in saved_paths: print(f" - {path.name}") if not args.no_report: report = build_comparison_report(results, gt_result, settings.symbol) json_path = save_comparison_report(report, settings.analysis_report_json) html_path = render_comparison_html(report, settings.analysis_report_html) print(f"\n=== GT 정합 순위 (상위 3) ===") gt_return = report["gt"]["return_pct"] print(f"GT 벤치마크: {gt_return:+.1f}%") for idx, row in enumerate(report["ranking"][:3], start=1): print( f" {idx}. {row['technique_name']}: " f"score {row['score']*100:.1f}, " f"수익 {row['tech_return_pct']:+.1f}%, " f"leg recall {row['leg_recall']*100:.0f}%" ) print(f"리포트 JSON: {json_path}") print(f"리포트 HTML: {html_path}") print(f"\n등록 기법: {', '.join(list_technique_ids())}") return 0 if __name__ == "__main__": raise SystemExit(main())