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:
Your Name
2026-08-21 19:05:23 +09:00
parent 0ecac7cb95
commit 0780b2cdd0
76 changed files with 4648 additions and 516 deletions

View File

@@ -40,20 +40,44 @@ def _live_feed_providers() -> Tuple[str, str]:
class FeedPrefixLoggerAdapter(logging.LoggerAdapter):
"""탈락/스캔 로그(🔍 [) 앞에 T:시세|O:호가 provider 접두어를 붙인다.
"""탈락/스캔 로그(🔍 [) 앞에 T:설정|O:설정|R:실제읽기 접두어를 붙인다.
예: 🔍 [탈락-RSI] → 🔍 [T:kiwoom|O:kiwoom|탈락-RSI]
(피드 출처 디버깅용 · 매매 수치 아님)
예: 🔍 [탈락-RSI] → 🔍 [T:kis|O:kiwoom|R:kis(1차)|탈락-RSI]
T/O = LIVE_*_PROVIDER(DB). R = get_tick_feed_label(code) — 직전 get_price 읽기.
extra={'scan_code': code} 로 R: 활성화 (_scan_log 헬퍼 사용).
TRIGGER_FEED_DETAIL_LOG(기본 true) 이면 틱가·틱타임·호가·1/2/3차 체인 꼬리 추가.
"""
def process(self, msg, kwargs):
if isinstance(msg, str) and "🔍 [" in msg and "시세:" not in msg and "LIVE_TICK_PROVIDER" in self.extra:
try:
# 이미 T:|O: 접두가 있으면 중복 삽입 금지 (매수체크 로그 등)
if "🔍 [T:" not in msg[:24]:
if "🔍 [T:" not in msg[:32]:
tick_p, ob_p = _live_feed_providers()
if tick_p or ob_p:
read_lab = ""
extra = kwargs.get("extra") or {}
scan_code = str(extra.get("scan_code") or "").strip()
ws = self.extra.get("ws")
if scan_code and ws is not None and hasattr(ws, "get_tick_feed_label"):
try:
read_lab = str(ws.get_tick_feed_label(scan_code) or "").strip()
except Exception:
read_lab = ""
if read_lab:
msg = msg.replace(
"🔍 [",
f"🔍 [T:{tick_p}|O:{ob_p}|R:{read_lab}|",
1,
)
elif tick_p or ob_p:
msg = msg.replace("🔍 [", f"🔍 [T:{tick_p}|O:{ob_p}|", 1)
if scan_code and ws is not None and hasattr(ws, "get_trigger_feed_trace"):
try:
detail = str(ws.get_trigger_feed_trace(scan_code) or "").strip()
except Exception:
detail = ""
if detail and "틱1차설정=" not in msg:
msg = f"{msg} · {detail}"
except Exception:
pass
return msg, kwargs
@@ -124,7 +148,10 @@ class BaseStrategy(ABC, threading.Thread):
self.ls_condition_mgr = ls_condition_mgr
self.market_guard = market_guard # MarketGuard (선택, None 이면 가드 없음)
base_logger = get_logger(f"kis_trader.strategy.{self.strategy_id}")
self.logger = FeedPrefixLoggerAdapter(base_logger, {"db": self.db, "LIVE_TICK_PROVIDER": True})
self.logger = FeedPrefixLoggerAdapter(
base_logger,
{"db": self.db, "ws": self.ws, "LIVE_TICK_PROVIDER": True},
)
# MarketGuard PANIC 차단 로그 스팸 방지용 (분당 1회)
self._panic_log_ts: float = 0.0
@@ -605,6 +632,21 @@ class BaseStrategy(ABC, threading.Thread):
except Exception:
return None
def _scan_log(
self,
level: str,
code: Optional[str],
msg: str,
*args: Any,
**kwargs: Any,
) -> None:
"""🔍 스캔/탈락 로그 — FeedPrefixLoggerAdapter 가 T/O/R 접두 부착."""
extra = dict(kwargs.pop("extra", None) or {})
c = str(code or "").strip()
if c:
extra["scan_code"] = c
getattr(self.logger, level)(msg, *args, extra=extra, **kwargs)
def _resolve_sell_price(self, code: str, *, is_eod: bool, buy_price: float) -> float:
"""실매 매도 현재가 — 마지막 WS를 TTL로 버리지 않음. EOD는 매수가 폴백."""
from kis_trader.engine.live_sell_price import resolve_live_sell_price
@@ -1152,7 +1194,7 @@ class BaseStrategy(ABC, threading.Thread):
t_pre0 = time.perf_counter()
guard = self._live_portfolio_entry_guard(code, max_stocks)
if guard:
self.logger.info("🔍 [%s] %s(%s)", guard, name, code)
self._scan_log("info", code, "🔍 [%s] %s(%s)", guard, name, code)
if prof_scan:
dt = (time.perf_counter() - t_pre0) * 1000.0
scan_pre_ms += dt