#!/usr/bin/env python3 """1단계: 연속 매수·매도 클러스터별 매수·매도 비율 튜닝 (타점 고정).""" from __future__ import annotations import argparse import json 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.ground_truth.sizing_rules import save_sizing_rules from bithumb.ground_truth.sizing_tune import tune_sizing_rules from bithumb.operations.signal_pipeline import run_signal_pipeline 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="연속 매수·매도 클러스터 상태별 사이징 비율 학습", ) parser.add_argument( "-o", "--output", type=str, default=None, help="규칙 JSON 경로 (기본: OPS_SIZING_RULES_JSON)", ) parser.add_argument( "--min-bucket-samples", type=int, default=5, help="클러스터 버킷별 최소 샘플 수", ) parser.add_argument("-v", "--verbose", action="store_true") args = parser.parse_args() _configure_logging(args.verbose) settings = load_settings() output_path = Path(args.output) if args.output else settings.ops_sizing_rules_json if not output_path.is_absolute(): output_path = ROOT / output_path print("\n=== 매수·매도 비율 튜닝 (타점 고정) ===", flush=True) print( f"기법: {settings.ops_technique_id} · sim {settings.gt_sim_lookback_days}일 · " f"기본 {settings.ops_buy_cash_pct:.0%}/{settings.ops_sell_coin_pct:.0%}", flush=True, ) pipeline = run_signal_pipeline(settings, use_cache=True) rules, final_sim = tune_sizing_rules( settings, pipeline["kept"], data_end=pipeline["data_end"], last_mark_price=pipeline["last_price"], technique_id=pipeline["technique_id"], min_bucket_samples=args.min_bucket_samples, ) save_sizing_rules(rules, output_path) tuning = rules.get("tuning") or {} print(f"\n기준(고정 10%): {tuning.get('baseline_return_pct'):+.2f}%") print( f"학습 후: {final_sim.get('total_return_pct'):+.2f}% · " f"매수/매도 {final_sim.get('buys_executed')}/{final_sim.get('sells_executed')}" ) print( f"전역 비율: 매수 {rules.get('default_buy_cash_pct', 0):.0%} · " f"매도 {rules.get('default_sell_coin_pct', 0):.0%}" ) by_cluster = rules.get("by_cluster") or {} if by_cluster.get("buy") or by_cluster.get("sell"): print("클러스터별:") print(json.dumps(by_cluster, ensure_ascii=False, indent=2)) print(f"\n규칙 JSON: {output_path}") return 0 if __name__ == "__main__": raise SystemExit(main())