diff --git a/score-engine/main.py b/score-engine/main.py index ff09f7d..737bf00 100644 --- a/score-engine/main.py +++ b/score-engine/main.py @@ -2878,9 +2878,17 @@ async def calculate_daily_scores(as_of: date | None = None, notify: bool = False # ── 정기 브리핑 ─────────────────────────────────────────── +_last_briefing_sent: datetime | None = None # 중복/폭주 차단 쓰로틀 + async def send_briefing(): - """정기 시황 브리핑 — 종목당 1카드(매매가/포지션/재무/근거 통합)""" + """정기 시황 브리핑 — 종목당 1카드(매매가/포지션/재무/근거 통합). + 30분 내 재호출은 무시 — n8n 중복/재시도 폭주로 같은 브리핑이 여러 건 발송되는 것 방지.""" + global _last_briefing_sent now = datetime.now() + if _last_briefing_sent and (now - _last_briefing_sent).total_seconds() < 1800: + logger.info("briefing.throttled", since=(now - _last_briefing_sent).total_seconds()) + return + _last_briefing_sent = now ta_redis = aioredis.Redis( host=REDIS_HOST, port=6379, password=REDIS_PASSWORD, db=5, decode_responses=True) @@ -3020,6 +3028,11 @@ async def send_briefing(): app = FastAPI(title="종목 점수 엔진") app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]) +async def _daily_score_notify(): + """16:30 정기 일간리포트 — 코루틴 함수로 등록해야 APScheduler가 await함 + (lambda로 감싸면 coroutine never awaited 버그).""" + await calculate_daily_scores(notify=True) + @app.on_event("startup") async def startup(): global pg_pool, redis_cl @@ -3029,7 +3042,7 @@ async def startup(): redis_cl = aioredis.Redis( host=REDIS_HOST, port=6379, password=REDIS_PASSWORD, db=3, decode_responses=True) await init_db() - scheduler.add_job(lambda: calculate_daily_scores(notify=True), "cron", + scheduler.add_job(_daily_score_notify, "cron", day_of_week="mon-fri", hour=16, minute=30, id="daily_score", replace_existing=True) # 텔레그램 정기 알림 하루 2회로 축소 (사용자 요청 2026-06-02): diff --git a/ta-engine/main.py b/ta-engine/main.py index e91ac90..a45a4c2 100644 --- a/ta-engine/main.py +++ b/ta-engine/main.py @@ -783,9 +783,9 @@ async def startup(): redis_cl = aioredis.Redis( host=REDIS_HOST, port=6379, password=REDIS_PASSWORD, db=5, decode_responses=True) await init_db() - scheduler.add_job(lambda: job_analyze(limit=500), "cron", day_of_week="mon-fri", + scheduler.add_job(_ta_intraday, "cron", day_of_week="mon-fri", hour="9-16", minute="*/30", id="ta_30m", replace_existing=True) - scheduler.add_job(lambda: job_analyze(limit=0), "cron", day_of_week="mon-fri", + scheduler.add_job(_ta_close, "cron", day_of_week="mon-fri", hour=16, minute=15, id="ta_close", replace_existing=True) scheduler.start() logger.info("ta-engine.started") @@ -870,6 +870,12 @@ async def buy_candidates(limit: int = Query(default=20)): result.append(d) return result +async def _ta_intraday(): # 코루틴 함수로 등록 (lambda 감싸면 APScheduler가 await 못함) + await job_analyze(limit=500) + +async def _ta_close(): + await job_analyze(limit=0) + @app.post("/analyze/all") async def analyze_all(limit: int = 0): asyncio.create_task(job_analyze(limit=limit))