diff --git a/README.md b/README.md index 6a55bb8..ea1b770 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ AI에게 맡기지는 않습니다. AI는 장 전/장중/장후 시장을 분석 - DB: SQLite (`data/stockbot.db`) - 알림: Discord Webhook - AI/ML: 시장 분석과 관찰용 점수 기록까지만 사용 +- 스케줄러 작업은 숨김 실행으로 등록하고, 장중 봇 본체는 Windows에서 + `pythonw.exe`를 우선 사용해 콘솔 창 없이 백그라운드로 실행합니다. 운영 모드는 아래처럼 나뉩니다. @@ -79,6 +81,10 @@ powershell -ExecutionPolicy Bypass -File scripts\setup_scheduler.ps1 powershell -ExecutionPolicy Bypass -File scripts\run_training_pipeline.ps1 ``` +운영 중 봇을 직접 켜야 할 때는 `python app/main.py`보다 +`python scripts\start_bot.py`를 사용합니다. 이 경로는 기존 봇을 정리한 뒤 +콘솔 없는 백그라운드 프로세스로 새 봇을 띄우고 Discord 시작 알림을 보냅니다. + ## 에이전트 운영 문서 - `AGENTS.md`: Codex가 읽는 운영 지침 diff --git a/reports/implementation_log.md b/reports/implementation_log.md index e008325..a7185bf 100644 --- a/reports/implementation_log.md +++ b/reports/implementation_log.md @@ -1,5 +1,17 @@ # Implementation Log +## 2026-07-02 + +- Hardened Windows background operation so users do not need to keep a visible + console window open: + - `scripts/start_bot.py` now starts `app/main.py` with `pythonw.exe` when it is + available. + - `scripts/_watchdog.py` uses the same `pythonw.exe` restart path. + - `scripts/setup_scheduler.ps1` registers scheduled PowerShell tasks with + `-WindowStyle Hidden` and marks task settings as hidden. +- Updated `README.md` with the background-operation rule and the preferred + manual start command. + ## 2026-06-10 - Enabled wake-from-sleep behavior for Scheduler tasks: diff --git a/scripts/_watchdog.py b/scripts/_watchdog.py index b34a811..00d03da 100644 --- a/scripts/_watchdog.py +++ b/scripts/_watchdog.py @@ -17,6 +17,14 @@ from app.main import load_env load_env() +def _bot_python() -> str: + if os.name == "nt": + pythonw = Path(sys.executable).with_name("pythonw.exe") + if pythonw.exists(): + return str(pythonw) + return sys.executable + + def _is_process_alive(pid: int) -> bool: result = subprocess.run( ["tasklist", "/FI", f"PID eq {pid}", "/NH"], @@ -69,7 +77,7 @@ def _restart_bot() -> int: log_path = PROJECT / "logs" / "bot_stderr.log" with open(log_path, "a", encoding="utf-8") as log: proc = subprocess.Popen( - [sys.executable, "-u", "app/main.py"], + [_bot_python(), "-u", "app/main.py"], cwd=PROJECT, creationflags=creationflags, stdout=log, diff --git a/scripts/setup_scheduler.ps1 b/scripts/setup_scheduler.ps1 index e5c00a8..5166083 100644 --- a/scripts/setup_scheduler.ps1 +++ b/scripts/setup_scheduler.ps1 @@ -25,7 +25,7 @@ function Register-StockTask { $Trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek $Weekdays -At $Time $Action = New-ScheduledTaskAction ` -Execute "powershell.exe" ` - -Argument "-NonInteractive -ExecutionPolicy Bypass -File `"$Project\scripts\$Script`"" ` + -Argument "-NoProfile -WindowStyle Hidden -NonInteractive -ExecutionPolicy Bypass -File `"$Project\scripts\$Script`"" ` -WorkingDirectory $Project $Settings = New-ScheduledTaskSettingsSet ` -ExecutionTimeLimit (New-TimeSpan -Minutes $LimitMinutes) ` @@ -34,6 +34,7 @@ function Register-StockTask { -DontStopIfGoingOnBatteries ` -RunOnlyIfNetworkAvailable:$false $Settings.DisallowStartIfOnBatteries = $false + $Settings.Hidden = $true Register-ScheduledTask ` -TaskName $Name ` @@ -49,7 +50,7 @@ function Register-StockTask { function Register-WatchdogTask { $ScriptPath = Join-Path $Project "scripts\run_watchdog.ps1" - $Command = 'schtasks /Create /TN "\StockBot\StockBot_Watchdog" /TR "\"powershell.exe\" -NonInteractive -ExecutionPolicy Bypass -File \"' + $ScriptPath + '\"" /SC WEEKLY /D MON,TUE,WED,THU,FRI /ST 09:00 /RI 5 /DU 06:05 /F' + $Command = 'schtasks /Create /TN "\StockBot\StockBot_Watchdog" /TR "\"powershell.exe\" -NoProfile -WindowStyle Hidden -NonInteractive -ExecutionPolicy Bypass -File \"' + $ScriptPath + '\"" /SC WEEKLY /D MON,TUE,WED,THU,FRI /ST 09:00 /RI 5 /DU 06:05 /F' cmd.exe /c $Command | Out-Null if ($LASTEXITCODE -ne 0) { throw "StockBot_Watchdog registration failed" @@ -60,6 +61,7 @@ function Register-WatchdogTask { $Task.Settings.WakeToRun = $true $Task.Settings.DisallowStartIfOnBatteries = $false $Task.Settings.StopIfGoingOnBatteries = $false + $Task.Settings.Hidden = $true Set-ScheduledTask -TaskName "StockBot_Watchdog" -TaskPath $TaskPath -Settings $Task.Settings | Out-Null Write-Host "[OK] StockBot_Watchdog registered weekdays at 09:00-15:05 every 5 minutes" -ForegroundColor Green diff --git a/scripts/start_bot.py b/scripts/start_bot.py index 669c2dd..fcd9b2b 100644 --- a/scripts/start_bot.py +++ b/scripts/start_bot.py @@ -11,6 +11,14 @@ PID_FILE = PROJECT / "logs" / "bot.pid" LOG_FILE = PROJECT / "logs" / "bot_stderr.log" +def _bot_python() -> str: + if os.name == "nt": + pythonw = Path(sys.executable).with_name("pythonw.exe") + if pythonw.exists(): + return str(pythonw) + return sys.executable + + def _taskkill(pid: int) -> None: subprocess.run(["taskkill", "/PID", str(pid), "/F"], capture_output=True, text=True) @@ -69,7 +77,7 @@ def _start_bot() -> int: with open(LOG_FILE, "a", encoding="utf-8") as log: proc = subprocess.Popen( - [sys.executable, "-u", "app/main.py"], + [_bot_python(), "-u", "app/main.py"], cwd=PROJECT, creationflags=creationflags, stdout=log,