""" daily_launcher.py — KRX 거래일 자동 런처 [동작 흐름] 1. 오늘이 KRX 거래일인지 확인 2. 휴장일이면: 텔레그램 알림 후 종료 3. 거래일이면: LSTM 워밍업 → main_server.py 시작 4. 봇은 15:35에 스스로 EOD 셧다운 [설치: Windows 작업 스케줄러] 트리거: 매일 08:30 (주말 포함 — 봇이 내부에서 휴장일 체크) 동작: python C:\\path\\to\\web-ai\\daily_launcher.py 시작 위치: C:\\path\\to\\web-ai 실행 계정: 현재 사용자 (로그인 여부 무관 실행 권장) [수동 실행] python daily_launcher.py """ import sys import time import subprocess import datetime import logging from pathlib import Path from zoneinfo import ZoneInfo ROOT = Path(__file__).parent LOG_FILE = ROOT / "daily_launcher.log" KST = ZoneInfo("Asia/Seoul") logging.basicConfig( level=logging.INFO, format="%(asctime)s [Launcher] %(message)s", datefmt="%Y-%m-%d %H:%M:%S", handlers=[ logging.FileHandler(LOG_FILE, encoding="utf-8"), logging.StreamHandler(sys.stdout), ], ) log = logging.getLogger("daily_launcher") def setup_path(): if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) def send_notify(msg: str): """텔레그램 알림 발송 (실패해도 런처 계속 진행)""" try: from modules.services.telegram import TelegramMessenger TelegramMessenger().send_message(msg) except Exception as e: log.warning(f"텔레그램 알림 실패: {e}") def clear_eod_marker(): """전일 EOD 마커 파일 삭제 (새 거래일 시작)""" eod_file = ROOT / "data" / ".eod_date" if not eod_file.exists(): return try: prev = datetime.date.fromisoformat(eod_file.read_text().strip()) today = datetime.datetime.now(KST).date() if prev < today: eod_file.unlink() log.info(f"전일({prev}) EOD 마커 삭제 완료") except Exception: eod_file.unlink(missing_ok=True) def wait_until_warmup_time(cal) -> None: """ 워밍업 시작 시각까지 대기 - 장 시작 30분 전이면 즉시 워밍업 - 그보다 일찍 실행되면 '장 시작 30분 전'까지 대기 """ secs = cal.seconds_to_open() if secs <= 0: log.info("이미 장 중 — 즉시 워밍업 시작") return warmup_start_secs = max(0, secs - 30 * 60) # 장 시작 30분 전 if warmup_start_secs > 0: warmup_at = datetime.datetime.now(KST) + datetime.timedelta(seconds=warmup_start_secs) log.info(f"워밍업 대기 중 ({warmup_start_secs/60:.0f}분 후 {warmup_at.strftime('%H:%M')} 시작)") time.sleep(warmup_start_secs) else: log.info(f"장 시작 {secs/60:.0f}분 전 — 즉시 워밍업") def run_warmup_and_server() -> int: """ warmup_and_restart.py 실행 - warmup: LSTM 사전학습 - 이후 main_server.py를 새 콘솔에서 자동 시작 """ log.info("LSTM 워밍업 시작...") result = subprocess.run( [sys.executable, "warmup_and_restart.py"], cwd=str(ROOT), ) return result.returncode def main(): setup_path() from modules.utils.market_calendar import KRXCalendar cal = KRXCalendar() today = datetime.datetime.now(KST).date() log.info(f"실행 날짜: {today} | 시장 상태: {cal.status_summary()}") # ── 1. 휴장일 체크 ──────────────────────────────────────────────────────── if not cal.is_trading_day(today): try: nxt = cal.next_trading_open() next_str = nxt.strftime("%m/%d(%a) %H:%M") except Exception: next_str = "미정" msg = ( f"[자동매매] {today.strftime('%m/%d(%a)')} 휴장일\n" f"다음 거래일: {next_str} KST 자동 시작" ) log.info(f"휴장일 — 봇 시작 안 함 (다음: {next_str})") send_notify(msg) return # ── 2. EOD 마커 초기화 ──────────────────────────────────────────────────── clear_eod_marker() # ── 3. 워밍업 시각까지 대기 ─────────────────────────────────────────────── wait_until_warmup_time(cal) # ── 4. 거래일 시작 알림 ─────────────────────────────────────────────────── log.info(f"거래일 확인 — 워밍업 및 봇 시작 ({datetime.datetime.now(KST).strftime('%H:%M')})") send_notify( f"[자동매매] {today.strftime('%m/%d(%a)')} 거래일 시작\n" f"LSTM 워밍업 중..." ) # ── 5. 워밍업 + 서버 시작 ───────────────────────────────────────────────── rc = run_warmup_and_server() if rc != 0: log.error(f"워밍업 실패 (exit={rc}) — 수동 확인 필요") send_notify(f"[Bot] 워밍업 실패! (exit={rc})\n수동으로 확인해 주세요.") return log.info("워밍업 완료. main_server.py가 백그라운드에서 실행 중.") log.info("봇은 15:35에 스스로 EOD 셧다운합니다.") if __name__ == "__main__": main()