#!/usr/bin/env python3
"""3단계: 운영 백테스트(+1,873,140%) 매수·매도 타점 HTML 차트 생성."""
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 bithumb.config import load_settings
from bithumb.operations.chart import render_ops_live_chart
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 _write_index_html(
index_path: Path,
chart_name: str,
report: dict,
) -> Path:
"""docs/live 인덱스 HTML을 생성한다."""
sim = report["filtered_sim"]
ret = sim.get("total_return_pct", 0)
sizing_line = ""
if sim.get("sizing_rules_applied"):
lb = sim.get("learned_default_buy_cash_pct")
ls = sim.get("learned_default_sell_coin_pct")
if lb is not None and ls is not None:
sizing_line = (
f"
학습 비율: 매수 {float(lb) * 100:.0f}% · 매도 {float(ls) * 100:.0f}%"
f" (클러스터별 규칙 적용)"
)
index_path.parent.mkdir(parents=True, exist_ok=True)
html = f"""
Bithumb Live — 운영 백테스트 차트
Bithumb Live — 운영 백테스트
{report.get("symbol", "BTC")} · {report.get("technique_name", "")} ({report.get("technique_id", "")})
sim 기간: 최근 {report.get("sim_lookback_days", 1095)}일 ·
슬리피지 {report.get("slippage_rate", 0) * 100:.2f}% ·
일 체결 상한 {report.get("daily_max_trades", "-")} ·
MTF {"on" if report.get("mtf_enabled") else "off"}{sizing_line}
3년 수익률 (운영 규칙 sim)
{ret:+.2f}%
매수·매도 타점 차트 열기
- 매수 {sim.get("buys_executed", 0):,} / 매도 {sim.get("sells_executed", 0):,} 체결
- 초기 {sim.get("initial_cash_krw", 0):,.0f}원 → 최종 {sim.get("final_equity_krw", 0):,.0f}원
- 차트: B=매수 S=매도 마커, 이전/다음 타점 탐색, 기간 줌
"""
index_path.write_text(html, encoding="utf-8")
return index_path
def main() -> int:
"""CLI 진입점."""
parser = argparse.ArgumentParser(
description="운영 백테스트 매수·매도 타점 HTML 차트 (docs/live)",
)
parser.add_argument(
"-o",
"--output",
type=str,
default=None,
help="HTML 출력 경로 (기본: docs/live/{technique}_ops_chart.html)",
)
parser.add_argument(
"--chart-days",
type=int,
default=None,
help="차트 캔들 표시 일수 (기본: GT_SIM_LOOKBACK_DAYS)",
)
parser.add_argument("-v", "--verbose", action="store_true")
args = parser.parse_args()
_configure_logging(args.verbose)
settings = load_settings()
live_dir = ROOT / "docs" / "live"
default_name = f"{settings.ops_technique_id}_ops_chart.html"
output_path = Path(args.output) if args.output else live_dir / default_name
if not output_path.is_absolute():
output_path = ROOT / output_path
print("\n=== 운영 백테스트 차트 생성 ===", flush=True)
print(
f"기법: {settings.ops_technique_id} · 슬리피지 {settings.ops_slippage_rate} · "
f"일 상한 {settings.ops_daily_max_trades}",
flush=True,
)
chart_path, report = render_ops_live_chart(
settings,
output_path,
chart_lookback_days=args.chart_days,
)
data_js = chart_path.with_name(f"{chart_path.stem}_data.js")
index_path = _write_index_html(live_dir / "index.html", chart_path.name, report)
sim = report["filtered_sim"]
print(f"\n3년 수익률: {sim.get('total_return_pct'):+.2f}%")
print(f"매수/매도: {sim.get('buys_executed')}/{sim.get('sells_executed')}")
print(f"HTML: {chart_path}")
print(f"데이터: {data_js} ({data_js.stat().st_size / 1e6:.1f} MB)")
print(f"인덱스: {index_path}")
return 0
if __name__ == "__main__":
raise SystemExit(main())