한투 호가 = 2번째 앱키 전용 키 없거나 start 실패 시 메인에 H0STASP0 안 붙임. 운영설정 WS_ORDERBOOK_SAVE_KIS 빨간 danger. LS RAM 합집합 후보∪보유∪영구∪grace. sync_targets와 split reconcile 둘 다. 틱 DB 영구 게이트는 그대로. 분봉 쓰레기 → 다음 소스 봉 통째 그 분 틱 0건이거나 전부 봉끝 대비 LIVE_FEED_FALLBACK_MAX_AGE_SEC 초과면 구멍. 메인 WS → 2차 → LS → REST → rollup. CANDLE_GARBAGE_FALLBACK 기본 true. 파일: feed_fallback.py(신규), ws_manager.py, kis_ws.py, candle_series.py, bt_candle_source.py, live_config_schema.py, database.py, 스모크, MD 2개. 같은 ws_manager/database/kis_ws/live_config에는 직전 커밋 이후 쌓여 있던 시세 폴백·ENV 키 정리도 같이 들어갔습니다. 파일 단위로 나눌 수 없어서입니다.
138 lines
3.5 KiB
Python
138 lines
3.5 KiB
Python
"""Optuna 후처리 진행 한 줄 로그 — 웹 프로그레스바 파서용.
|
|
|
|
실매 엔진과 무관. 형식:
|
|
OPTUNA_POST_PROGRESS pct=.. step=.. total=.. stage=.. axis=a/b 설명
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import threading
|
|
from typing import Any, Optional
|
|
|
|
_tls = threading.local()
|
|
|
|
|
|
def _ctx() -> dict:
|
|
c = getattr(_tls, "ctx", None)
|
|
if not isinstance(c, dict):
|
|
c = {
|
|
"log": None,
|
|
"step": 1,
|
|
"total": 1,
|
|
"stage": "idle",
|
|
"n_axes": 1,
|
|
"axis_i": 0,
|
|
"axis_trials": 0,
|
|
}
|
|
_tls.ctx = c
|
|
return c
|
|
|
|
|
|
def begin_job(log: Any, total: int) -> None:
|
|
c = _ctx()
|
|
c["log"] = log
|
|
c["total"] = max(1, int(total or 1))
|
|
c["step"] = 0
|
|
c["stage"] = "start"
|
|
c["n_axes"] = 1
|
|
c["axis_i"] = 0
|
|
c["axis_trials"] = 0
|
|
emit(stage="start", detail="후처리 시작")
|
|
|
|
|
|
def begin_unit(step: int, stage: str, detail: str = "") -> None:
|
|
c = _ctx()
|
|
c["step"] = max(1, int(step))
|
|
c["stage"] = str(stage or "")
|
|
c["n_axes"] = 1
|
|
c["axis_i"] = 0
|
|
c["axis_trials"] = 0
|
|
emit(stage=stage, detail=detail or stage)
|
|
|
|
|
|
def next_unit(stage: str, detail: str = "") -> int:
|
|
c = _ctx()
|
|
step = int(c.get("step") or 0) + 1
|
|
begin_unit(step, stage, detail)
|
|
return step
|
|
|
|
|
|
def set_ob_axes(n_axes: int, axis_trials: int) -> None:
|
|
c = _ctx()
|
|
c["n_axes"] = max(1, int(n_axes or 1))
|
|
c["axis_trials"] = max(0, int(axis_trials or 0))
|
|
|
|
|
|
def begin_ob_axis(axis_name: str, axis_i: int, axis_trials: Optional[int] = None) -> None:
|
|
c = _ctx()
|
|
c["axis_i"] = max(0, int(axis_i))
|
|
if axis_trials is not None:
|
|
c["axis_trials"] = max(0, int(axis_trials))
|
|
emit(
|
|
stage=str(c.get("stage") or "ob"),
|
|
detail=str(axis_name or ""),
|
|
axis_done=0,
|
|
axis_total=int(c.get("axis_trials") or 0),
|
|
)
|
|
|
|
|
|
def on_ob_axis_trial(done: int, total: int, axis_name: str) -> None:
|
|
emit(
|
|
stage=str(_ctx().get("stage") or "ob"),
|
|
detail=str(axis_name or ""),
|
|
axis_done=int(done),
|
|
axis_total=int(total),
|
|
)
|
|
|
|
|
|
def finish_job(log: Optional[Any] = None) -> None:
|
|
c = _ctx()
|
|
if log is not None:
|
|
c["log"] = log
|
|
c["step"] = int(c.get("total") or 1)
|
|
emit(stage="done", detail="", done=True)
|
|
|
|
|
|
def emit(
|
|
*,
|
|
stage: str,
|
|
detail: str = "",
|
|
axis_done: int = 0,
|
|
axis_total: int = 0,
|
|
done: bool = False,
|
|
) -> None:
|
|
c = _ctx()
|
|
log = c.get("log")
|
|
if log is None:
|
|
return
|
|
step = max(0, int(c.get("step") or 0))
|
|
total = max(1, int(c.get("total") or 1))
|
|
n_axes = max(1, int(c.get("n_axes") or 1))
|
|
axis_i = max(0, int(c.get("axis_i") or 0))
|
|
ax_tot = int(axis_total or c.get("axis_trials") or 0)
|
|
ax_done = int(axis_done or 0)
|
|
if done:
|
|
pct = 100.0
|
|
else:
|
|
inner = 0.0
|
|
if ax_tot > 0:
|
|
inner = (axis_i + min(1.0, ax_done / float(ax_tot))) / float(n_axes)
|
|
elif n_axes > 1:
|
|
inner = axis_i / float(n_axes)
|
|
frac = (max(0, step - 1) + min(1.0, inner)) / float(total)
|
|
pct = round(min(99.9, max(0.0, 100.0 * frac)), 1)
|
|
bits = [
|
|
f"OPTUNA_POST_PROGRESS pct={pct}",
|
|
f"step={max(1, step) if step else 0}",
|
|
f"total={total}",
|
|
f"stage={stage}",
|
|
f"axis={ax_done}/{ax_tot}",
|
|
]
|
|
if detail:
|
|
bits.append(str(detail).replace("\n", " ")[:80])
|
|
if done:
|
|
bits.append("상세가능")
|
|
try:
|
|
log.info("%s", " ".join(bits))
|
|
except Exception:
|
|
pass
|