옵투나 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

@@ -0,0 +1,232 @@
#!/usr/bin/env python3
"""Optuna mode 타점 vs mode+100호가 vs mode+100+휩쏘 — 실엔진 백테 비교.
후처리 표의 「필터후 +23,337」은 체결 재시뮬이고,
이 스크립트는 mode params 로 포트폴리오 백테를 다시 돌린다.
"""
from __future__ import annotations
import json
import sys
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, Optional
ROOT = Path("/home/hoon/kis_bot")
sys.path.insert(0, str(ROOT))
JSON_PATH = ROOT / "kis_trader/backtest/results/optuna_momentum_tpe_20260821_220227.json"
OUT_PATH = ROOT / "kis_trader/backtest/results" / (
f"compare_mom_mode_ob100_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
)
def _pack(label: str, rec: Optional[Dict[str, Any]], note: str = "") -> Dict[str, Any]:
if not isinstance(rec, dict):
return {"label": label, "ok": False, "note": note or "evaluate None"}
return {
"label": label,
"ok": True,
"total_pnl": rec.get("total_pnl"),
"total_trades": rec.get("total_trades"),
"win_rate": rec.get("win_rate"),
"pf": rec.get("pf"),
"mdd": rec.get("mdd"),
"stability_score": rec.get("stability_score"),
"n_losing_days": rec.get("n_losing_days"),
"worst_day_pnl": rec.get("worst_day_pnl"),
"note": note,
}
def main() -> int:
print(f"[1] load {JSON_PATH}", flush=True)
data = json.loads(JSON_PATH.read_text(encoding="utf-8"))
mc = data.get("mode_combo") or {}
mode_params = dict(mc.get("params") or {})
if not mode_params:
print("❌ mode_combo.params 없음", flush=True)
return 1
mode_a = next(
(a for a in ((data.get("postprocess_topn") or {}).get("postprocess_by_anchor") or [])
if a.get("role") == "mode"),
None,
)
if not mode_a:
print("❌ mode 후처리 앵커 없음", flush=True)
return 1
combos = ((mode_a.get("orderbook") or {}).get("combos") or {})
e = combos.get("e") or combos.get("100") or {}
if not e.get("ok"):
print("❌ 100방(e) 없음", flush=True)
return 1
ep = dict(e.get("params") or {})
ws = e.get("whipsaw") if isinstance(e.get("whipsaw"), dict) else None
if not (ws and ws.get("ok")):
ws = mode_a.get("whipsaw") or {}
wp = dict((ws or {}).get("params") or {}) if (ws or {}).get("ok") else {}
start, end = str(data["start"]), str(data["end"])
slot = float(data.get("slot_money") or 300000)
ms = int(data.get("max_stocks") or 8)
bud = float(data.get("total_budget_krw") or 1200000)
grid_keys = list(data.get("grid_keys") or [])
print(
f"[2] prepare momentum {start}~{end} OB로드=on slot={slot} max={ms} bud={bud}",
flush=True,
)
print(f" 100방: spread≤{ep.get('orderbook_max_spread_pct')} ratio≥{ep.get('orderbook_min_bid_ask_ratio')} ask×{ep.get('orderbook_entry_ask_max_mult')}", flush=True)
print(f" 휩쏘: {wp}", flush=True)
print(
f" 후처리참고 필터후 OB {((e.get('recommended_stats') or {}))} / 휩쏘 {((ws or {}).get('recommended_stats') or {})}",
flush=True,
)
from kis_trader.backtest.optuna_momentum import prepare_momentum_search_context
from kis_trader.backtest.param_search_momentum import evaluate_momentum_param_combo
ctx = prepare_momentum_search_context(
start, end, str(data.get("mode") or "tpe"),
slot_money=slot, max_stocks=ms, total_budget_krw=bud,
orderbook_filter="on", # 호가·틱 로드 (필터 ON/OFF는 콤보에서 덮어씀)
history_source="kiwoom",
market="KR",
)
if ctx is None:
print("❌ prepare 실패", flush=True)
return 1
# prepare 가 base_fixed 에 필터 ON 을 넣어도, 케이스별로 명시 덮어씀
base = dict(ctx.base_fixed)
def _eval(combo: Dict[str, Any]) -> Optional[Dict[str, Any]]:
return evaluate_momentum_param_combo(
combo,
base_fixed=base,
grid_keys=grid_keys or list(ctx.grid_keys),
codes_candles=ctx.codes_candles,
min_trades=1,
min_win_rate=0.0,
min_pf=0.0,
universe_by_slot=ctx.universe_by_slot,
slot_money=ctx.slot_money,
max_stocks=ctx.max_stocks,
total_budget_krw=ctx.total_budget_krw,
fee_rate=ctx.fee_rate,
sell_tax=ctx.sell_tax,
period_days=ctx.period_days,
cache_holder=ctx.cache_holder,
ticks_by_code=ctx.ticks_by_code,
orderbook_by_code=ctx.orderbook_by_code,
program_by_code=ctx.program_by_code,
log_verdict_by_code=getattr(ctx, "log_verdict_by_code", None),
start_key=ctx.start_key,
end_key=ctx.end_key,
include_trades=False,
)
cases = []
# A) 타점만 (호가·휩쏘 OFF)
a = dict(mode_params)
a["_orderbook_filter_enabled"] = False
a["whipsaw_enabled"] = False
print("[3A] mode 타점만 (호가OFF·휩쏘OFF)", flush=True)
cases.append(_pack("A_mode_only", _eval(a), "호가OFF·휩쏘OFF"))
# B) +100 진입호가
b = dict(mode_params)
b["_orderbook_filter_enabled"] = True
b["_ob_max_spread_pct"] = float(ep["orderbook_max_spread_pct"])
b["_ob_min_bid_ask_ratio"] = float(ep["orderbook_min_bid_ask_ratio"])
if ep.get("orderbook_entry_ask_max_mult") is not None:
b["_ob_ask_max_mult"] = float(ep["orderbook_entry_ask_max_mult"])
b["whipsaw_enabled"] = False
print("[3B] mode + 100진입호가 (휩쏘OFF)", flush=True)
cases.append(_pack("B_mode_ob100", _eval(b), "호가100 ON · 휩쏘OFF"))
# C) +100 + 휩쏘
c = dict(b)
c["whipsaw_enabled"] = True
if wp.get("whipsaw_subbar_sec") is not None:
c["whipsaw_subbar_sec"] = int(wp["whipsaw_subbar_sec"])
if wp.get("whipsaw_lookback_sec") is not None:
c["whipsaw_lookback_sec"] = int(wp["whipsaw_lookback_sec"])
if wp.get("whipsaw_dip_pct") is not None:
c["whipsaw_dip_pct"] = float(wp["whipsaw_dip_pct"])
print("[3C] mode + 100진입호가 + 휩쏘", flush=True)
cases.append(_pack("C_mode_ob100_whip", _eval(c), "호가100 ON · 휩쏘ON"))
# D) 웹 폼 방금 끝난 결과(참고)
web_path = ROOT / "kis_trader/backtest/results/momentum_bt_20260818_20260821_20260823_152820.json"
web_row = {"label": "D_web_form_done", "ok": False, "note": "파일없음"}
if web_path.is_file():
wj = json.loads(web_path.read_text(encoding="utf-8"))
sm = wj.get("summary") or wj
web_row = {
"label": "D_web_form_done",
"ok": True,
"total_pnl": sm.get("total_pnl"),
"total_trades": sm.get("total_trades"),
"win_rate": sm.get("win_rate"),
"pf": sm.get("profit_factor") or sm.get("pf"),
"mdd": sm.get("max_drawdown") or sm.get("mdd"),
"note": "웹 폼(다른 호가/휩쏘 DB값) 방금 완료 — Optuna 100방과 다름",
}
cases.append(web_row)
# E) Optuna JSON mode 실측 + 후처리 표 숫자(참고)
bt = mc.get("backtest") or {}
cases.append({
"label": "E_optuna_mode_recorded",
"ok": True,
"total_pnl": bt.get("total_pnl"),
"total_trades": bt.get("total_trades"),
"win_rate": bt.get("win_rate"),
"pf": bt.get("pf"),
"note": "Optuna JSON mode_combo.backtest (호가탐색 전 타점 실측)",
})
cases.append({
"label": "F_post_ob100_filter_sim",
"ok": True,
"total_pnl": (e.get("recommended_stats") or {}).get("pnl"),
"total_trades": (e.get("recommended_stats") or {}).get("count"),
"win_rate": (e.get("recommended_stats") or {}).get("win_rate"),
"note": "후처리 필터시뮬(체결 재필터) — 엔진 재백테 아님",
})
cases.append({
"label": "G_post_ob100_whip_filter_sim",
"ok": bool(ws and ws.get("ok")),
"total_pnl": ((ws or {}).get("recommended_stats") or {}).get("pnl"),
"total_trades": ((ws or {}).get("recommended_stats") or {}).get("count"),
"win_rate": ((ws or {}).get("recommended_stats") or {}).get("win_rate"),
"note": "후처리 휩쏘 필터시뮬 — 엔진 재백테 아님",
})
out = {
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"source_json": str(JSON_PATH),
"period": f"{start}~{end}",
"ob100": ep,
"whip": wp,
"cases": cases,
}
OUT_PATH.write_text(json.dumps(out, indent=2, ensure_ascii=False), encoding="utf-8")
print("\n===== 비교 =====", flush=True)
for row in cases:
print(
f"{row['label']:28s} ok={row.get('ok')} "
f"trades={row.get('total_trades')} WR={row.get('win_rate')} "
f"PF={row.get('pf')} PnL={row.get('total_pnl')} "
f"| {row.get('note','')}",
flush=True,
)
print(f"\n💾 {OUT_PATH}", flush=True)
print("OK", flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())