Fix run_session_now: log all exceptions, never silently drop errors

- First log call moved to very top of run_session_now() so user always
  sees the task started, even if subsequent DB/engine calls fail
- Entire function body wrapped in try/except: errors go to scheduler log
  instead of vanishing in asyncio's unhandled-exception machinery
- Endpoint wraps create_task() in a logging shim (_task()) for the same reason
- 'already ran' path now logs a visible warning instead of silent return

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main
I Luk Kim 4 months ago
parent 407238e5d5
commit 84e80fca75

@ -489,59 +489,71 @@ class ORBAutoScheduler:
After detection, injects the session's remaining today-events into the After detection, injects the session's remaining today-events into the
active schedule so breakout checks, stop checks, and EOD exit still fire. active schedule so breakout checks, stop checks, and EOD exit still fire.
Returns a summary dict or {"error": "<reason>"} on failure. All exceptions are caught and logged so the fire-and-forget task never
silently vanishes.
""" """
from apps.orb_trader.state import ORBStateManager # Log immediately so the user knows the task started — even before any
# DB / engine work that might fail.
self._log(f"🔄 지금 시작 요청: {session_name}")
now_et = self._now_et() try:
today = now_et.date() from apps.orb_trader.state import ORBStateManager
date_str = today.isoformat()
state_mgr = ORBStateManager(self._db_path) now_et = self._now_et()
session = state_mgr.get_session(session_name) today = now_et.date()
if session is None: date_str = today.isoformat()
return {"error": f"Session '{session_name}' not found"}
state_mgr = ORBStateManager(self._db_path)
# Already ran if daily_state.phase is set (engine wrote it during orb_detect) session = state_mgr.get_session(session_name)
daily = state_mgr.get_daily_state(session.session_id, date_str) if session is None:
if daily.phase: self._log(f" ❌ 세션 '{session_name}' 을 DB에서 찾을 수 없음")
return {"error": f"Session '{session_name}' already ran today (phase={daily.phase})"} return {"error": f"Session '{session_name}' not found"}
self._log(f"🔄 지금 시작: {session_name} — ORB 감지 실행 중 (현재 시세 기준)...") # Already ran if daily_state.phase is beyond idle
daily = state_mgr.get_daily_state(session.session_id, date_str)
# Phase 1: Run ORB detection (fetches historical bars for ORB window) if daily.phase not in ("idle", "", None):
await self._run_trading("orb_detect", [session_name], date_str) self._log(f"{session_name}: 오늘 이미 실행됨 (phase={daily.phase})")
return {"error": f"already ran today (phase={daily.phase})"}
# Phase 2: Immediately check for breakouts using current snapshot prices.
# This is the "지금 시작" core — regardless of whether scheduled breakout self._log(f" ORB 감지 실행 중 (현재 시세 기준)...")
# windows have already passed, we check current price right now.
self._log(f"🔍 {session_name}: 현재 가격으로 브레이크아웃 즉시 체크...") # Phase 1: ORB detection — fetches historical bars for the ORB window
await self._run_trading("breakout", [session_name], date_str) await self._run_trading("orb_detect", [session_name], date_str)
# Phase 3: Inject remaining future events (stop checks + EOD exit only). # Phase 2: Immediate breakout check using current snapshot prices.
# Skip all breakout events — we just ran the manual check above. # This is the core of "지금 시작" — the scheduled breakout windows have
params = _load_session_params(self._db_path, session_name) # already passed, so we check once manually right now.
all_events = build_schedule(today, **params) self._log(f" 현재 가격으로 브레이크아웃 즉시 체크...")
for ev in all_events: await self._run_trading("breakout", [session_name], date_str)
ev["session"] = session_name
ev["name"] = f"{session_name}:{ev['name']}" # Phase 3: Inject remaining future events (stop checks + EOD exit only).
# Mark all scheduled breakout events as done — we already ran one above.
existing_names = {e["name"] for e in self._today_schedule} params = _load_session_params(self._db_path, session_name)
injected = 0 all_events = build_schedule(today, **params)
for ev in all_events: for ev in all_events:
if ev["kind"] == "breakout": ev["session"] = session_name
# Already handled by the manual check above ev["name"] = f"{session_name}:{ev['name']}"
self._completed.add(ev["name"])
elif ev["et_dt"] <= now_et: existing_names = {e["name"] for e in self._today_schedule}
self._completed.add(ev["name"]) injected = 0
elif ev["name"] not in existing_names: for ev in all_events:
self._today_schedule.append(ev) if ev["kind"] == "breakout":
injected += 1 self._completed.add(ev["name"])
elif ev["et_dt"] <= now_et:
self._today_schedule.sort(key=lambda e: (e["et_dt"], e.get("session", ""))) self._completed.add(ev["name"])
self._log(f"{session_name}: {injected} 이벤트 추가됨 (스톱/EOD 일정)") elif ev["name"] not in existing_names:
self._today_schedule.append(ev)
return {"session": session_name, "injected": injected} injected += 1
self._today_schedule.sort(key=lambda e: (e["et_dt"], e.get("session", "")))
self._log(f" ✓ 완료 — 스톱/EOD 이벤트 {injected}개 추가됨")
return {"session": session_name, "injected": injected}
except Exception as exc:
tb = traceback.format_exc()
self._log(f" ❌ 지금 시작 오류: {exc}")
log.error("run_session_now error: %s\n%s", exc, tb)
return {"error": str(exc)}
# ── Main scheduler loop ──────────────────────────────────────────────────── # ── Main scheduler loop ────────────────────────────────────────────────────

@ -169,8 +169,17 @@ async def run_session_today(session_id: str) -> dict[str, Any]:
detail="스케줄러가 실행 중이 아닙니다. 먼저 자동 스케줄러를 시작하세요.", detail="스케줄러가 실행 중이 아닙니다. 먼저 자동 스케줄러를 시작하세요.",
) )
# Fire-and-forget — detection takes ~12 min; we return immediately # Fire-and-forget — detection takes ~12 min; we return immediately.
_asyncio.create_task(orb_auto_scheduler.run_session_now(session.session_name)) # Wrap in a logging shim so unhandled exceptions surface in the scheduler log,
# not silently in Python's asyncio warning machinery.
async def _task() -> None:
try:
await orb_auto_scheduler.run_session_now(session.session_name)
except Exception as exc:
import traceback as _tb
orb_auto_scheduler._log(f"❌ run_today 작업 예외: {exc}\n{_tb.format_exc()}")
_asyncio.create_task(_task())
return { return {
"session_id": session.session_id, "session_id": session.session_id,

Loading…
Cancel
Save