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:
@@ -18,6 +18,31 @@ from kis_trader.utils.env import get_env_float
|
||||
_stale_rest_lock = threading.Lock()
|
||||
_stale_rest_ts: dict = {}
|
||||
_stale_log_ts: dict = {}
|
||||
# WS/REST 성공 직후 가격 — REST 쿨다운(기본 30s) 동안 재사용 (매도 공백 방지)
|
||||
_last_good_lock = threading.Lock()
|
||||
_last_good_px: dict = {}
|
||||
|
||||
|
||||
def _remember_last_good(code: str, px: float, src: str) -> None:
|
||||
code = str(code or "").strip()
|
||||
if not code or px <= 0:
|
||||
return
|
||||
with _last_good_lock:
|
||||
_last_good_px[code] = (float(px), str(src or "WS"), time.time())
|
||||
|
||||
|
||||
def _last_good_price(code: str, max_age_sec: float) -> Tuple[float, str]:
|
||||
code = str(code or "").strip()
|
||||
if not code or max_age_sec <= 0:
|
||||
return 0.0, ""
|
||||
with _last_good_lock:
|
||||
rec = _last_good_px.get(code)
|
||||
if not rec:
|
||||
return 0.0, ""
|
||||
px, src, ts = rec
|
||||
if (time.time() - float(ts)) > float(max_age_sec):
|
||||
return 0.0, ""
|
||||
return float(px), str(src or "")
|
||||
|
||||
|
||||
def _stck_prpr(raw: Any) -> float:
|
||||
@@ -63,6 +88,43 @@ def _ws_chain_price(ws: Any, code: str) -> Tuple[float, str]:
|
||||
return 0.0, ""
|
||||
|
||||
|
||||
def _quote_packet_raw(wsd: Any) -> str:
|
||||
"""체결시각 원문. FID20 동결 RAM 을 매도 last-RAM 으로 쓰지 않기 위함."""
|
||||
if not isinstance(wsd, dict):
|
||||
return ""
|
||||
for k in (
|
||||
"kis_cntg_hour_raw", "chetime", "tick_time", "kiwoom_fid20",
|
||||
"stck_cntg_hour",
|
||||
):
|
||||
v = str(wsd.get(k) or "").strip()
|
||||
if v:
|
||||
return v
|
||||
return ""
|
||||
|
||||
|
||||
def _last_ram_sell_ok(wsd: Any, max_age_sec: float) -> bool:
|
||||
"""마지막 RAM: wall 나이·패킷 나이 둘 다 max 이내. 동결 FID20 거부."""
|
||||
if max_age_sec <= 0 or not isinstance(wsd, dict):
|
||||
return False
|
||||
try:
|
||||
wall = float(wsd.get("_age_ms") or 0) / 1000.0
|
||||
except (TypeError, ValueError):
|
||||
wall = 0.0
|
||||
if wall > float(max_age_sec):
|
||||
return False
|
||||
raw = _quote_packet_raw(wsd)
|
||||
if not raw:
|
||||
return True
|
||||
try:
|
||||
from kis_trader.engine.feed_fallback import packet_lag_seconds
|
||||
lag = packet_lag_seconds(raw)
|
||||
except Exception:
|
||||
return True
|
||||
if lag is None:
|
||||
return True
|
||||
return float(lag) <= float(max_age_sec)
|
||||
|
||||
|
||||
def _ws_last_and_age(ws: Any, code: str) -> Tuple[float, float]:
|
||||
"""마지막 RAM 체결가 + 나이(초). 캐시 없으면 (0, inf). EOD 전용."""
|
||||
getter = getattr(ws, "get_price", None)
|
||||
@@ -153,6 +215,7 @@ def resolve_live_sell_price(
|
||||
if is_eod:
|
||||
ws_px, _age = _ws_last_and_age(ws, code)
|
||||
if ws_px > 0:
|
||||
_remember_last_good(code, ws_px, "WS")
|
||||
return ws_px, "WS"
|
||||
if stale_sec > 0 and _stale_rest_allowed(code, cooldown):
|
||||
rest_px = _kiwoom_rest_once(ws, code)
|
||||
@@ -171,11 +234,52 @@ def resolve_live_sell_price(
|
||||
|
||||
chain_px, vendor = _ws_chain_price(ws, code)
|
||||
if chain_px > 0:
|
||||
_remember_last_good(code, chain_px, vendor or "WS")
|
||||
return chain_px, vendor or "WS"
|
||||
|
||||
last_max = float(get_env_float("SELL_WS_LAST_RAM_MAX_AGE_SEC", 30.0) or 0.0)
|
||||
if last_max > 0:
|
||||
last_px, last_age = _ws_last_and_age(ws, code)
|
||||
getter = getattr(ws, "get_price", None)
|
||||
wsd_last = None
|
||||
if callable(getter):
|
||||
try:
|
||||
wsd_last = getter(code, max_age_sec=None)
|
||||
except TypeError:
|
||||
wsd_last = None
|
||||
except Exception:
|
||||
wsd_last = None
|
||||
if last_px > 0 and _last_ram_sell_ok(wsd_last, last_max):
|
||||
lab = ""
|
||||
if isinstance(wsd_last, dict):
|
||||
lab = str(wsd_last.get("_feed_vendor") or "").strip()
|
||||
_remember_last_good(code, last_px, lab or "WS_last")
|
||||
_log_throttled(
|
||||
logger,
|
||||
"last_ram:" + code,
|
||||
"📌 [매도시세] %s 3초체인 실패 → last-RAM %.0f (age=%.1fs)",
|
||||
code,
|
||||
last_px,
|
||||
last_age if last_age != float("inf") else -1.0,
|
||||
)
|
||||
return last_px, "WS_last"
|
||||
|
||||
cached_px, cached_src = _last_good_price(code, cooldown)
|
||||
if cached_px > 0:
|
||||
_log_throttled(
|
||||
logger,
|
||||
"last_good:" + code,
|
||||
"📌 [매도시세] %s WS체인 실패 → 직전가 유지(%s) %.0f",
|
||||
code,
|
||||
cached_src,
|
||||
cached_px,
|
||||
)
|
||||
return cached_px, cached_src + "_cached"
|
||||
|
||||
if stale_sec > 0 and _stale_rest_allowed(code, cooldown):
|
||||
rest_px = _kiwoom_rest_once(ws, code)
|
||||
if rest_px > 0:
|
||||
_remember_last_good(code, rest_px, "kiwoom_rest")
|
||||
_log_throttled(
|
||||
logger,
|
||||
"stale_rest:" + code,
|
||||
|
||||
Reference in New Issue
Block a user