자동매매 봇 재시작 복구란 프로세스가 재시작된 후에도 이전과 동일한 조건으로 매매를 이어가는 능력이에요. 봇이 살아나는 것보다 살아난 뒤에 “원래 의도대로 작동하는지”가 더 중요한데, 그 사실을 운영 중에 직접 겪었습니다. 재시작 직후에도 별다른 에러 없이 돌아가지만, 손절·익절 라인이 조용히 달라져 있던 상황이었어요.
📌 이 글에서 알 수 있는 것
- 재시작 후 손절/익절 라인이 최대 1.2% 어긋나는 이유와 해결 방법
- 멀쩡한 주문을 매번 지웠다 다시 만드는 비효율을 없애는 방법
- 고아 주문이 다음 포지션을 즉시 청산시키는 사고 예방법
이 개선은 지난 3월에 정리한 코인 자동매매 봇이 알아서 오류를 복구하게 만들었습니다 — 안정성 개선 11가지의 직접적인 연장선이에요. 그 작업에서는 “봇이 재시작 후에 되살아나는가”를 해결했다면, 이번에는 “살아난 봇이 의도한 대로 작동하는가”를 다뤘습니다.
출처: Pexels
자동매매 봇 재시작 후 가장 위험한 버그 — 손절 라인이 달라졌다
재시작 전과 동일한 포지션인데도, 재시작 후에는 손절·익절 라인이 다른 위치에 걸리는 문제가 있었어요. 에러 로그도 없고, 봇은 정상적으로 돌아가는 것처럼 보였기 때문에 발견하기가 쉽지 않은 유형의 버그였습니다.
봇은 진입할 때의 캔들 저점·고점을 기준으로 손절·익절 기준값을 계산해요. 서버가 재시작되면 거래소에서 진행 중인 포지션은 감지되지만, “정확히 어느 캔들에서 진입했는지”는 메모리에서 사라집니다. 이전 코드는 이 경우 “가장 최근 캔들”로 근사값을 계산해서 다시 배치했어요.
실제로 확인한 예시를 들면 이렇습니다. 진입 시점의 캔들 저점이 67,400달러였는데, 몇 시간 뒤 서버가 재시작되는 순간 최근 캔들 저점이 68,200달러로 바뀌어 있었어요. 손절 기준값이 800달러, 약 1.2% 다른 위치에 배치됩니다. 작아 보이지만, 시장 노이즈에 걸려 억울하게 청산되거나, 반대로 예상보다 훨씬 큰 손실을 감수하게 될 수 있어요. 그리고 이 차이가 사일런트하게 발생한다는 게 핵심 문제였어요. 알람도 없고, 로그에도 잡히지 않습니다.
해결 방법은 단순했어요. 진입하는 시점에 손절·익절 계산에 사용한 캔들 기준값들을 DB에 함께 저장하는 것입니다. 재시작 시 근사 계산 대신 DB에서 원래 값을 그대로 불러와서 복원해요. 7개 기준값이 진입 시 단일 쓰기 작업으로 저장되기 때문에 “일부만 저장된 부분 상태”가 발생하지 않아요. DB에 해당 값이 없으면 기존 근사 계산 방식으로 폴백(fall back)해서 이전 버전 호환성도 유지됩니다.
이 개선 후에는 재시작 전후로 손절·익절 라인이 동일한 위치를 유지해요. 오차 없이 원래 전략 의도대로 작동합니다.
고아 주문이 다음 포지션을 즉시 청산시킨다
스탑 주문 하나가 체결되어 포지션이 청산될 때, 봇은 남은 주문들을 취소해야 해요. 그런데 네트워크 오류로 취소가 실패하면 거래소에는 주문이 그대로 남고, 봇 메모리에서는 포지션이 정리된 상태가 됩니다. 이 잔류 주문을 “고아 주문”이라고 부릅니다.
출처: Pexels
문제는 다음 사이클에서 발생해요. 새로운 신호가 나와 포지션에 진입하는 순간, 이전 사이클에서 미처 취소되지 않은 스탑 주문이 새 포지션을 즉시 청산시킵니다. 이유를 알 수 없는 즉시 청산이 발생하는 거예요. 원인이 몇 사이클 전의 취소 실패였다는 걸 파악하기가 쉽지 않아요.
두 가지 안전망을 추가했어요. 첫째, 취소 실패 즉시 Slack 알림을 보내고 재시도 큐에 등록해요. 이후 모니터 사이클마다 재시도를 반복합니다. 둘째, 새 포지션에 진입하기 전에 잔류 주문 취소 완료 여부를 확인해요. 취소가 완료되지 않은 상태라면 진입 자체를 차단합니다. “고아 주문이 있는 상태에서는 절대 새 포지션에 진입하지 않는다”는 가드가 추가된 거예요.
재시작할 때마다 멀쩡한 주문을 지웠다 다시 만들었다
이전 코드는 재시작 시 무조건 거래소의 스탑 주문 전부를 취소하고 처음부터 다시 배치했어요. 설정이 전혀 바뀌지 않았어도, 핫리로드(코드 변경 없이 프로세스만 재시작)를 해도 동일했습니다. 재시작 한 번에 약 8회의 불필요한 API 호출이 발생했어요.
취소 자체가 실패하면 기존 주문과 새로 만든 주문이 동시에 걸리는 잔류 충돌 위험도 있었어요. 트레일링 스탑의 경우 재시작 사이에 가격이 움직였으면 추적을 처음부터 다시 시작하게 되는 문제도 있었습니다.
개선 방향은 이렇습니다. 진입 시 청산 규칙 설정값과 배치된 주문 ID 목록을 DB에 함께 저장해요. 재시작 시 아래 4가지 조건을 순서대로 검증합니다.
주문 보존 4가지 조건 (모두 충족해야 보존)
- DB에 청산 규칙 설정 스냅샷과 주문 ID 목록이 저장되어 있음
- 현재 설정값과 DB 스냅샷이 일치함 (6개 핵심 필드 비교)
- DB에 기록된 주문 ID가 거래소에 모두 살아있음
- 거래소에 DB에 없는 잉여 주문이 없음
하나라도 어긋나면 기존 방식(전부 취소 후 재배치)으로 안전하게 폴백해요. 실제 운영에서는 이런 로그를 확인했어요. “기존 스탑 주문 보존: 4개 (config 스냅샷 일치, 거래소 주문 모두 존재)” — 아무것도 바뀐 게 없으면 건드리지 않고 넘어가는 거예요. 반대로 설정이 바뀌었거나 주문이 누락되면 청산 주문 누락으로 판단하고 안전 재배치로 넘어갑니다.
단, 한 가지 한계가 있어요. 이 보존 기능은 크래시·핫리로드·API 재시작 시나리오에서만 작동해요. 사용자가 직접 봇을 정상 종료(stop → start)하면 종료 시점에 주문이 취소되기 때문에 보존 대상이 없어집니다.
트레일링 스탑 체결 직후 DB가 업데이트되지 않는 문제는 왜 생겼을까요?
트레일링 스탑은 가격이 유리하게 움직일 때 기존 주문을 취소하고 새 가격으로 재배치하는 방식이에요. 기존 주문을 취소하는 과정에서 “이미 체결됨”이 확인되면, 해당 주문 ID를 메모리에서 비워요. 그런데 이 처리를 DB에는 반영하지 않았습니다.
출처: Pexels
이 순간에 프로세스가 재시작되면 어떻게 될까요? DB에는 이미 체결된, 즉 거래소에 더 이상 존재하지 않는 주문 ID가 남아 있어요. 재시작 시 그 ID로 “주문이 살아있는지” 검증하면 없으니까 누락으로 판정되고, 바로 위에서 다룬 주문 보존 기능이 실패해서 전체 재배치로 넘어갑니다.
직접적으로 큰 손해가 나는 버그는 아니에요. 어차피 재배치로 폴백되니까요. 하지만 불필요한 재배치 방지를 위해 만들어놓은 기능이 이 케이스에서 작동하지 않는 문제였어요. 해결은 한 줄 추가로 끝났어요. “이미 체결됨” 확인 후 메모리를 정리할 때 DB도 동시에 업데이트하는 것입니다.
자주 묻는 질문
Q. 재시작 후 손절 라인이 달라지는 버그는 얼마나 자주 발생했나요?
A. 재시작이 일어나는 모든 경우에 발생했어요. 크래시, 배포, 핫리로드 등 재시작 원인과 무관하게, 포지션이 열린 상태에서 재시작되면 항상 최근 캔들로 재계산했습니다. 에러 메시지가 없기 때문에 의도적으로 확인하지 않으면 발견하기 어려운 버그예요.
Q. DB에 저장하는 방식이 성능에 영향을 주지 않나요?
A. 진입 시 한 번 추가 쓰기가 발생하는 수준이라 실질적인 성능 영향은 없어요. 더 중요한 것은 7개 기준값이 단일 쓰기 작업으로 저장되어 “일부만 저장된 부분 상태”가 없다는 점입니다. DB 쓰기 자체가 실패하면 경고를 남기고 메모리 기반으로 계속 작동해요.
Q. 고아 주문이 생기면 즉시 알 수 있나요?
A. 이번 개선 후에는 취소 실패 즉시 Slack 알림을 받아요. 이전에는 알림이 없었기 때문에 다음 사이클에서 이상한 즉시 청산이 발생해도 원인을 추적하기 어려웠습니다. 지금은 취소 실패가 발생하면 알림이 오고, 재시도가 완료되지 않으면 새 포지션 진입 자체가 차단됩니다.
Q. 이런 버그들은 백테스트에서 발견할 수 있나요?
A. 백테스트로는 발견이 어려워요. 백테스트는 서버 재시작이나 네트워크 오류 같은 인프라 이벤트를 시뮬레이션하지 않거든요. 근데 이런 버그는 백테스트에서 절대 안 잡혀요. 실제 운영 환경에서만 표면화됩니다. 운영 로그를 꼼꼼히 분석하거나, 재시작 전후 포지션 상태를 직접 비교해봐야 발견할 수 있어요.
빌더 관점 — 에러가 없는 버그가 더 무서운 이유
이번 개선 작업에서 가장 인상적이었던 건 네 문제 모두 에러 메시지가 없었다는 점이에요. 봇은 정상 작동 중이고, 로그도 깔끔하고, 주문도 잘 걸려 있어요. 그런데 내부 상태가 의도한 것과 달라요. 저는 이런 유형을 “사일런트 버그”라고 부르는데, 자동화 시스템에서 가장 위험한 종류입니다.
손절 라인이 800달러 틀린 것이 티가 나려면, 실제로 그 근방에서 청산이 일어나야 해요. 아무 일도 없으면 모르고 지나가요. 이 버그를 발견한 건 재시작 전후 포지션 데이터를 직접 비교해보면서였는데, 그전까지는 몇 주 동안 버그가 있는 상태로 운영했을 가능성이 있어요.
자동매매 봇처럼 무인 운영 시스템을 만들면서 배운 게 있어요. “돌아가는 것”과 “의도한 대로 돌아가는 것”은 달라요. 재시작 같은 인프라 이벤트를 거쳐도 내부 상태가 일관성을 유지하는지 직접 확인하는 루틴이 필요합니다. 이번 작업처럼 한 사이클에 4가지 버그가 동시에 나오는 경우는 흔하지 않지만, 운영을 계속할수록 이런 숨겨진 불일치가 하나씩 나오더라고요.
실전 운영 후기가 궁금하다면 코인 자동매매 봇 후기 — 4연패 끝에 첫 수익, 그래도 봇을 계속 돌리는 이유도 참고해보세요. 안정성 개선 이야기와 실제 거래 결과를 같이 읽으면 맥락이 더 잘 잡혀요.
이 시리즈의 전체 기록은 코인 자동매매 봇 프로젝트 전체 기록에서 확인할 수 있어요. 초기 구축부터 지금까지 경험한 내용을 순서대로 정리해뒀습니다.
비슷한 유형의 버그를 운영 중에 발견했거나, 자동매매 봇 안정성 관련해서 궁금한 부분이 있으면 댓글로 남겨주세요.
Written by 비온 (Bion)
코인 자동매매 봇과 주식 분석 웹앱을 직접 운영하는 빌더. BTC/USDT 자동매매 봇을 AWS Lightsail에 24시간 배포해 운영 중이며, Claude Code 기반 블로그 자동화 파이프라인도 직접 만들었다.
이 글은 투자 추천이 아닙니다. 모든 투자 판단과 책임은 본인에게 있습니다.