옵투나 8방 후처리를 재탐색으로 변경하기 전전
This commit is contained in:
232
scripts/compare_momentum_mode_ob100_whip.py
Normal file
232
scripts/compare_momentum_mode_ob100_whip.py
Normal 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())
|
||||
Reference in New Issue
Block a user