옵투나 8방 후처리를 재탐색으로 변경하기 전전
This commit is contained in:
@@ -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"))
|
||||
|
||||
Reference in New Issue
Block a user