Files
Bithumb/deepcoin/ground_truth/causal_gt_calibrate.py
xavis d385456867 hybrid DD tier와 Option C 2차(+1000%) 검증을 추가하고 실거래 사이징을 정합한다.
인과 GT leg 엔진·drawdown tier·train 캘리브레이션, Phase 2 Go/No-Go 및 시뮬 리포트를 반영한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 16:09:28 +09:00

174 lines
5.2 KiB
Python

"""
인과 GT leg 엔진 파라미터 그리드 탐색·최적 저장.
"""
from __future__ import annotations
import json
from itertools import product
from pathlib import Path
from typing import Any
from config import (
CHART_LOOKBACK_DAYS,
GT_BUY_BB_MAX,
GT_BUY_MIN_SWING_PCT,
GT_MIN_SWING_PCT,
GT_PIVOT_ORDER,
MATCH_PRIMARY_INTERVAL,
SYMBOL,
)
from deepcoin.data.mtf_bb import load_frames_from_db
from deepcoin.ground_truth.causal_gt_trades import simulate_causal_gt_portfolio
from deepcoin.ground_truth.ground_truth import load_ground_truth
from deepcoin.ops.monitor import Monitor
from deepcoin.paths import MATCHING_CAUSAL_GT_CALIBRATION_JSON, resolve_ground_truth_file
def default_causal_gt_params() -> dict[str, Any]:
"""
인과 GT leg 엔진 기본 파라미터.
Returns:
build_causal_split_buy_peak_sell_trades 키워드 인자.
"""
from config import (
CAUSAL_GT_MIN_BARS_BETWEEN_LEGS,
CAUSAL_GT_MIN_LEG_PCT,
CAUSAL_GT_PEAK_MODE,
CAUSAL_GT_USE_LOCAL_TROUGH,
)
return {
"pivot_order": GT_PIVOT_ORDER,
"buy_swing_pct": GT_BUY_MIN_SWING_PCT,
"sell_swing_pct": GT_MIN_SWING_PCT,
"bb_max": GT_BUY_BB_MAX,
"min_leg_pct": CAUSAL_GT_MIN_LEG_PCT,
"use_local_trough": CAUSAL_GT_USE_LOCAL_TROUGH,
"peak_mode": CAUSAL_GT_PEAK_MODE,
"min_bars_between_legs": CAUSAL_GT_MIN_BARS_BETWEEN_LEGS,
}
def load_causal_gt_params(path: Path | None = None) -> dict[str, Any]:
"""
캘리브레이션 JSON 또는 config 기본값.
Args:
path: JSON 경로. None이면 MATCHING_CAUSAL_GT_CALIBRATION_JSON.
Returns:
best params dict.
"""
p = path or MATCHING_CAUSAL_GT_CALIBRATION_JSON
if p.is_file():
data = json.loads(p.read_text(encoding="utf-8"))
best = data.get("best_params") or data.get("params")
if best:
return dict(best)
return default_causal_gt_params()
def _grid_space() -> dict[str, list[Any]]:
"""탐색 그리드 (로컬 peak 최적화 반영, 조합 ~864)."""
return {
"peak_mode": ["local", "zigzag"],
"pivot_order": [8, 10, 12, 15],
"buy_swing_pct": [2.0, 2.5, 3.0],
"sell_swing_pct": [3.0, 4.0],
"bb_max": [0.55, 0.65, 0.75],
"min_leg_pct": [3.0, 5.0, 8.0],
"min_bars_between_legs": [60, 90],
"use_local_trough": [True, False],
}
def run_causal_gt_calibration(
*,
min_trades: int = 30,
top_n: int = 20,
out_path: Path | None = None,
) -> dict[str, Any]:
"""
그리드 탐색 후 최적 파라미터 JSON 저장.
Args:
min_trades: 최소 체결 수 미만 조합 제외.
top_n: 상위 N개 기록.
out_path: 저장 경로.
Returns:
calibration report dict.
"""
gt = load_ground_truth(resolve_ground_truth_file()) or {}
mark = float((gt.get("summary") or {}).get("mark_price") or 0)
gt_pnl = float(
(gt.get("summary") or {}).get("pnl_pct")
or 0
)
mon = Monitor(cooldown_file=None)
frames = load_frames_from_db(mon, SYMBOL, lookback_days=CHART_LOOKBACK_DAYS)
df = frames[MATCH_PRIMARY_INTERVAL].copy()
grid = _grid_space()
keys = list(grid.keys())
results: list[dict[str, Any]] = []
total = 1
for k in keys:
total *= len(grid[k])
print(f"[causal_gt] 그리드 {total} 조합 탐색...")
done = 0
for combo in product(*(grid[k] for k in keys)):
params = dict(zip(keys, combo))
r = simulate_causal_gt_portfolio(df, last_price=mark or None, **params)
tc = int(r.get("trade_count") or 0)
done += 1
if done % 200 == 0:
print(f" ... {done}/{total}")
if tc < min_trades:
continue
pnl = float(r.get("pnl_pct") or 0)
results.append(
{
"pnl_pct": round(pnl, 2),
"trade_count": tc,
"leg_count": r.get("leg_count", 0),
"max_drawdown_pct": r.get("max_drawdown_pct"),
"capture_ratio": round(pnl / gt_pnl, 4) if gt_pnl else 0,
"params": params,
}
)
results.sort(key=lambda x: x["pnl_pct"], reverse=True)
best = results[0] if results else None
report: dict[str, Any] = {
"symbol": SYMBOL,
"interval_min": MATCH_PRIMARY_INTERVAL,
"gt_pnl_pct": gt_pnl,
"grid_combinations": total,
"valid_combinations": len(results),
"min_trades": min_trades,
"best": best,
"best_params": best["params"] if best else default_causal_gt_params(),
"top": results[:top_n],
"target_pnl_pct": 300.0,
"target_met": bool(best and best["pnl_pct"] >= 300.0),
}
out = out_path or MATCHING_CAUSAL_GT_CALIBRATION_JSON
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"[causal_gt] 저장: {out}")
if best:
print(
f"[causal_gt] 최적 PnL={best['pnl_pct']}% "
f"trades={best['trade_count']} legs={best['leg_count']} "
f"capture={best.get('capture_ratio', 0):.2%}"
)
else:
print("[causal_gt] 유효 조합 없음")
return report