"""trade-monitor FastAPI entry — lifespan(monitor_loop + heartbeat_loop) + /health.""" from __future__ import annotations import asyncio import logging from contextlib import asynccontextmanager import redis.asyncio as aioredis from fastapi import FastAPI import monitor from config import load_settings from kis_client import KISClient from nas_client import NASClient from _shared.heartbeat import heartbeat_loop, WorkerStats logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s") logger = logging.getLogger(__name__) HEARTBEAT_INTERVAL = 15 # 60초 루프 > TTL 45초 → 독립 15초 발신으로 만료갭 해소 HEARTBEAT_TTL = 45 @asynccontextmanager async def lifespan(app: FastAPI): settings = load_settings() nas = NASClient(settings.nas_base_url, settings.webai_api_key) kis = KISClient(settings.kis_app_key, settings.kis_app_secret, settings.kis_account, settings.kis_is_virtual) state = monitor.MonitorState() stats = WorkerStats() redis = aioredis.from_url(settings.redis_url, decode_responses=False) mon_task = asyncio.create_task( monitor.monitor_loop(nas, kis, state, stats, settings)) hb_task = asyncio.create_task(heartbeat_loop( redis, "trade-monitor", "trader", stats, interval=HEARTBEAT_INTERVAL, ttl=HEARTBEAT_TTL, state_fn=monitor.make_state_fn(state))) logger.info("trade-monitor lifespan 시작") try: yield finally: for t in (mon_task, hb_task): t.cancel() try: await t except asyncio.CancelledError: pass await kis.close() await nas.close() await redis.aclose() logger.info("trade-monitor lifespan 종료") app = FastAPI(lifespan=lifespan) @app.get("/health") def health(): return {"ok": True, "service": "trade-monitor"}