봇이 죽은 걸 언제 아는가 — 하트비트와 무음 실패
자동매매 봇이 멈추는 방식은 대부분 조용합니다. 화면에 빨간 에러가 뜨고 프로그램이 닫히는 그림을 상상하지만, 실제로 자주 일어나는 건 프로세스는 그대로 떠 있는데 루프만 안 도는 상태입니다. 겉으로는 아무 일도 없어 보이고, 거래가 없다는 사실만 남습니다. 문제는 거래가 없다는 사실이 "죽었다"와 "기다리는 중"을 구분해 주지 않는다는 점입니다.
거래가 없다는 건 신호가 아니다
봇이 멈춘 걸 알아채는 가장 흔한 경로는 "요즘 거래가 안 나오네"입니다. 그런데 이건 감시가 아니라 뒤늦은 눈치채기입니다. 진입 조건을 까다롭게 잡은 봇은 원래도 며칠씩 조용하기 때문입니다.
얼마나 조용해야 이상한지 숫자로 잡아 보겠습니다. 진입이 무작위로 흩어져 일어난다고 두면 무거래 구간의 길이는 계산이 됩니다.
6시간 기대 진입 3 × 6÷24 = 0.75건
한 건도 없을 확률 47.2%
12시간 기대 1.5건
한 건도 없을 확률 22.3%
24시간 기대 3건
한 건도 없을 확률 5.0%
48시간 기대 6건
한 건도 없을 확률 0.25%
─────────────
하루 종일 조용해도 20번에 한 번은 정상
6시간 무거래로 경보를 내면 절반 가까이가 헛경보입니다. 헛경보가 반복되면 사람은 경보를 끕니다. 그러면 진짜일 때도 안 봅니다. 반대로 48시간까지 기다리면 헛경보는 거의 없지만 이틀을 날립니다.
즉 거래 발생 여부는 봇의 생존을 재는 자가 아닙니다. 무거래는 정상 상태의 일부이고, 사망도 무거래로 보입니다. 두 가지를 겹쳐 놓은 지표로는 둘을 못 가릅니다. 필요한 건 거래와 무관하게 "지금 살아 있다"를 말해 주는 별도의 신호입니다.
하트비트 — 한 바퀴 돌 때마다 시각을 남긴다
방법 자체는 단순합니다. 봇의 메인 루프가 한 바퀴를 끝낼 때마다 현재 시각을 파일에 덮어씁니다. 거래를 했든 안 했든 상관없이 매번 씁니다. 감시하는 쪽은 그 파일의 시각만 봅니다. 시각이 계속 밀려나면 살아 있는 것이고, 멈춰 있으면 루프가 안 도는 것입니다.
핵심은 하트비트를 루프 끝에서 쓴다는 것입니다. 시작할 때 쓰면 루프 중간에서 걸려 멈춰도 계속 갱신됩니다. 끝에서 쓰면 한 바퀴를 온전히 돈 것만 기록됩니다.
문턱을 60초로 두면
한 번만 느려져도 경보 → 헛경보 폭주
문턱 = 주기 × 5 + 여유 60초
60 × 5 + 60 = 360초 (6분)
= 5회 연속 못 돈 상태
감시기가 2분마다 확인하면
최대 탐지 지연 6분 + 2분 = 8분
─────────────
주기가 5분인 봇이면 문턱은 26분이 된다
문턱을 주기의 배수로 잡는 이유는 봇마다 주기가 다르기 때문입니다. 1분마다 도는 봇과 5분마다 도는 봇에 같은 6분 문턱을 씌우면 후자는 정상인데도 계속 경보가 납니다. 봇별로 "내 주기는 몇 초"를 같이 기록해 두면 감시기가 문턱을 자동으로 잡습니다.
탐지 지연 8분이 짧아 보일 수 있는데, 짧게 만드는 데는 비용이 없습니다. 하트비트 파일 하나 쓰는 건 공짜에 가깝고, 감시기가 2분마다 파일 시각을 읽는 것도 공짜입니다. 매매 로그를 이미 남기고 있다면 거기에 한 줄 더 얹는 정도입니다.
8분과 6시간의 차이는 얼마인가
탐지가 늦으면 뭘 잃는지를 두 가지로 나눠 봐야 합니다. 못 잡은 진입 기회와, 이미 들고 있던 포지션입니다. 앞의 것은 생각보다 작고 뒤의 것은 생각보다 큽니다.
놓친 진입
8분 방치 3 × 8÷1440 × $4 = $0.07
6시간 방치 3 × 6÷24 × $4 = $3.00
보유 포지션 (노셔널 $800)
손절 −1.5% → 정상이면 −$12
방치 중 −5%까지 진행 → −$40
차액 −$28
$28 ÷ $4 = 정상 거래 7건치
─────────────
진짜 비용은 못 들어간 쪽이 아니라 못 나온 쪽
포지션을 들고 있는 동안 봇이 멈추면 손절이 집행되지 않습니다. 손절을 거래소에 미리 걸어 두는 방식이면 이 위험이 줄지만, 조건부 청산이나 트레일링을 봇이 계산해서 내보내는 구조라면 봇이 멈춘 순간 그 손절은 존재하지 않는 것과 같습니다. 그래서 포지션을 들고 있을 때의 경보 문턱은 무포지션일 때보다 짧아야 합니다. 같은 6분이라도 무게가 다릅니다.
멈춘 걸 알고 나서 무엇을 할지도 미리 정해 두는 편이 낫습니다. 즉시 재기동할지, 포지션부터 수동으로 정리할지에 따라 순서가 갈립니다. 재기동 쪽이면 봇 재시작 시 상태 복구를 먼저 확인해야 하고, 멈추는 것 자체를 안전하게 만들어 두려면 킬 스위치 설계가 따로 필요합니다.
살아 있다는 말은 네 가지 뜻이다
"봇이 살아 있다"를 한 겹으로 보면 놓칩니다. 실제로는 네 개의 서로 다른 상태가 있고, 위층이 멀쩡해도 아래층이 죽을 수 있습니다.
① 프로세스 존재
작업 목록에 보인다
→ 멈춘 루프도 여기선 정상
② 루프 회전
하트비트 시각이 갱신된다
→ 통신이 끊겨도 루프는 돌 수 있다
③ 외부 통신
마지막 시세 수신 시각이 신선하다
→ 읽기는 되는데 쓰기가 막힐 수 있다
④ 주문 집행
마지막 주문 응답 시각·결과
→ 여기까지 와야 진짜 살아 있다
─────────────
①만 보면 ②③④의 사고를 전부 놓친다
③이 죽는 경우가 특히 조용합니다. 시세 수신이 끊겼는데 봇이 마지막으로 받은 값을 계속 쓰면, 봇은 멀쩡히 돌면서 얼어붙은 가격으로 판단을 내립니다. 그래서 시세에도 신선도 문턱을 걸어야 합니다. 마지막 수신이 몇 초 이상 지났으면 새 진입을 멈추는 식입니다.
④가 막히는 이유는 대체로 셋입니다. 요청 한도 초과로 거부되거나, 타임스탬프 오차로 서명이 틀리거나, 거래소 점검 중이거나. 셋 다 봇 입장에서는 "주문을 넣었는데 안 됐다"로 똑같이 보이므로, 주문 거부 사유를 코드까지 로그에 남겨 둬야 나중에 구분이 됩니다.
감시가 0을 채우면 초록불인 채로 지나간다
감시기를 만들 때 가장 자주 나오는 버그는 실패를 0으로 적는 것입니다. 잔고 조회가 타임아웃 났는데 잔고를 0으로 기록하고, 포지션 조회가 실패했는데 "포지션 없음"으로 기록합니다. 그러면 화면에는 "이상 없음"이 뜹니다. 값이 없다는 것과 값이 0이라는 것은 완전히 다른 말인데 같은 칸에 들어간 것입니다.
틀린 처리
조회 실패 → 포지션 0 기록
화면 "무포지션 · 정상"
실제 $800 보유 중
맞는 처리
조회 실패 → 값 없음(모름)으로 기록
화면 "조회 실패 · 상태 불명"
단발 타임아웃 헛경보를 피하려면
연속 3회 실패에서 경보
3회 × 2분 = 6분 지연
─────────────
"모름"은 정상이 아니라 별도의 상태다
같은 실수가 잔고·미실현손익·거래 건수에서도 나옵니다. 조회가 실패한 시점의 숫자를 0으로 채워 넣으면 그래프가 바닥을 한 번 찍고 올라옵니다. 보는 사람은 손실로 오해하고, 자동 판단이 그 값을 쓰면 엉뚱한 행동을 합니다. 포지션 대사도 같은 원칙 위에 서 있습니다 — 조회 실패를 "포지션 없음"으로 읽으면 대사 자체가 거짓말이 됩니다.
스케줄러의 "성공"은 스폰이 아니다
정해진 시각에 돌아야 하는 작업이라면 함정이 하나 더 있습니다. 운영체제 스케줄러나 작업 관리자는 보통 프로세스를 띄우는 데 성공했는지를 기준으로 성공·실패를 기록합니다. 스크립트가 1초 만에 예외로 죽어도 "성공"으로 남습니다.
스폰 기준
프로세스 기동됨 → 성공 기록
8일 × 1회 = 초록불 8회
실제 산출물 0회
산출물 기준
종료코드 0 그리고
산출물 파일 시각이 이번 실행 이후
→ 첫날 바로 빨간불
─────────────
감시 화면이 초록불이어도 증거는 따로 본다
정기적으로 나가야 하는 것이 있다면 그 자체에도 감시를 붙이는 편이 안전합니다. 기대 주기 안에 실제 기록이 남지 않으면 경보를 내는 방식입니다. 그리고 감시기 자신도 주기적으로 살아있음을 남겨야 합니다. 안 그러면 감시기가 죽었을 때 "아무 경보도 없다"와 "아무 문제도 없다"가 같은 모습이 됩니다.
흔히 하는 실수
첫째, 하트비트를 루프 시작에서 쓰는 것입니다. 루프 중간에서 걸려도 갱신이 계속되어 감시가 무력해집니다. 끝에서 씁니다.
둘째, 예외를 통째로 삼키는 것입니다. 루프를 전부 try로 감싸 놓고 아무 것도 안 하면 봇은 영원히 돌면서 아무 일도 안 합니다. 하트비트는 갱신되므로 겉보기엔 정상입니다. 예외 횟수를 세어 연속 실패가 쌓이면 경보를 내야 합니다.
셋째, 경보를 보내고 도착을 확인 안 하는 것입니다. 경보 발송 함수가 조용히 실패하면 사고가 나도 아무 것도 안 옵니다. 발송 결과를 기록하고, 주기적으로 "정상 작동 중" 같은 신호를 한 번씩 흘려서 경로 자체가 살아 있는지 확인합니다.
넷째, 경보를 너무 촘촘하게 거는 것입니다. 조건이 풀릴 때까지 같은 내용이 매 사이클 재발사되면 도배가 되고, 도배는 무시로 이어집니다. 같은 사유의 경보는 한 번만 보내고 상태가 바뀔 때 다시 보냅니다.
다섯째, 실계좌에서 처음 확인하는 것입니다. 감시와 경보 경로는 모의 운용 단계에서 일부러 봇을 죽여 보고 경보가 오는지 확인해 두는 게 순서입니다. 사고가 났을 때가 첫 시험이 되면 그때 감시가 작동하는지는 아무도 모릅니다.
정리
② 거래 없음은 정상과 사망이 겹친 지표다
③ 하루 3건 봇의 6시간 무거래 확률 47.2%
④ 24시간 무거래여도 20번에 한 번은 정상
⑤ 그래서 거래와 무관한 생존 신호가 필요하다
⑥ 하트비트는 루프 끝에서 쓴다
⑦ 문턱 = 주기 × 5 + 여유
⑧ 주기 60초면 6분, 탐지 지연 8분
⑨ 놓친 진입보다 못 나온 포지션이 비싸다
⑩ 손절 −$12가 방치 −$40이면 차액이 7건치
⑪ 포지션 보유 중 문턱은 더 짧게
⑫ 생존은 프로세스·루프·통신·집행 네 층
⑬ 조회 실패는 0이 아니라 "모름"으로 적는다
⑭ 스케줄러 성공은 스폰이 아니라 산출물로 정의
⑮ 감시기도 자기 생존을 남겨야 한다
한 줄로 줄이면 이렇습니다. 감시되지 않는 봇은 돌고 있는 게 아니라 돌고 있다고 믿어지는 중입니다. 믿음과 사실을 가르는 건 시각이 적힌 파일 한 줄입니다.
주의
본문의 하루 평균 3건과 그에 따른 무거래 확률 47.2%·22.3%·5.0%·0.25%, 루프 주기 60초와 문턱 360초·감시 주기 2분·탐지 지연 8분, 건당 기대 +$4와 방치 비용 $0.07·$3.00, 노셔널 $800·손절 −1.5%인 −$12와 방치 −5%인 −$40 및 차액 −$28과 7건, 연속 3회 실패 경보의 6분 지연, 8일 초록불 8회와 산출물 0회 같은 수치는 계산 구조를 보여주기 위한 가정 예시이며 실측이 아닙니다. 무거래 확률 계산은 진입이 시간에 무작위로 흩어진다는 단순 가정에서 나온 것이라 실제 전략은 시장 상황에 따라 진입이 몰리거나 끊기므로 그대로 적용되지 않습니다. 적절한 하트비트 주기와 경보 문턱은 전략의 매매 빈도·보유 시간·거래소 응답 속도에 따라 달라지며, 문턱을 짧게 잡을수록 헛경보가 늘고 길게 잡을수록 방치 시간이 늘어나는 맞바꿈이 있습니다. 감시 체계를 갖췄다고 손실이 줄어든다는 뜻도 아닙니다. 이 글은 자동매매 운영에서의 상태 감시 방법에 대한 설명이며 특정 종목·방향이나 진입·청산 시점을 권유하지 않습니다. 암호화폐 거래는 원금 전액을 잃을 수 있고 레버리지를 쓰면 손실이 증거금을 넘을 수도 있습니다. 투자 판단과 그 결과는 본인 책임입니다.
NOONOO TRADING 무료 채팅방에서 실시간 트레이딩을 같이 보세요.
봇에서 시작하기📈 OKX 신규 가입 시 거래 수수료 할인
OKX 수수료 할인 가입 →