diff --git a/apps/pipeline/filing_poller/main.py b/apps/pipeline/filing_poller/main.py index e2c1916..4420e9a 100644 --- a/apps/pipeline/filing_poller/main.py +++ b/apps/pipeline/filing_poller/main.py @@ -116,7 +116,7 @@ async def poll_filings( ) except Exception as exc: - logger.error("poll_error", ticker=ticker, error=str(exc)) + logger.error("poll_error", ticker=ticker, error=str(exc) or repr(exc), exc_type=type(exc).__name__) stats["errors"] += 1 # Update job record diff --git a/apps/web/paper_trading_service.py b/apps/web/paper_trading_service.py index d8af049..1c08f3b 100644 --- a/apps/web/paper_trading_service.py +++ b/apps/web/paper_trading_service.py @@ -538,7 +538,7 @@ class AutoScheduler: # ── Catch-up on startup ─────────────────────────────────────────────────── async def _run_catchup(self) -> None: - """Run missed pipeline steps on startup. Mirrors auto.py _run_catchup().""" + """Run missed pipeline steps and paper trading phases on startup.""" now_et = self._now_et() today = now_et.date() catchup_items: list[tuple[str, list[list[str]]]] = [] @@ -553,14 +553,34 @@ class AutoScheduler: if pre_market_et < now_et: catchup_items.append((f"Pre-market pipeline ({today})", _PIPELINE_CMDS)) - if not catchup_items: - return - - self._log("━━━ Catch-up: 놓친 파이프라인 실행 ━━━") - for label, cmds in catchup_items: - self._log(f"▶ {label}") - await self._run_pipeline(cmds) - self._log("━━━ Catch-up 완료 ━━━") + if catchup_items: + self._log("━━━ Catch-up: 놓친 파이프라인 실행 ━━━") + for label, cmds in catchup_items: + self._log(f"▶ {label}") + await self._run_pipeline(cmds) + self._log("━━━ Catch-up 완료 ━━━") + + # Paper trading phase catchup: if run_open was missed but market is still open, run now. + # This fires when the server starts after 9:35 AM ET — the scheduler would otherwise + # silently skip run_open because its scheduled time has already passed. + if self._is_trading_day(today) and not self._dry_run: + run_open_et = self._et_dt_for(today, _SCHEDULE[1]) # run_open 9:35 + market_close_et = now_et.replace(hour=16, minute=0, second=0, microsecond=0) + if run_open_et <= now_et < market_close_et: + sessions = self._sessions or self._get_active_sessions() + if sessions and self._db_path: + from apps.paper_trader.state import StateManager + state_mgr = StateManager(self._db_path) + already_ran = any( + (sess := state_mgr.get_session(s)) is not None + and state_mgr.is_phase_processed(sess.session_id, today, "next_open") + for s in sessions + ) + if not already_ran: + self._log("━━━ Catch-up: run_open 누락 (9:35 ET 이후 시작) — 지금 실행 ━━━") + await self._run_trading("run-open", sessions) + self._completed.add("run_open") + self._log("━━━ Catch-up run_open 완료 ━━━") # ── Main scheduler loop ───────────────────────────────────────────────────