refactor: DeepCoin 1·2단계 파이프라인으로 구조 재편
레거시 분석·매칭·운영 코드를 정리하고 src/deepcoin 기반으로 재구성한다. 1단계 GT는 2년 스윙·눌림목·돌파·다이버전스 타점을 차트에 표시하고, 2단계는 8개 매매 기법과 GT 정합 평가 스크립트를 추가한다. .env와 GT JSON 산출물은 추적에서 제외한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
137
scripts/03_run_techniques.py
Normal file
137
scripts/03_run_techniques.py
Normal file
@@ -0,0 +1,137 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user