feat(옵투나·웹): 후처리 재탐색·ob_modes·적용감사·수집통계
- Optuna web jobs/TPE/apply snapshot·틱로더 정합, jobs limit·감사로그 - 백테 UI 호가모드·후보 적용 흐름, feed_collect_stats API/탭 - 가설검증·교차검증 룰, 4전략 스모크·OB slot41 진단 스크립트 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -18,6 +18,7 @@ from kis_trader.backtest import breakout_backtest_common as bbc
|
||||
from kis_trader.backtest.optuna_search_space import breakout_grid_axis_keys, suggest_breakout_params
|
||||
from kis_trader.backtest.optuna_breakout_tpe_space import (
|
||||
breakout_tpe_axis_keys,
|
||||
normalize_tpe_breakout_ob_mode,
|
||||
normalize_tpe_breakout_sl_mode,
|
||||
suggest_breakout_params_tpe,
|
||||
)
|
||||
@@ -80,6 +81,7 @@ class BreakoutSearchContext:
|
||||
cache_holder: Dict[str, Any] = field(default_factory=dict)
|
||||
shared_tick_store: Any = None # ws_ticks 공유메모리 핸들 (종료 시 unlink)
|
||||
tpe_sl_mode: str = "fixed" # TPE 고정 손절모드(탐색 축 아님)
|
||||
tpe_ob_mode: str = "off" # TPE 호가 스터디 스위치 on|off (탐색 축 아님)
|
||||
|
||||
|
||||
def prepare_breakout_search_context(
|
||||
@@ -115,16 +117,35 @@ def prepare_breakout_search_context(
|
||||
base_fixed["skip_hts_scan_dupes"] = False
|
||||
apply_session_to_fixed(base_fixed, time_start_hm=time_start_hm, time_end_hm=time_end_hm)
|
||||
|
||||
_ob_mode = (orderbook_filter or "off").strip().lower()
|
||||
# 돌파 TPE: 호가=스터디 스위치(--orderbook-filter on|off). trial categorical 금지.
|
||||
tpe_ob_mode = "off"
|
||||
if mode == "tpe":
|
||||
tpe_ob_mode = normalize_tpe_breakout_ob_mode(orderbook_filter)
|
||||
_ob_mode = tpe_ob_mode
|
||||
else:
|
||||
_ob_mode = (orderbook_filter or "off").strip().lower()
|
||||
if _ob_mode == "off":
|
||||
base_fixed["_orderbook_filter_enabled"] = False
|
||||
elif _ob_mode == "on":
|
||||
elif _ob_mode in ("on", "auto"):
|
||||
base_fixed["_orderbook_filter_enabled"] = True
|
||||
from kis_trader.backtest.optuna_tpe_common import optuna_tpe_needs_orderbook_feed
|
||||
|
||||
if mode == "tpe":
|
||||
# 글로벌 INCLUDE_ORDERBOOK 때문에 off 스터디까지 스냅 로드하던 구멍 차단
|
||||
need_ob_feed = tpe_ob_mode == "on"
|
||||
else:
|
||||
need_ob_feed = optuna_tpe_needs_orderbook_feed(mode, _ob_mode)
|
||||
ob_filter_on = bool(base_fixed.get("_orderbook_filter_enabled")) or _ob_mode == "auto"
|
||||
if need_ob_feed:
|
||||
base_fixed["backtest_use_trigger_snapshot_db"] = True
|
||||
# 돌파 filter_eval 거의 없음 → 호가 ON 스터디는 kiwoom_0d 본체 재계산
|
||||
base_fixed["backtest_use_kiwoom_body_snapshot"] = True
|
||||
base_fixed["_backtest_use_kiwoom_body"] = True
|
||||
logger.info(
|
||||
"📌 호가필터: %s (%s)",
|
||||
"📌 호가필터: %s (%s)%s",
|
||||
_ob_mode.upper(),
|
||||
"적용" if ob_filter_on else "스킵 — 코어 파라미터 순수 탐색",
|
||||
"스냅로드" if need_ob_feed else ("적용" if ob_filter_on else "스킵 — 코어 파라미터 순수 탐색"),
|
||||
" · 돌파TPE 스터디스위치" if mode == "tpe" else "",
|
||||
)
|
||||
|
||||
db = TradeDB()
|
||||
@@ -158,8 +179,9 @@ def prepare_breakout_search_context(
|
||||
tpe_sl_mode = normalize_tpe_breakout_sl_mode(sl_mode)
|
||||
base_fixed["sl_mode"] = tpe_sl_mode
|
||||
logger.info(
|
||||
"📌 TPE 손절모드 고정: %s (탐색 축 아님 · atr_sl_*/sl_pct 중 해당만)",
|
||||
"📌 TPE 손절모드 고정: %s · 호가스위치: %s (둘 다 탐색 축 아님)",
|
||||
tpe_sl_mode,
|
||||
tpe_ob_mode,
|
||||
)
|
||||
|
||||
from kis_trader.backtest.universe_history_source import (
|
||||
@@ -238,25 +260,42 @@ def prepare_breakout_search_context(
|
||||
pass
|
||||
|
||||
# grid 는 상단에서 mode별 설정 (tpe=빈 dict). grids[mode] 재조회 금지.
|
||||
_ob_axes = ("max_spread_pct", "min_bid_ask_ratio", "ask_wall_max_qty")
|
||||
# TPE=ask_max_mult · Grid 레거시=ask_wall_max_qty — 둘 다 스윕 축으로 인정
|
||||
_ob_axes = ("max_spread_pct", "min_bid_ask_ratio", "ask_max_mult", "ask_wall_max_qty")
|
||||
_ob_sweeping = any(len(set(grid.get(k) or [])) > 1 for k in _ob_axes)
|
||||
if ob_filter_on and _ob_sweeping:
|
||||
base_fixed["backtest_use_kiwoom_body_snapshot"] = True
|
||||
base_fixed["_backtest_use_kiwoom_body"] = True
|
||||
|
||||
# engine_probe 는 위에서 만들었으므로 body/스냅 플래그를 동기화 (로드·trial 공통)
|
||||
for _k in (
|
||||
"backtest_use_trigger_snapshot_db",
|
||||
"backtest_use_kiwoom_body_snapshot",
|
||||
"_backtest_use_kiwoom_body",
|
||||
):
|
||||
if _k in base_fixed:
|
||||
engine_probe[_k] = base_fixed[_k]
|
||||
|
||||
orderbook_by_code: Dict[str, Any] = {}
|
||||
program_by_code: Dict[str, Any] = {}
|
||||
log_verdict_by_code: Dict[str, Any] = {}
|
||||
_snap_db = TradeDB()
|
||||
try:
|
||||
from kis_trader.backtest.trigger_snapshot_loader import load_trigger_snapshots_by_code
|
||||
orderbook_by_code, program_by_code, trigger_snap_meta = load_trigger_snapshots_by_code(
|
||||
_snap_db, start_key, end_key, set(codes_candles.keys()),
|
||||
engine_params=engine_probe, strategy="BREAKOUT",
|
||||
)
|
||||
log_verdict_by_code = trigger_snap_meta.get("log_verdict_by_code") or {}
|
||||
finally:
|
||||
_snap_db.close()
|
||||
if need_ob_feed:
|
||||
_snap_db = TradeDB()
|
||||
try:
|
||||
from kis_trader.backtest.trigger_snapshot_loader import load_trigger_snapshots_by_code
|
||||
_load_params = dict(engine_probe)
|
||||
_load_params["_orderbook_filter_enabled"] = True
|
||||
orderbook_by_code, program_by_code, trigger_snap_meta = load_trigger_snapshots_by_code(
|
||||
_snap_db, start_key, end_key, set(codes_candles.keys()),
|
||||
engine_params=_load_params, strategy="BREAKOUT",
|
||||
)
|
||||
log_verdict_by_code = trigger_snap_meta.get("log_verdict_by_code") or {}
|
||||
ob_rows = int(trigger_snap_meta.get("ws_orderbook_rows_loaded") or 0)
|
||||
logger.info("✅ 호가ON 스터디용 ws_orderbook %s건", f"{ob_rows:,}")
|
||||
finally:
|
||||
_snap_db.close()
|
||||
else:
|
||||
logger.info("📌 호가OFF 스터디 — 호가 스냅 로드 생략")
|
||||
|
||||
universe_by_slot = None
|
||||
fallback_sim_interval = 5
|
||||
@@ -315,12 +354,17 @@ def prepare_breakout_search_context(
|
||||
total_budget_krw=total_budget_f,
|
||||
period_days=period_days,
|
||||
portfolio=portfolio,
|
||||
grid_keys=breakout_tpe_axis_keys(tpe_sl_mode) if mode == "tpe" else breakout_grid_axis_keys(mode),
|
||||
grid_keys=(
|
||||
breakout_tpe_axis_keys(tpe_sl_mode, orderbook_filter=tpe_ob_mode)
|
||||
if mode == "tpe"
|
||||
else breakout_grid_axis_keys(mode)
|
||||
),
|
||||
start_key=start_key,
|
||||
end_key=end_key,
|
||||
cache_holder=cache_holder,
|
||||
shared_tick_store=shared_tick_store,
|
||||
tpe_sl_mode=tpe_sl_mode,
|
||||
tpe_ob_mode=tpe_ob_mode,
|
||||
)
|
||||
|
||||
|
||||
@@ -365,7 +409,11 @@ def run_breakout_optuna(
|
||||
|
||||
def objective(trial: optuna.Trial) -> float:
|
||||
if ctx.mode == "tpe":
|
||||
combo = suggest_breakout_params_tpe(trial, sl_mode=ctx.tpe_sl_mode)
|
||||
combo = suggest_breakout_params_tpe(
|
||||
trial,
|
||||
sl_mode=ctx.tpe_sl_mode,
|
||||
orderbook_filter=ctx.tpe_ob_mode,
|
||||
)
|
||||
else:
|
||||
combo = suggest_breakout_params(trial, ctx.mode)
|
||||
result = evaluate_breakout_param_combo(
|
||||
|
||||
Reference in New Issue
Block a user