feat: Enhance Optuna integration and logging for backtesting framework
Changes: - Added new API endpoints for continuing and confirming Optuna jobs, allowing for better management of ongoing studies. - Introduced detailed logging for tick feed tracking and order book processing, improving traceability of vendor performance during backtests. - Updated database schema to include new fields for managing Optuna study results, enhancing the ability to track study progress and outcomes. - Refactored existing functions to utilize the new logging and tracking features, ensuring consistency across the backtesting framework. Impact: - These enhancements improve the robustness and transparency of the Optuna backtesting process, facilitating better analysis and optimization of trading strategies.
This commit is contained in:
@@ -30,6 +30,7 @@ class Snap:
|
||||
best_ask: int
|
||||
bid_qty_l3: int = 0
|
||||
ask_qty_l3: int = 0
|
||||
source: str = "" # kiwoom_0d / ls_uh1 … 후처리 피드 추적용
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -201,6 +202,8 @@ def _fetch_ob_snaps(
|
||||
sel.append("bid_qty_l3")
|
||||
if "ask_qty_l3" in cols:
|
||||
sel.append("ask_qty_l3")
|
||||
if "source" in cols:
|
||||
sel.append("source")
|
||||
snap_sql = (
|
||||
f"SELECT {', '.join(sel)} FROM {table} "
|
||||
"WHERE code=%s AND snap_time >= %s AND snap_time < %s"
|
||||
@@ -230,6 +233,7 @@ def _fetch_ob_snaps(
|
||||
best_ask=int(sr["best_ask"] or 0),
|
||||
bid_qty_l3=int(sr["bid_qty_l3"] or 0) if "bid_qty_l3" in sr else 0,
|
||||
ask_qty_l3=int(sr["ask_qty_l3"] or 0) if "ask_qty_l3" in sr else 0,
|
||||
source=str(sr.get("source") or "").strip().lower() if "source" in sr else "",
|
||||
)
|
||||
)
|
||||
return snaps
|
||||
@@ -286,6 +290,7 @@ def raw_fills_to_ob_trades(
|
||||
"""백테 체결 dict(buy_time/buy_price/qty/pnl) → 호가 TradeInfo."""
|
||||
lookback, horizon = _ob_lookback_horizon()
|
||||
out: List[TradeInfo] = []
|
||||
n_miss = 0
|
||||
for b in raw_fills or []:
|
||||
if not isinstance(b, dict):
|
||||
continue
|
||||
@@ -312,8 +317,42 @@ def raw_fills_to_ob_trades(
|
||||
actual_profit_rate=actual_profit_rate,
|
||||
snaps=snaps,
|
||||
)
|
||||
# 후처리 피드 추적: 스냅 hit/miss + 벤더·bid/ask
|
||||
try:
|
||||
from kis_trader.backtest.optuna_feed_trace import maybe_log_bt_ob_postprocess_sample
|
||||
|
||||
if ti is not None:
|
||||
es = ti.entry_snaps[-1] if ti.entry_snaps else None
|
||||
maybe_log_bt_ob_postprocess_sample(
|
||||
code=code,
|
||||
buy_time=buy_dt.strftime("%Y%m%d%H%M%S"),
|
||||
buy_price=buy_price,
|
||||
snap_t=es.t.strftime("%Y%m%d%H%M%S") if es else "",
|
||||
best_bid=int(es.best_bid) if es else 0,
|
||||
best_ask=int(es.best_ask) if es else 0,
|
||||
spread_pct=float(ti.orig_spread_pct),
|
||||
bid_ask_ratio=float(ti.orig_bid_ask_ratio),
|
||||
snap_source=str(es.source or "") if es else "",
|
||||
hit=True,
|
||||
)
|
||||
else:
|
||||
n_miss += 1
|
||||
maybe_log_bt_ob_postprocess_sample(
|
||||
code=code,
|
||||
buy_time=buy_dt.strftime("%Y%m%d%H%M%S"),
|
||||
buy_price=buy_price,
|
||||
hit=False,
|
||||
)
|
||||
except Exception:
|
||||
if ti is None:
|
||||
n_miss += 1
|
||||
if ti:
|
||||
out.append(ti)
|
||||
if n_miss > 0:
|
||||
logger.info(
|
||||
"🔎 [호가후처리] 체결→스냅 미스 %s/%s건 (필터·기간·종목 공백)",
|
||||
n_miss, len(raw_fills or []),
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
@@ -809,6 +848,28 @@ def recommend_orderbook_parameters(
|
||||
lg.warning("⚠️ [%s] 테이블 조회 실패: %s. 추천 생략.", table, exc)
|
||||
return {"ok": False, "reason": "table_not_found", "table": table}
|
||||
|
||||
# 코어 TPE 호가OFF와 무관 — 후처리에서 DB 호가 벤더·기간을 추적 로그
|
||||
try:
|
||||
from kis_trader.backtest.optuna_feed_trace import (
|
||||
log_bt_feed_chain_banner,
|
||||
log_bt_postprocess_ob_db_scope,
|
||||
reset_bt_feed_sample_counter,
|
||||
)
|
||||
|
||||
reset_bt_feed_sample_counter(postprocess=True)
|
||||
log_bt_feed_chain_banner(context="호가후처리")
|
||||
log_bt_postprocess_ob_db_scope(
|
||||
db,
|
||||
table=table,
|
||||
cols=cols,
|
||||
source_filter=source_filter,
|
||||
date_from=str(date_from or ""),
|
||||
date_to=str(date_to or ""),
|
||||
context="호가후처리",
|
||||
)
|
||||
except Exception as e:
|
||||
lg.debug("호가후처리 피드추적 스킵: %s", e)
|
||||
|
||||
date_sql = f"SELECT DISTINCT SUBSTR(snap_time, 1, 8) as dt FROM {table}"
|
||||
date_params: Tuple[Any, ...] = ()
|
||||
if source_filter and "source" in cols:
|
||||
@@ -886,8 +947,33 @@ def recommend_orderbook_parameters(
|
||||
trades.append(ti)
|
||||
|
||||
if len(trades) < 3:
|
||||
lg.warning("⚠️ [%s] 호가 연제 가능한 실제 매수 건수(%s건)가 부족하여 최적화 생략.", strat_upper, len(trades))
|
||||
return {"ok": False, "reason": "not_enough_trades", "trade_count": len(trades)}
|
||||
n_raw = len(raw_fills) if raw_fills else 0
|
||||
lg.warning(
|
||||
"⚠️ [%s] 호가 연제 가능한 실제 매수 건수(%s건, 체결원본=%s)가 부족하여 최적화 생략.",
|
||||
strat_upper, len(trades), n_raw,
|
||||
)
|
||||
return {
|
||||
"ok": False,
|
||||
"reason": "not_enough_trades",
|
||||
"trade_count": len(trades),
|
||||
"fill_count": n_raw,
|
||||
}
|
||||
|
||||
# 연동된 진입 스냅 벤더 요약 (후처리 원인파악)
|
||||
try:
|
||||
from collections import Counter as _Ctr
|
||||
|
||||
_src_ctr = _Ctr()
|
||||
for _ti in trades:
|
||||
_es = _ti.entry_snaps[-1] if _ti.entry_snaps else None
|
||||
_src_ctr[str((_es.source if _es else "") or "?").strip().lower() or "?"] += 1
|
||||
lg.info(
|
||||
"🔎 [호가후처리] 연동체결=%d건 | 진입스냅벤더 %s",
|
||||
len(trades),
|
||||
" ".join(f"{k}={v}" for k, v in _src_ctr.most_common()),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
orig_stats = _ob_orig_stats(trades)
|
||||
orig_cnt = int(orig_stats["count"])
|
||||
|
||||
Reference in New Issue
Block a user