refactor: enhance Optuna backtesting framework, optimize orderbook filtering, and update database management utilities.

This commit is contained in:
Your Name
2026-08-12 10:19:19 +09:00
parent cb7e5037a0
commit c6bd62a25f
218 changed files with 31613 additions and 759 deletions

View File

@@ -37,13 +37,18 @@ def _env_flag(key: str, default: bool) -> bool:
def live_universe_slot_align_enabled(strategy_id: str) -> bool:
"""실매 후보 ∩ history 슬롯 필터.
기본 **False** — history 지연/축소 시 전량탈락(universe_wipe) 방지.
백테 슬롯 정합이 필요하면 ``LIVE_UNIVERSE_SLOT_ALIGN=true`` 또는 전략별 키로 ON.
"""
sid = (strategy_id or "").upper()
per_key = f"{sid}_LIVE_UNIVERSE_SLOT_ALIGN"
if get_env_from_db(per_key, "") not in (None, "", "None"):
return _env_flag(per_key, True)
return _env_flag(per_key, False)
if sid in _UNIVERSE_SLOT_SKIP_DEFAULT:
return False
return _env_flag("LIVE_UNIVERSE_SLOT_ALIGN", True)
return _env_flag("LIVE_UNIVERSE_SLOT_ALIGN", False)
def resolve_live_universe_history_source(
@@ -144,6 +149,59 @@ def resolve_live_min_invest_ratio(strategy_id: str) -> float:
return min_invest_ratio_of_slot({}, strategy=portfolio_strategy_key(strategy_id))
def _history_snapshot_event_time(
db: Any,
strategy_id: str,
at_time: str,
history_source: str,
) -> Optional[Any]:
"""``at_time`` 이전 최신 ``event_time`` (없으면 None)."""
conn = getattr(db, "conn", None)
if conn is None:
return None
table_fn = getattr(db, "_universe_history_table", None)
if callable(table_fn):
try:
table = table_fn(history_source)
except Exception:
table = None
else:
table = None
if not table:
from kis_trader.backtest.universe_history_source import history_table_for_source
table = history_table_for_source(history_source)
try:
row = conn.execute(
f"""
SELECT MAX(event_time) AS et
FROM {table}
WHERE strategy_id=%s AND event_time <= %s
""",
(strategy_id, at_time),
).fetchone()
return (row or {}).get("et") if row else None
except Exception:
return None
def _event_time_ymd(et: Any) -> str:
if et is None:
return ""
if hasattr(et, "strftime"):
try:
return et.strftime("%Y-%m-%d")
except Exception:
pass
s = str(et).strip()
if len(s) >= 10 and s[4] == "-" and s[7] == "-":
return s[:10]
# YYYYMMDDHHMM / YYYYMMDD...
digits = "".join(ch for ch in s if ch.isdigit())
if len(digits) >= 8:
return f"{digits[:4]}-{digits[4:6]}-{digits[6:8]}"
return ""
def history_universe_codes_at(
db: Any,
strategy_id: str,
@@ -157,6 +215,9 @@ def history_universe_codes_at(
- kiwoom → ``target_candidates_history``
- ls → ``ls_candidates_history``
스냅샷 없으면 ``None`` (필터 생략 = 실시간 후보 유지).
**당일 스냅샷만** 사용한다. 주말 재시작·키움 매니저 다운 뒤 남은
며칠 전 history 와 sticky 후보를 교집합하면 전원 탈락(11→0) 한다.
"""
when = when or dt.now()
at_time = when.strftime("%Y-%m-%d %H:%M:%S")
@@ -170,6 +231,14 @@ def history_universe_codes_at(
src = resolve_live_universe_history_source(sid, universe_source=None)
if src not in ("ls", "kiwoom"):
src = "kiwoom"
# 당일(캘린더) 스냅샷만 슬롯정합에 쓴다 — 낡은 history 전멸 방지
et = _history_snapshot_event_time(db, sid, at_time, src)
if not et:
return None
if _event_time_ymd(et) != when.strftime("%Y-%m-%d"):
return None
getter = getattr(db, "get_universe_at", None)
if getter is None:
return None