파이프라인 산출물(data/, docs/)을 Git 추적에서 제외하고 히스토리를 단일 커밋으로 재구성해 저장소 용량을 경량화한다. Co-authored-by: Cursor <cursoragent@cursor.com>
133 lines
4.6 KiB
Python
133 lines
4.6 KiB
Python
"""2단계: 인과 기법 GT 정합 비교 리포트."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from deepcoin.techniques.base import TechniqueResult
|
|
|
|
|
|
def build_comparison_report(
|
|
results: list[TechniqueResult],
|
|
gt_result: dict[str, Any],
|
|
symbol: str,
|
|
) -> dict[str, Any]:
|
|
"""기법 비교 요약 리포트를 생성한다."""
|
|
gt_meta = gt_result.get("meta", {})
|
|
gt_pnl = gt_result.get("pnl", {})
|
|
gt_summary = gt_result.get("summary", {})
|
|
|
|
rows: list[dict[str, Any]] = []
|
|
for result in results:
|
|
align = result.alignment or {}
|
|
buy = align.get("buy", {})
|
|
sell = align.get("sell", {})
|
|
legs = align.get("legs", {})
|
|
rows.append(
|
|
{
|
|
"technique_id": result.technique_id,
|
|
"technique_name": result.technique_name,
|
|
"category": result.category,
|
|
"causal": result.causal,
|
|
"leg_count": result.summary.get("leg_count", 0),
|
|
"tech_return_pct": result.pnl.get("total_return_pct", 0.0),
|
|
"buy_recall": buy.get("recall", 0.0),
|
|
"sell_recall": sell.get("recall", 0.0),
|
|
"leg_recall": legs.get("leg_recall", 0.0),
|
|
"return_capture_ratio": align.get("return_capture_ratio", 0.0),
|
|
"score": align.get("score", 0.0),
|
|
"avg_buy_offset": buy.get("avg_bar_offset", 0.0),
|
|
"avg_sell_offset": sell.get("avg_bar_offset", 0.0),
|
|
}
|
|
)
|
|
|
|
rows.sort(key=lambda r: r["score"], reverse=True)
|
|
|
|
return {
|
|
"generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
"symbol": symbol,
|
|
"gt": {
|
|
"leg_count": gt_summary.get("leg_count", 0),
|
|
"return_pct": gt_pnl.get("total_return_pct", 0.0),
|
|
"interval_label": gt_meta.get("interval_label", ""),
|
|
"lookback_days": gt_meta.get("lookback_days", 365),
|
|
},
|
|
"ranking": rows,
|
|
}
|
|
|
|
|
|
def save_comparison_report(report: dict[str, Any], json_path: Path) -> Path:
|
|
"""비교 리포트 JSON을 저장한다."""
|
|
json_path.parent.mkdir(parents=True, exist_ok=True)
|
|
with json_path.open("w", encoding="utf-8") as fp:
|
|
json.dump(report, fp, ensure_ascii=False, indent=2)
|
|
return json_path
|
|
|
|
|
|
def render_comparison_html(report: dict[str, Any], html_path: Path) -> Path:
|
|
"""비교 리포트 HTML을 생성한다."""
|
|
html_path.parent.mkdir(parents=True, exist_ok=True)
|
|
rows = report.get("ranking", [])
|
|
gt = report.get("gt", {})
|
|
|
|
table_rows = ""
|
|
for idx, row in enumerate(rows, start=1):
|
|
table_rows += f"""
|
|
<tr>
|
|
<td>{idx}</td>
|
|
<td>{row['technique_name']}</td>
|
|
<td>{row['category']}</td>
|
|
<td>{'Y' if row['causal'] else 'N'}</td>
|
|
<td>{row['leg_count']}</td>
|
|
<td>{row['tech_return_pct']:+.1f}%</td>
|
|
<td>{row['buy_recall']*100:.1f}%</td>
|
|
<td>{row['sell_recall']*100:.1f}%</td>
|
|
<td>{row['leg_recall']*100:.1f}%</td>
|
|
<td>{row['return_capture_ratio']*100:.1f}%</td>
|
|
<td><strong>{row['score']*100:.1f}</strong></td>
|
|
</tr>"""
|
|
|
|
html = f"""<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>DeepCoin 2단계 — 인과 GT 정합</title>
|
|
<style>
|
|
body {{ font-family: "Malgun Gothic", Arial, sans-serif; margin: 24px; color: #333; background: #f5f5f5; }}
|
|
h1 {{ font-size: 20px; margin-bottom: 8px; }}
|
|
.meta {{ color: #666; margin-bottom: 20px; font-size: 14px; }}
|
|
table {{ border-collapse: collapse; width: 100%; background: #fff; }}
|
|
th, td {{ border: 1px solid #ddd; padding: 8px 10px; text-align: center; font-size: 13px; }}
|
|
th {{ background: #e8e8e8; }}
|
|
tr:nth-child(even) {{ background: #fafafa; }}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>DeepCoin 2단계 — 인과 기법 Ground Truth 정합</h1>
|
|
<div class="meta">
|
|
생성: {report.get('generated_at', '')} |
|
|
{report.get('symbol', '')} |
|
|
GT: {gt.get('leg_count', 0)}레그, {gt.get('return_pct', 0):+.1f}% |
|
|
기간: 최근 {gt.get('lookback_days', 365)}일
|
|
</div>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>순위</th><th>기법</th><th>유형</th><th>인과</th><th>레그</th>
|
|
<th>수익률</th><th>매수 Recall</th><th>매도 Recall</th><th>레그 Recall</th>
|
|
<th>수익 포착</th><th>종합 Score</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>{table_rows}
|
|
</tbody>
|
|
</table>
|
|
</body>
|
|
</html>"""
|
|
|
|
with html_path.open("w", encoding="utf-8") as fp:
|
|
fp.write(html)
|
|
return html_path
|