import asyncio import os import subprocess import sys import time from pathlib import Path PROJECT = Path(__file__).resolve().parents[1] PID_FILE = PROJECT / "logs" / "bot.pid" LOG_FILE = PROJECT / "logs" / "bot_stderr.log" def _taskkill(pid: int) -> None: subprocess.run(["taskkill", "/PID", str(pid), "/F"], capture_output=True, text=True) def _find_bot_pids() -> list[int]: command = ( "Get-CimInstance Win32_Process | " "Where-Object { $_.Name -like 'python*' -and " "($_.CommandLine -like '*app/main.py*' " "-or $_.CommandLine -like '*app\\\\main.py*') } | " "Select-Object -ExpandProperty ProcessId" ) result = subprocess.run( ["powershell", "-NoProfile", "-Command", command], capture_output=True, text=True, ) pids: list[int] = [] for line in result.stdout.splitlines(): line = line.strip() if line.isdigit(): pids.append(int(line)) return pids def _kill_existing_bots() -> None: killed: set[int] = set() if PID_FILE.exists(): try: pid = int(PID_FILE.read_text(encoding="utf-8").strip()) _taskkill(pid) killed.add(pid) print(f"PID file process stopped: {pid}") except Exception as exc: print(f"PID file stop skipped: {exc}") PID_FILE.unlink(missing_ok=True) for pid in _find_bot_pids(): if pid in killed: continue _taskkill(pid) killed.add(pid) print(f"Existing bot process stopped: {pid}") if not killed: print("No existing bot process found") def _start_bot() -> int: PROJECT.joinpath("logs").mkdir(exist_ok=True) env = os.environ.copy() env["PYTHONUNBUFFERED"] = "1" creationflags = 0 if hasattr(subprocess, "DETACHED_PROCESS"): creationflags = subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP with open(LOG_FILE, "a", encoding="utf-8") as log: proc = subprocess.Popen( [sys.executable, "-u", "app/main.py"], cwd=PROJECT, creationflags=creationflags, stdout=log, stderr=subprocess.STDOUT, close_fds=True, env=env, ) time.sleep(2) if proc.poll() is not None: raise RuntimeError(f"bot process exited during startup: returncode={proc.returncode}") PID_FILE.write_text(str(proc.pid), encoding="utf-8") return proc.pid async def _notify_start() -> None: sys.path.insert(0, str(PROJECT)) from app.main import load_env load_env() from app.monitor.notifier import send mode = os.getenv("KIS_MOCK", "true") dry = os.getenv("DRY_RUN", "true") label = "[모의투자]" if mode == "true" else "[실거래]" await send(f"{label} 자동매매 봇 시작 (DRY_RUN={dry})") def main() -> int: os.chdir(PROJECT) _kill_existing_bots() pid = _start_bot() print(f"Bot started PID={pid}") asyncio.run(_notify_start()) print("Discord start notification sent") return 0 if __name__ == "__main__": raise SystemExit(main())