봇은 새벽부터 죽어 있었습니다. 그런데 그걸 감시하라고 만들어둔 프로그램은 하루 종일 ‘이상 없음’을 출력하고 있었습니다.
봇 헬스체크를 돌리다가 알았습니다. 화면을 보고 처음 든 생각은 하나였어요.
“아 망했다.”
그날 자동매매 봇은 아침부터 한 건도 주문을 내지 못했습니다. 데이터도 하나도 쌓이지 않았고요. 그런데 감시 화면에는 이렇게 찍혀 있었습니다.
“[health] 자동수집 전부 FRESH — 이상 없음.”
원인은 허무했습니다
증권사 API 키가 만료됐습니다. 모의투자용 키는 발급일로부터 3개월이 유효기간인데, 5월 초에 받은 키가 8월 5일 새벽에 수명을 다한 겁니다. 갱신 알림 같은 건 따로 오지 않습니다. 그냥 어느 날 아침부터 안 됩니다.
에러 메시지도 분명했습니다. 인증에 실패했습니다[8001:App Key와 Secret Key 검증에 실패했습니다]. 로그를 열면 이 줄이 하루 종일 쌓여 있었습니다. 숨어 있던 게 아니라 대놓고 찍혀 있었어요.
그런데 감시는 그걸 못 봤습니다.
감시가 왜 못 봤나
나중에 뜯어보니 구멍이 하나가 아니었습니다. 세 가지가 겹쳤습니다.
두 번째가 특히 얄궂었습니다. 감시 로직이 확인하는 건 “오늘 날짜로 로그가 생겼는가”였습니다. 그런데 프로그램이 실패해도 로그는 생깁니다. 에러 메시지로 가득 찬 파일이 쌓이고 있었는데, 감시는 그걸 보고 “오늘도 잘 돌았네”라고 판단한 겁니다.
세 번째는 이렇게 생긴 문제였습니다. 배치 파일 마지막 두 줄이
py 스크립트실행 >> 로그 echo ----- 종료 ----- >> 로그
이렇게 돼 있었습니다. 윈도우는 배치 파일의 성공·실패를 마지막 명령 기준으로 잡습니다. 마지막이 echo니까 결과는 언제나 성공이었어요. 실제 프로그램이 어떻게 끝나든 스케줄러에는 늘 “정상 종료”로 기록됐습니다.
진짜 문제는 따로 있었습니다
위의 셋은 기술적인 실수입니다. 찾아서 고치면 끝나는 것들이죠. 이번 일에서 제일 뼈아팠던 건 그게 아니었습니다.
감시 시스템을 AI에게 만들게 하고, 그 뒤로는 결과만 봤습니다. 만들 때 “이게 진짜 다 보고 있나”를 확인하지 않았어요. 너무 믿었습니다.
그 대가가 이렇게 나왔습니다. 사고를 정리하다가 8거래일 연속으로 거짓 보고를 받고 있었다는 걸 알았습니다. 매일 저녁 텔레그램으로 “오늘 거래 없음”이라는 요약이 왔는데, 실제로는 8월 3일과 4일에 체결이 있었습니다. 봇은 거래를 했고, 저는 안 했다고 보고받았습니다.
이유는 경로 하나였습니다. 거래 기록을 읽어오는 파일 위치가 존재하지 않는 폴더를 가리키고 있었어요. 파일이 없으니 “거래 없음”으로 처리됐고, 그게 매일 알림으로 나갔습니다.
여기서 스스로에게 물어봤습니다. 8일 동안 매일 그 알림을 받으면서 한 번도 이상하다고 느끼지 않았나?
못 느꼈습니다.
다른 봇 하나는 3주 동안 거래가 한 건도 없었습니다. 그것도 별다른 생각 없이 넘어갔습니다. 나중에 확인해보니 이건 실제로 정상이었습니다. 시장 조건이 진입 기준에 안 맞아서 설계대로 막고 있었어요. 고장이 아니었습니다.
문제는 제가 그 둘을 구분할 방법이 없었다는 겁니다. 하나는 진짜 정상이고 하나는 거짓 보고인데, 둘 다 똑같이 “거래 없음”으로 보였습니다. 그리고 저는 어느 쪽도 의심하지 않았습니다.
3주 무거래는 왜 정상이었나
이건 따로 확인해볼 가치가 있었습니다. 그 봇에는 시장이 나쁠 때 아예 안 들어가는 장치가 걸려 있습니다. 조건이 두 개인데, 둘 다 통과해야 매매를 시작합니다.
지수는 위에 있었지만 오르는 종목이 다섯 중 하나뿐이었습니다. 지수만 몇 개 대형주가 끌어올리고 나머지는 빠지는 장이었던 거죠. 이런 국면에서 단기 매매를 하면 지수는 멀쩡한데 내 종목만 깨지기 쉽습니다. 그래서 막았습니다.
즉 3주 침묵은 고장이 아니라 설계대로 작동한 결과였습니다. 그런데 저는 그걸 확인해서 안 게 아니라, 사고를 조사하다가 우연히 알았습니다. 결과적으로 맞았을 뿐이지 제가 판단한 게 아닙니다.
부수적으로 하나 더 알게 됐습니다. 봇이 죽어 있던 8월 5일에는 이 시장 판정 자체가 새로 계산되지 않았습니다. 그래서 판정값이 전날 것에 멈춰 있었습니다. 이번엔 “진입 금지” 상태라 무해했지만, 반대로 “진입 허용” 상태에서 멈췄다면 며칠간 옛 판정으로 매매할 수도 있었다는 뜻입니다.
침묵보다 거짓 안심이 위험합니다
이번 일을 정리하면서 남은 게 이겁니다. 감시를 붙였다는 것만으로 마음이 놓였고, 그 마음이 눈으로 확인하는 일을 밀어냈습니다.
무엇을 고쳤나
- ✓매매하는 봇들을 감시 대상에 실제로 편입 (목록에 없던 게 원인이었으니)
- ✓판정 기준을 바꿈 — ‘로그가 써졌는가’가 아니라 ‘오늘 인증 실패 줄이 몇 개인가’
- ✓배치 파일이 실제 종료 코드를 넘기도록 수정
- ✓거짓 보고를 하던 기록 경로를 실제 파일로 교체
- ✓키 발급일을 파일에 기록하고 만료 14일 전부터 경고
그리고 하나를 새로 만들었습니다. 평일 아침 9시 5분 점검입니다.
기존 점검은 밤에 돌았습니다. “어제 잘 돌았나”를 확인하는 용도죠. 그런데 이번 사고는 새벽 6시 30분에 이미 판별 가능한 상태였습니다. 밤에 알아봐야 그날 장은 끝난 뒤입니다.
그래서 아침 점검은 목적을 다르게 잡았습니다. 인증 실패만 봅니다. 데이터가 최신인지는 안 봐요. 그건 장 끝나고 만들어지는 값이라 아침에 확인하면 거짓 경보만 납니다. 아침 점검의 목적은 진단이 아니라 그날 장을 구제하는 것입니다.
고치면서 알게 된 게 하나 더 있습니다. 기존 밤 점검이 오후 5시에 돌고 있었는데, 정작 데이터를 만드는 작업 하나는 5시 25분에 끝났습니다. 감시가 생산보다 먼저 도는 구조였던 거죠. 이 상태에서 그 작업을 감시 목록에 넣었다면 매일 “데이터 없음” 경보가 울렸을 겁니다. 그리고 매일 울리는 경보는 며칠이면 무시하게 됩니다. 점검 시각을 밤 10시로 옮겼습니다.
거짓 안심이 위험한 만큼, 거짓 경보도 결국 같은 곳으로 갑니다. 둘 다 사람이 화면을 안 보게 만듭니다.
키 만료도 이제 미리 알려줍니다. 발급일을 파일에 적어두고 만기 14일 전부터 경고가 뜨게 했습니다. 실제로 날짜를 앞뒤로 바꿔가며 D-14, D-4, 당일, 만료 후까지 네 번 다 알림이 오는지 확인했습니다. 이번처럼 어느 날 아침에 갑자기 죽는 일은 없어야 하니까요.
돈은 안 잃었는데 신경이 쓰였습니다
솔직히 이 사고로 손실을 본 건 아닙니다. 모의계좌로 돌리고 있었고, 그날은 매매 자체가 0건이었으니까요.
그런데도 계속 마음에 걸렸습니다. 모의로 검증을 쌓아서 실거래로 넘어갈 계획이었는데, 그 일정이 밀리는 게 아까웠습니다. 하루치 기록이 통째로 비면 그만큼 검증이 늦어집니다.
복구가 안 되는 것도 있었습니다. 장중 수급 데이터는 그 시각에만 존재하는 값이라 나중에 다시 받을 수가 없습니다. 종가는 장 끝나고도 조회되지만, “오전 10시에 외국인이 얼마를 사고 있었나”는 그 시각을 놓치면 영영 못 채웁니다. 8월 5일치는 지금도 비어 있습니다.
그래서 자동화를 믿는 정도가 달라졌습니다
이번 일 이후로 믿음이 많이 사라졌습니다.
그런데 이 이야기를 정리하다가 생각난 게 있습니다. 저는 봇을 만들고 감시까지 붙여놓고도 이렇게 됐는데, 사실 더 오래된 게 하나 있더군요.
“나도 자동이체 걸어놓고 몇 년째 안 봤다.”
자동이체, 적립식 자동매수, 자동 리밸런싱. 한 번 걸어두면 알아서 돌아가니까 신경을 끄게 됩니다. 그게 자동화를 쓰는 이유이기도 하고요.
다만 이번에 확인한 건, “조용하다”가 “잘 돌아간다”는 뜻은 아니라는 것입니다. 제 경우엔 조용한 정도가 아니라 매일 “이상 없음”이라고 말해주기까지 했습니다. 그래서 더 안 봤고요.
자동이체가 잔액 부족으로 몇 달째 실패하고 있어도, 적립식 매수가 어느 달부터 안 들어가고 있어도, 알림이 안 오면 모릅니다. 가끔은 자동화가 뭐라고 말하는지가 아니라, 계좌를 직접 열어봐야 합니다. 저도 이번 주말에 자동이체부터 볼 생각입니다.
금융거래 자동이체·자동납부 관련 소비자 정보는 금융감독원에서 확인할 수 있습니다.
관련 글: 자동매매 봇 여정 #10 — 100건의 길 · 자동매매로 돈 못 버는 이유