Files
Bithumb/deepcoin/matching/option_c_phase2.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

246 lines
7.4 KiB
Python

"""
Option C 2차 목표(+1000%) 검증 — hybrid tier 포트폴리오 WF·슬리피지·Go/No-Go.
"""
from __future__ import annotations
from typing import Any
import pandas as pd
from config import (
GT_INITIAL_CASH_KRW,
LIVE_SLIPPAGE_PCT,
SIM_HYBRID_MAX_MDD_PCT,
SIM_HYBRID_PORTFOLIO_WF_MIN_RATIO,
SIM_OPTION_C_MIN_GT_CAPTURE,
SIM_OPTION_C_PHASE2_FEE_STRESS_RATIO,
SIM_OPTION_C_PHASE2_TARGET_PNL_PCT,
)
from deepcoin.ground_truth.gt_allocation import simulate_portfolio_summary
def apply_slippage_to_trades(
trades: list[dict[str, Any]],
slippage_pct: float = LIVE_SLIPPAGE_PCT,
) -> list[dict[str, Any]]:
"""
체결가에 슬리피지 반영 (매수 불리·매도 불리).
Args:
trades: amount_krw·price·action trade dict.
slippage_pct: 체결가 대비 % (0.05 = 0.05%).
Returns:
price 조정된 trade dict 복사본.
"""
slip = float(slippage_pct) / 100.0
out: list[dict[str, Any]] = []
for t in trades:
row = dict(t)
price = float(row.get("price") or 0)
if price <= 0:
out.append(row)
continue
action = row.get("action", "")
if action == "buy":
row["price"] = round(price * (1.0 + slip), 4)
elif action == "sell":
row["price"] = round(price * (1.0 - slip), 4)
out.append(row)
return out
def walk_forward_portfolio_by_month(
steps: list[dict[str, Any]],
*,
initial_cash: float = GT_INITIAL_CASH_KRW,
) -> list[dict[str, Any]]:
"""
포트폴리오 step에서 월별 자산 증감률 (인과적).
Args:
steps: simulate_portfolio_steps 결과.
initial_cash: 첫 달 시작 자산(이전 달 종료 없을 때).
Returns:
{month, pnl_pct, start_asset, end_asset} 리스트.
"""
if not steps:
return []
df = pd.DataFrame(steps)
df["ts"] = pd.to_datetime(df["dt"])
df = df.sort_values("ts")
df["month"] = df["ts"].dt.to_period("M").astype(str)
rows: list[dict[str, Any]] = []
prev_end = float(initial_cash)
for month in sorted(df["month"].unique()):
grp = df[df["month"] == month]
end_asset = float(grp["total_asset_krw"].iloc[-1])
pnl_pct = (end_asset - prev_end) / prev_end * 100.0 if prev_end > 0 else 0.0
rows.append(
{
"month": month,
"pnl_pct": round(pnl_pct, 2),
"start_asset_krw": round(prev_end, 0),
"end_asset_krw": round(end_asset, 0),
}
)
prev_end = end_asset
return rows
def walk_forward_portfolio_summary(wf_rows: list[dict[str, Any]]) -> dict[str, Any]:
"""
월별 포트폴리오 WF 요약.
Args:
wf_rows: walk_forward_portfolio_by_month 결과.
Returns:
months, positive_months, positive_ratio, mean_pnl_pct.
"""
if not wf_rows:
return {"months": 0, "positive_ratio": 0.0, "mean_pnl_pct": 0.0}
n = len(wf_rows)
pos = sum(1 for r in wf_rows if float(r.get("pnl_pct") or 0) > 0)
mean_pnl = sum(float(r.get("pnl_pct") or 0) for r in wf_rows) / n
return {
"months": n,
"positive_months": pos,
"positive_ratio": round(pos / n, 4),
"mean_pnl_pct": round(mean_pnl, 2),
}
def simulate_hybrid_slippage_stress(
sized_trades: list[dict[str, Any]],
*,
last_price: float | None,
slippage_pct: float = LIVE_SLIPPAGE_PCT,
initial_cash: float = GT_INITIAL_CASH_KRW,
fee_rate: float,
) -> dict[str, Any]:
"""
hybrid sized trades + 슬리피지 포트폴리오 요약.
Args:
sized_trades: build_monitor_hybrid_sized_trades 출력.
last_price: 미청산 평가가.
slippage_pct: 체결 슬리피지 %.
initial_cash: 시작 현금.
fee_rate: 수수료율.
Returns:
simulate_portfolio_summary 결과.
"""
slipped = apply_slippage_to_trades(sized_trades, slippage_pct)
result = simulate_portfolio_summary(
slipped,
initial_cash=initial_cash,
fee_rate=fee_rate,
last_price=last_price,
use_amount_krw=True,
)
result["slippage_pct"] = slippage_pct
result["sizing_mode"] = "hybrid_slippage_stress"
return result
def evaluate_option_c_phase2_go(
hybrid_go: dict[str, Any],
hybrid_full: dict[str, Any],
hybrid_holdout: dict[str, Any],
hybrid_fee_stress: dict[str, Any],
hybrid_slippage: dict[str, Any],
portfolio_wf_summary: dict[str, Any],
gt_pnl_pct: float,
) -> dict[str, Any]:
"""
Option C 2차(+1000%) Go/No-Go.
Args:
hybrid_go: 1차 hybrid tier Go 결과.
hybrid_full: 전기간 hybrid 포트폴리오.
hybrid_holdout: holdout 구간 증감.
hybrid_fee_stress: 수수료 2x hybrid.
hybrid_slippage: 슬리피지 반영 hybrid.
portfolio_wf_summary: 월별 포트폴리오 WF.
gt_pnl_pct: GT 전기간 PnL %.
Returns:
go, checks, targets.
"""
full_pnl = float(hybrid_full.get("pnl_pct", 0))
capture = full_pnl / gt_pnl_pct if abs(gt_pnl_pct) > 1e-6 else 0.0
ho_pnl = float(hybrid_holdout.get("pnl_pct", -999))
mdd = float(hybrid_full.get("max_drawdown_pct", 999))
fee_pnl = float(hybrid_fee_stress.get("pnl_pct", -999))
slip_pnl = float(hybrid_slippage.get("pnl_pct", -999))
wf_ratio = float(portfolio_wf_summary.get("positive_ratio", 0))
c_phase1 = bool(hybrid_go.get("go"))
c_pnl = full_pnl >= SIM_OPTION_C_PHASE2_TARGET_PNL_PCT
c_capture = capture >= SIM_OPTION_C_MIN_GT_CAPTURE
c_holdout = ho_pnl > 0.0
c_mdd = mdd <= SIM_HYBRID_MAX_MDD_PCT
c_fee = fee_pnl >= SIM_OPTION_C_PHASE2_TARGET_PNL_PCT * SIM_OPTION_C_PHASE2_FEE_STRESS_RATIO
c_slip = slip_pnl > 0.0
c_wf = wf_ratio >= SIM_HYBRID_PORTFOLIO_WF_MIN_RATIO
all_go = (
c_phase1
and c_pnl
and c_capture
and c_holdout
and c_mdd
and c_fee
and c_slip
and c_wf
)
return {
"go": all_go,
"gt_capture_ratio": round(capture, 4),
"targets": {
"phase2_pnl_pct": SIM_OPTION_C_PHASE2_TARGET_PNL_PCT,
"min_gt_capture": SIM_OPTION_C_MIN_GT_CAPTURE,
"portfolio_wf_min_ratio": SIM_HYBRID_PORTFOLIO_WF_MIN_RATIO,
},
"checks": [
{"name": "phase1_hybrid_go", "pass": c_phase1},
{
"name": "full_pnl_1000pct",
"pass": c_pnl,
"value": full_pnl,
},
{
"name": "gt_capture_23pct",
"pass": c_capture,
"value": round(capture, 4),
},
{"name": "holdout_pnl_positive", "pass": c_holdout, "value": ho_pnl},
{"name": "max_mdd", "pass": c_mdd, "value": mdd},
{
"name": "fee_stress_ratio",
"pass": c_fee,
"value": fee_pnl,
"threshold": round(
SIM_OPTION_C_PHASE2_TARGET_PNL_PCT * SIM_OPTION_C_PHASE2_FEE_STRESS_RATIO,
2,
),
},
{
"name": "slippage_stress_positive",
"pass": c_slip,
"value": slip_pnl,
"note": "체결가 슬리피지 반영 후에도 흑자",
},
{
"name": "portfolio_wf_positive_ratio",
"pass": c_wf,
"value": wf_ratio,
},
],
}