옵투나 8방 후처리를 재탐색으로 변경하기 전전

This commit is contained in:
Your Name
2026-08-23 17:51:29 +09:00
parent 3eaa3b61df
commit ba2f9f0953
58 changed files with 25306 additions and 344 deletions

View File

@@ -112,6 +112,10 @@ def mode_combo_from_results(
def _bt_summary(result: Optional[Dict[str, Any]]) -> Dict[str, Any]:
"""실측 요약 — 사후합격/안정 Top 표와 같은 일별 안정 필드도 유지.
(예전엔 pnl·trades·wr·pf만 남겨 mode 표에 손실일·최악일·안정점수가 — 로 비었음)
"""
if not result:
return {
"ok": False,
@@ -121,7 +125,7 @@ def _bt_summary(result: Optional[Dict[str, Any]]) -> Dict[str, Any]:
"pf": None,
"note": "evaluate returned None (게이트·0건·invalid)",
}
return {
out: Dict[str, Any] = {
"ok": True,
"total_pnl": float(result.get("total_pnl") or 0),
"total_trades": int(result.get("total_trades") or 0),
@@ -129,6 +133,77 @@ def _bt_summary(result: Optional[Dict[str, Any]]) -> Dict[str, Any]:
"pf": float(result.get("pf") or 0) if result.get("pf") is not None else None,
"score": float(result.get("score") or 0) if result.get("score") is not None else None,
}
# attach_daily_stability 가 evaluate_* 에 붙인 키 — 웹 mode 표 컬럼용
for k in (
"stability_score",
"n_losing_days",
"n_active_days",
"worst_day_pnl",
"best_day_pnl",
"daily_pnl_mean",
"daily_pnl_std",
"stability_lambda",
"daily_pnl",
):
if k in result and result.get(k) is not None:
out[k] = result.get(k)
return out
def _attach_best_trial_trades(
out_data: Dict[str, Any],
evaluate_fn: EvalFn,
*,
log: logging.Logger,
) -> None:
"""results[0](#1 best) 체결을 export 시 1회 재실측해 JSON에 남김 (정합 diff용)."""
from kis_trader.backtest.optuna_common import slim_trades_for_optuna_json
res0 = (out_data.get("results") or [None])[0]
if not isinstance(res0, dict):
return
combo = dict(res0.get("params") or {})
if not combo:
return
try:
raw = evaluate_fn(dict(combo))
except Exception as exc:
log.warning("⚠️ best 체결 재실측 예외: %s", exc)
return
if not isinstance(raw, dict):
log.warning("⚠️ best 체결 재실측 실패(게이트/None)")
return
fills = list(raw.pop("_trades", None) or [])
slim = slim_trades_for_optuna_json(fills)
res0["_trades"] = slim
out_data["best_trial_trades"] = slim
# trial 기록값과 export 시점 재실측이 다르면 바로 보이게
out_data["best_trial_reeval"] = {
"total_pnl": raw.get("total_pnl"),
"total_trades": raw.get("total_trades"),
"win_rate": raw.get("win_rate"),
"pf": raw.get("pf"),
"mdd": raw.get("mdd"),
"recorded_total_pnl": res0.get("total_pnl"),
"recorded_total_trades": res0.get("total_trades"),
"delta_pnl": (
float(raw.get("total_pnl") or 0) - float(res0.get("total_pnl") or 0)
),
"delta_trades": (
int(raw.get("total_trades") or 0) - int(res0.get("total_trades") or 0)
),
"note": (
"export 직후 동일 evaluate_fn 재실측. "
"delta≠0 이면 trial 기록과 엔진/데이터 드리프트."
),
}
log.info(
"🧾 [best 체결저장] n=%s | reeval_pnl=%s recorded_pnl=%s Δ=%+.0f",
len(slim),
raw.get("total_pnl"),
res0.get("total_pnl"),
float(raw.get("total_pnl") or 0) - float(res0.get("total_pnl") or 0),
)
def enrich_out_data_with_mode_combo(
@@ -205,20 +280,30 @@ def enrich_out_data_with_mode_combo(
except Exception as exc:
lg.warning("⚠️ mode_combo 부분저장 실패: %s", exc)
# #1 best 체결 — mode 실측 전에 저장 (후처리가 evaluate를 여러 번 돌려도 best는 1회)
if evaluate_fn is not None:
_attach_best_trial_trades(out_data, evaluate_fn, log=lg)
mode_fills: List[Dict[str, Any]] = []
if evaluate_fn is not None and mode_params:
try:
from kis_trader.backtest.optuna_common import slim_trades_for_optuna_json
bt = evaluate_fn(dict(mode_params))
if isinstance(bt, dict):
mode_fills = list(bt.pop("_trades", None) or [])
report["backtest"] = _bt_summary(bt)
# 후처리용 raw fills 는 mode_fills 로 유지 + JSON에는 슬림 저장
if report["backtest"].get("ok") and isinstance(report.get("backtest"), dict):
report["backtest"]["_trades"] = slim_trades_for_optuna_json(mode_fills)
if report["backtest"].get("ok"):
lg.info(
"🧪 [mode] 실측 백테 | pnl=%s | trades=%s | wr=%.1f%% | pf=%s",
"🧪 [mode] 실측 백테 | pnl=%s | trades=%s | wr=%.1f%% | pf=%s | _trades=%s",
report["backtest"]["total_pnl"],
report["backtest"]["total_trades"],
float(report["backtest"]["win_rate"] or 0),
report["backtest"].get("pf"),
len(report["backtest"].get("_trades") or []),
)
else:
lg.warning("🧪 [mode] 실측 백테 실패/게이트: %s", report["backtest"].get("note"))