작업 스케줄러 파이썬 0x1 — 자동매매 봇이 안 켜질 때
작업 스케줄러의 "마지막 실행 결과 0x1"은 대부분 스케줄러 문제가 아니라 python.exe가 종료 코드 1로 끝났다는 뜻, 즉 파이썬 안에서 예외가 났다는 뜻입니다. 가장 흔한 원인은 "시작 위치"를 비워 둬 작업 폴더가 C:\Windows\System32가 되는 것이고(상대경로 config.json이 FileNotFoundError, 실측), 다음이 pythonw.exe에서 sys.stdout이 None이라 생기는 오류입니다(실측). 0x1이 아닌 코드는 뜻이 따로 있습니다. 0x41301=실행 중, 0x41303=아직 실행 안 됨, 0x80070002=프로그램 경로 없음, 0x800710E0=조건·권한 때문에 거부. 그리고 기본값 두 개가 장 중 봇을 조용히 죽이거나 느리게 만듭니다. 실행 시간 제한 72시간(PT72H)과 우선순위 7(낮음)입니다.
목차
- 결과 코드 읽는 법 — 이 PC 225개 작업 실측
- 0x1 원인 ① 시작 위치 — System32에서 실행된다
- 0x1 원인 ② pythonw — 출력이 None이다
- 0x1이 아닌 코드 — 0x80070002·0x800710E0
- 기본값 함정 — 72시간 제한과 우선순위 7
- "로그온 여부에 관계없이 실행"이 깨는 것
- 재시작과 증권사 토큰 규칙
- 봇용 설정표와 래퍼 코드
- 자주 묻는 질문
1. 결과 코드 읽는 법 — 이 PC 225개 작업 실측
작업 스케줄러 화면의 "마지막 실행 결과"는 16진수(0x1)로, PowerShell Get-ScheduledTaskInfo의 LastTaskResult는 10진수(267011)로 보입니다. 같은 값이니 바꿔 읽어야 합니다. 2026-10-23 이 글을 쓴 윈도우 11 PC에 등록된 작업 전체의 결과 코드를 세어 본 실측입니다.
PS> Get-ScheduledTask | Get-ScheduledTaskInfo |
Group-Object LastTaskResult | Sort-Object Count -Descending
Count Name
----- ----
123 0 # 0x0 정상 종료
88 267011 # 0x41303 아직 한 번도 실행 안 됨
4 267009 # 0x41301 지금 실행 중
2 2 # 0x2 프로그램이 종료 코드 2로 끝남
2 2147946720 # 0x800710E0 운영자·관리자가 요청 거부
1 2147942402 # 0x80070002 지정한 파일 없음
16진수로 바꾸는 한 줄은 '{0:X}' -f 267011(결과 41303)입니다. 읽는 규칙은 두 가지입니다.
| 코드 | 뜻 | 봇 운영에서의 의미 |
|---|---|---|
0x0 | 정상 종료 | 봇이 스스로 끝났다. 장 중이라면 그것 자체가 이상 |
0x1, 0x2 …(작은 수) | 프로그램의 종료 코드 그대로 | 파이썬 예외(sys.exit(1)와 같음). 스케줄러가 아니라 코드·환경 문제 |
0x41301 | 실행 중 (SCHED_S_TASK_RUNNING) | 정상. 장 중 봇이면 이게 맞는 값 |
0x41303 | 아직 실행 안 됨 (SCHED_S_TASK_HAS_NOT_RUN) | 트리거가 한 번도 안 걸렸다. 시작 날짜·요일 확인 |
0x80070002 | 파일을 찾을 수 없음 | "프로그램/스크립트" 경로 오타, 가상환경 경로 변경 |
0x800710E0 | 운영자·관리자가 요청 거부 | 전원 조건(배터리), 이전 인스턴스 실행 중, 계정 권한 |
핵심은 0x8007로 시작하는 큰 수는 스케줄러가 실행 자체를 못 한 것, 0x1 같은 작은 수는 실행은 됐는데 파이썬이 실패한 것이라는 구분입니다. 0x1을 보고 작업 설정을 이리저리 바꾸는 건 대개 헛수고입니다.
2. 0x1 원인 ① 시작 위치 — System32에서 실행된다
작업의 "동작 → 시작 위치(옵션)"를 비워 두면 프로그램은 C:\Windows\System32를 현재 폴더로 삼아 실행됩니다. 명령 프롬프트에서 직접 돌릴 때는 프로젝트 폴더에 있으니 멀쩡하다가, 스케줄러로만 실패합니다. 같은 스크립트를 두 폴더에서 실행해 본 결과입니다(Python 3.10.4, 실측).
# bot.py
import json, os
print("cwd =", os.getcwd())
cfg = json.load(open("config.json", encoding="utf-8")) # 상대경로
== 프로젝트 폴더에서 실행
cwd = C:\...\scratchpad\tsx
loaded {'stk_cd': '005930'}
exit=0
== System32에서 실행 (시작 위치를 비운 작업과 같은 상태)
cwd = C:\Windows\System32
FileNotFoundError: [Errno 2] No such file or directory: 'config.json'
exit=1 ← 스케줄러에는 0x1로 보인다
봇에서는 config.json뿐 아니라 KIS 접근토큰 캐시 파일, 키움 REST 토큰 파일, .env, SQLite DB, 로그 폴더가 전부 같은 이유로 깨집니다. 더 나쁜 경우는 쓰기입니다. 읽기는 실패하면 멈추지만, open("trades.csv", "a")는 권한이 있으면 엉뚱한 폴더에 새 파일을 만들고 계속 돕니다. 고치는 방법은 둘 다 하는 것입니다.
# fixed.py — 현재 폴더와 무관하게 스크립트 위치 기준으로 찾는다
from pathlib import Path
BASE = Path(__file__).resolve().parent
cfg = json.load(open(BASE / "config.json", encoding="utf-8"))
# System32에서 실행해도
cwd = C:\Windows\System32 | loaded {'stk_cd': '005930'}
exit=0
그리고 작업의 "시작 위치"에도 프로젝트 폴더를 넣습니다. 이때 따옴표를 넣지 않습니다. "프로그램/스크립트" 칸은 공백이 있으면 따옴표가 필요하지만 "시작 위치" 칸에 따옴표를 넣으면 폴더 이름이 틀린 것으로 처리됩니다(공백이 있는 경로도 따옴표 없이 씁니다).
3. 0x1 원인 ② pythonw — 출력이 None이다
장 중에 검은 콘솔 창이 떠 있는 게 싫어서 python.exe 대신 pythonw.exe를 걸면 새 함정이 생깁니다. 콘솔이 없는 상태로 실행하면 sys.stdout과 sys.stderr가 None입니다. PowerShell Start-Process로 콘솔 없이 실행해 본 결과입니다(실측).
# pw2.py
with open(here / "pw2.txt", "w") as f:
f.write("stdout=%r stderr=%r\n" % (sys.stdout, sys.stderr))
print("print is silently ignored") # 조용히 무시된다
sys.stdout.write("this line raises\n") # AttributeError
# 결과
exit=1
stdout=None stderr=None
print()는 출력 대상이 None이면 아무것도 하지 않고 넘어가지만, sys.stdout.write()·sys.stdout.flush()나 이를 직접 부르는 라이브러리(진행 표시줄, 일부 로깅 설정)는 AttributeError로 죽습니다. 그리고 stderr도 None이라 트레이스백이 어디에도 남지 않습니다. 스케줄러에는 0x1만 보이고 원인은 사라집니다. 같은 터미널에서 테스트하면 파이프가 연결돼 있어 stdout이 살아 있으므로 재현도 안 됩니다.
해법은 로그를 파일로 보내는 것입니다. 봇 맨 앞에 아래 블록을 두면 python.exe든 pythonw.exe든 같은 곳에 남습니다.
import sys, logging
from pathlib import Path
BASE = Path(__file__).resolve().parent
LOG = BASE / "logs"; LOG.mkdir(exist_ok=True)
if sys.stdout is None: # pythonw · 스케줄러 무콘솔
sys.stdout = open(LOG / "stdout.log", "a", encoding="utf-8", buffering=1)
if sys.stderr is None:
sys.stderr = open(LOG / "stderr.log", "a", encoding="utf-8", buffering=1)
logging.basicConfig(filename=LOG / "bot.log", level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s", encoding="utf-8")
sys.excepthook = lambda t, v, tb: logging.critical("uncaught", exc_info=(t, v, tb))
마지막 줄이 중요합니다. 잡히지 않은 예외가 bot.log에 트레이스백째 남으므로, 0x1을 본 다음 날 아침에 원인을 읽을 수 있습니다. 매매 기록까지 남기는 구성은 자동매매 로그·매매일지 기록 가이드에 따로 정리했습니다.
4. 0x1이 아닌 코드 — 0x80070002·0x800710E0
0x80070002 — 프로그램 경로가 없다
"프로그램/스크립트"에 python만 적었는데 작업 계정의 PATH에 파이썬이 없거나, 가상환경 폴더를 옮기거나 새로 만들어 .venv\Scripts\python.exe 경로가 바뀐 경우입니다. 늘 가상환경 안의 python.exe 절대경로를 적으십시오. 윈도우 스토어판 파이썬의 python.exe(앱 실행 별칭)는 사용자 세션 밖에서 동작이 다를 수 있어 봇에는 python.org 설치판이나 가상환경 경로를 권합니다.
0x800710E0 — 실행이 거부됐다
메시지는 "운영자 또는 관리자가 요청을 거부했습니다"입니다. 봇 운영에서 자주 보는 원인은 셋입니다. ① 노트북에서 "컴퓨터의 AC 전원이 켜져 있는 경우에만 작업 시작"(기본 체크)이 걸려 배터리 상태에서 거부, ② 이전 실행이 아직 살아 있는데 새 트리거가 와서 거부, ③ 비밀번호를 바꾼 뒤 작업에 저장된 자격 증명이 안 맞는 경우입니다. 공식 문서가 원인을 하나로 특정하지 않는 코드라 ①→②→③ 순으로 확인하십시오. ②는 의도된 동작일 수 있습니다. 봇이 두 개 뜨지 않게 막는 것이니까요. 그 이야기는 자동매매 봇 중복 실행 방지에서 다뤘습니다.
참고로 원인 추적용 "작업 기록"은 기본으로 꺼져 있습니다. 이 PC에서 wevtutil gl Microsoft-Windows-TaskScheduler/Operational을 조회하면 enabled: false였습니다. 작업 스케줄러 오른쪽 패널의 "모든 작업 기록 사용"을 켜 두면 이벤트 ID 100(시작)·102(완료)·201(동작 완료, 종료 코드 포함)로 언제 무엇이 끝났는지 남습니다.
5. 기본값 함정 — 72시간 제한과 우선순위 7
설정을 따로 만지지 않은 작업이 어떤 값을 갖는지 PowerShell로 확인한 결과입니다. New-ScheduledTaskSettingsSet은 작업을 등록하지 않고 기본 설정 객체만 만들기 때문에 안전하게 볼 수 있습니다(실측).
PS> New-ScheduledTaskSettingsSet | Format-List ExecutionTimeLimit, MultipleInstances,
DisallowStartIfOnBatteries, StopIfGoingOnBatteries, StartWhenAvailable,
WakeToRun, RestartCount, Priority
ExecutionTimeLimit : PT72H
MultipleInstances : IgnoreNew
DisallowStartIfOnBatteries : True
StopIfGoingOnBatteries : True
StartWhenAvailable : False
WakeToRun : False
RestartCount : 0
Priority : 7
| 기본값 | 봇에 생기는 일 | 권장 |
|---|---|---|
ExecutionTimeLimit PT72H | 재부팅 없이 켜 두는 봇은 시작 72시간 뒤 스케줄러가 강제 종료. 월요일 아침에 켠 봇이 목요일 아침에 사라진다 | 매일 장 전 재시작 구조면 유지, 상시 실행이면 해제 |
Priority 7 | Microsoft 표상 BELOW_NORMAL_PRIORITY_CLASS. PC가 바쁘면 시세 처리·주문이 밀린다 | 작업 XML에서 <Priority>5</Priority>(보통) |
StopIfGoingOnBatteries True | 노트북 전원선이 빠지면 봇 종료 | 노트북 운영이면 해제 |
StartWhenAvailable False | 08:30 트리거 때 PC가 꺼져 있었으면 그날은 안 켜짐 | 켜기 |
RestartCount 0 | 죽으면 다시 안 켜짐 | 3회·2분 간격 등 (7절) |
우선순위는 GUI에 칸이 없어 작업을 XML로 내보내 <Priority> 값을 고친 뒤 다시 가져오거나, PowerShell에서 -Priority 5로 설정 객체를 만들어 등록합니다. Microsoft 문서 기준으로 4~6이 보통(NORMAL_PRIORITY_CLASS), 7~8이 낮음입니다. 72시간 제한은 일반 탭이 아니라 "설정" 탭의 "다음 시간 이상 작업이 실행되면 중지"입니다.
6. "로그온 여부에 관계없이 실행"이 깨는 것
"사용자가 로그온할 때만 실행"(기본, LogonType Interactive)을 "사용자의 로그온 여부에 관계없이 실행"으로 바꾸면 화면 없는 별도 세션에서 실행됩니다. 재부팅 뒤 로그인 없이도 켜진다는 장점이 있지만 봇에서는 세 가지가 깨집니다.
- 키움 OpenAPI+: 로그인 창이 뜨는 OCX 기반이라 화면 없는 세션에서는 로그인이 진행되지 않습니다. 자동 로그온 + "로그온할 때" 트리거 조합이 필요합니다(→ 키움 OpenAPI+ 자동로그인 무인 운영). 키움 REST API·KIS REST는 HTTP라 이 제약이 없습니다.
- 네트워크 드라이브·구글 드라이브 같은 가상 드라이브: 사용자 로그인 때 연결되는
G:·Z:문자는 그 세션에 없습니다. 봇 코드·토큰 파일을 이런 드라이브에 두면0x1(파일 없음)이 납니다. 봇은 로컬 디스크에 두십시오. - 사용자 환경 변수:
setx로 넣은KIS_APP_KEY같은 값은 작업 계정이 다르면 보이지 않습니다. 키를 환경 변수로 관리하는 방법은 자동매매 API 키 보안을 참고하십시오.
재부팅 직후 자동으로 켜지게 하는 전체 흐름(활성 시간, 자동 로그온, 재부팅 후 잔고 재조회)은 윈도우 업데이트 재부팅 대비 설정에 있습니다. 이 글은 그 마지막 단계, "켜졌는데 왜 바로 죽나"를 다룬 것입니다.
7. 재시작 설정이 증권사 토큰 규칙과 부딪히는 곳
"실패 시 다시 시작"을 켜면 봇이 죽을 때마다 처음부터 다시 뜨고, 대부분의 봇은 시작하자마자 접근토큰부터 발급받습니다. 여기서 증권사 규칙과 부딪힙니다.
- KIS(한국투자증권):
/oauth2/tokenP로 받는access_token은 유효기간 1일, 발급은 1분에 1회, 6시간 이내 재요청은 기존 토큰이 그대로 나오고 발급할 때마다 알림톡이 옵니다. 재시작 간격을 1분으로 두고 토큰 캐시가 없으면 재시작마다 발급을 시도하다 제한에 걸리고, 휴대폰에는 알림톡이 쌓입니다(→ KIS 토큰 만료·재발급 규칙). - 키움 REST API:
au10001응답의expires_dt가 만료 시각입니다. 재시작마다 새로 받지 말고 이 값과 함께 파일에 저장해 두었다가 남은 시간이 충분하면 재사용하십시오. - 공통: 재시작 직후 잔고·미체결·시세를 한꺼번에 조회하면 KIS
EGW00201(초당 거래건수 초과)이나 키움 429에 걸리기 쉽습니다. 시작 시 조회는 순서대로, 간격을 두고 하십시오.
토큰 캐시 파일 경로가 2절의 상대경로 문제와 정확히 겹친다는 점도 기억하십시오. open("kis_token.json")은 스케줄러에서 System32를 가리키므로 캐시를 못 찾고, 그러면 재시작할 때마다 새로 발급합니다. BASE / "kis_token.json"으로 고정하고, appkey·appsecret은 작업 계정에서 보이는 환경 변수(KIS_APP_KEY 등)로 넣으면 0x1과 토큰 제한을 한 번에 피합니다.
8. 봇용 설정표와 래퍼 코드
| 탭 / 칸 | 값 |
|---|---|
| 동작 · 프로그램/스크립트 | C:\bot\.venv\Scripts\python.exe (절대경로, 공백 있으면 따옴표) |
| 동작 · 인수 추가 | -u main.py (-u = 출력 버퍼 끄기 → 로그가 바로 기록) |
| 동작 · 시작 위치 | C:\bot (따옴표 없이) |
| 트리거 | 평일 08:30 (KRX 정규장 09:00 전), 휴장일은 코드에서 거른다 |
| 설정 · 다음 시간 이상 실행되면 중지 | 매일 재시작 구조면 12시간, 상시면 해제 |
| 설정 · 이미 실행 중이면 | 새 인스턴스 시작 안 함 (IgnoreNew) |
| 설정 · 실패 시 다시 시작 | 2분 간격, 3회 (토큰 캐시 전제 — 7절) |
| 조건 · AC 전원일 때만 시작 | 해제 (노트북·미니PC) |
휴장일 판단은 스케줄러가 못 하므로 봇이 직접 합니다(→ KRX 휴장일 확인과 봇 스케줄링). 마지막으로 결과 코드를 매일 아침 확인하는 PowerShell 한 줄입니다. 작업 이름 AlgoBot은 예시입니다.
PS> $i = Get-ScheduledTaskInfo -TaskName "AlgoBot" # 출력은 형식 예시
PS> "{0} | last=0x{1:X} | next={2}" -f $i.LastRunTime, $i.LastTaskResult, $i.NextRunTime
2026-10-23 오전 8:30:00 | last=0x41301 | next=2026-10-26 오전 8:30:00
장 중에 0x41301이 아니면 봇이 떠 있지 않은 것입니다. 이 확인을 텔레그램으로 받는 구성은 텔레그램 봇 모니터링, 죽었을 때의 복구 순서는 자동매매 봇 장애 복구 플레이북에 있습니다.
위 결과 코드·기본값은 2026-10-23 윈도우 11에서 확인한 것이며 버전에 따라 다를 수 있습니다. 증권사 API 정책과 윈도우 동작은 Microsoft·각 증권사 공식 문서로 확인하십시오. 이 글은 운영 설정을 다룰 뿐 특정 전략이나 종목을 권하지 않습니다.
9. 자주 묻는 질문
작업 스케줄러 결과가 0x1인데 직접 실행하면 잘 됩니다. 왜 그런가요?
0x1은 python.exe가 종료 코드 1로 끝났다는 뜻이라 파이썬 안에서 예외가 난 것입니다. 직접 실행과 다른 점은 대개 현재 폴더입니다. 작업의 시작 위치를 비우면 C:\Windows\System32에서 실행돼 config.json·토큰 파일 같은 상대경로가 FileNotFoundError를 냅니다. 시작 위치에 프로젝트 폴더를 넣고, 코드는 Path(__file__) 기준 절대경로로 바꾸십시오.
0x41301과 0x41303은 오류인가요?
둘 다 오류가 아닙니다. 0x41301(10진수 267009)은 지금 실행 중, 0x41303(267011)은 아직 한 번도 실행되지 않았다는 뜻입니다. 장 중 봇이라면 0x41301이 정상이고, 0x41303이 계속 보이면 트리거 시작 날짜·요일 설정을 확인하십시오.
pythonw로 실행하면 왜 로그가 안 남나요?
콘솔 없이 실행된 pythonw.exe에서는 sys.stdout과 sys.stderr가 None입니다. print는 조용히 무시되고 sys.stdout.write는 AttributeError로 죽으며, stderr도 None이라 트레이스백이 남지 않습니다. logging으로 파일에 기록하고 sys.excepthook으로 잡히지 않은 예외를 기록하십시오.
며칠 켜 둔 봇이 혼자 꺼졌습니다. 작업 스케줄러 때문일 수 있나요?
그럴 수 있습니다. 작업 기본 설정의 실행 시간 제한이 72시간(PT72H)이라, 재부팅 없이 상시 실행하는 봇은 시작 72시간 뒤 강제 종료됩니다. 설정 탭의 '다음 시간 이상 작업이 실행되면 중지'를 해제하거나, 매일 장 전에 재시작하는 구조로 바꾸십시오.
켜 두기만 하면 도는 봇으로 납품합니다
알고랩은 자동 실행 설정, 로그 파일, 재시작·휴장일 처리까지 맞춰 설치해 드립니다. 지금 쓰는 봇이 아침마다 안 켜진다면 상황만 알려 주세요. 24시간 빠른 답변 가능합니다.
무료 상담 시작하기