자동매매에서 가장 무서운 버그는 상태 불일치다
자동매매에서 가장 위험한 순간은 봇이 꺼진 때보다, 봇이 믿는 상태와 거래소 현실이 조용히 어긋난 때다. 상태 정합성과 reconciliation을 점검한다.
자동매매 봇을 운영하다 보면 가장 안심되는 순간은 시스템이 켜져 있을 때처럼 보인다. 프로세스가 살아 있고, 로그가 찍히고, 주기적으로 가격을 읽고 있다면 일단 정상처럼 느껴진다. 하지만 실전 운영에서는 이 감각이 가끔 위험하다. 봇이 살아 있다는 것과 봇이 현실을 정확히 알고 있다는 것은 같은 말이 아니기 때문이다.
트레이딩 봇은 시장을 직접 만지는 것이 아니라, 데이터와 주문 응답과 내부 기록을 통해 현실을 이해한다. 그런데 이 현실은 계속 바뀐다. 가격은 움직이고, 주문은 체결되거나 취소되고, 포지션 수량은 변하고, 보호주문은 생성되거나 사라진다. 이때 봇 내부가 믿는 상태와 거래소가 보여주는 실제 상태가 조금씩 어긋날 수 있다.
이 어긋남은 처음에는 작아 보인다. 주문 하나가 일부만 체결되었거나, 마지막으로 처리한 봉이 한 번 더 처리되었거나, 내부에는 포지션이 없다고 기록되어 있는데 거래소에는 아주 작은 잔량이 남아 있는 식이다. 하지만 자동매매에서 작은 상태 불일치는 시간이 지나며 큰 문제로 번질 수 있다.
자동매매에서 가장 위험한 순간은 계산식이 틀린 순간보다, 봇이 믿는 현실과 실제 계좌의 현실이 조용히 달라지는 순간이다.
봇이 켜져 있다고 정상은 아니다
사람은 시스템이 멈추면 바로 문제라고 느낀다. 프로세스가 죽었거나, 서버가 응답하지 않거나, 에러가 크게 터지면 장애라는 사실이 비교적 명확하다. 하지만 더 까다로운 문제는 시스템이 겉으로는 살아 있는데 내부 상태가 현실과 달라진 경우다.
예를 들어 봇은 마지막 진입 주문이 취소되었다고 믿고 있을 수 있다. 그런데 거래소에는 아직 열린 주문이 남아 있을 수 있다. 봇은 포지션이 정리되었다고 기록했을 수 있다. 그런데 실제 계좌에는 일부 수량이 남아 있을 수 있다. 봇은 보호주문이 걸려 있다고 믿지만, 거래소에서는 그 주문이 취소되었거나 수량이 맞지 않을 수 있다.
이런 상태에서는 프로세스가 살아 있다는 사실이 큰 위로가 되지 않는다. 오히려 더 위험할 수 있다. 시스템은 자신이 정상이라고 믿고 계속 다음 판단을 하기 때문이다. 봇이 잘못된 현실 위에서 판단을 이어가면, 전략이 맞고 틀리고의 문제가 아니라 운영 전체가 엉뚱한 방향으로 움직일 수 있다.
그래서 Operate 관점에서 첫 번째 질문은 “봇이 켜져 있는가?”가 아니다. 더 중요한 질문은 이것이다. 봇이 지금 믿고 있는 상태는 실제 시장과 계좌의 상태와 같은가?
상태 불일치는 무엇인가
상태 불일치는 봇 내부가 기억하거나 추정하는 상태와 외부 현실이 맞지 않는 상황을 말한다. 여기서 외부 현실은 주로 거래소의 주문 상태, 실제 포지션, 체결 내역, 계좌 잔고처럼 봇 바깥에 존재하는 기준이다.
상태 불일치가 생기는 이유는 단순하다. 자동매매는 여러 단계의 사건을 순서대로 처리해야 하는데, 현실은 항상 깔끔한 순서로 움직이지 않는다. 주문을 보냈지만 응답이 늦을 수 있다. 응답은 받았지만 체결은 일부만 될 수 있다. 체결은 되었지만 내부 기록 업데이트가 실패할 수 있다. 서버가 재시작되었는데 이전 상태를 어디서부터 복원해야 할지 애매할 수도 있다.
이런 문제는 코드가 허술해서만 생기는 것이 아니다. 라이브 시장에서는 네트워크, 거래소 API, 체결 지연, 부분 체결, 주문 취소, 서버 재시작, 사람의 수동 개입이 모두 상태를 흔들 수 있다. 그래서 좋은 봇은 “상태 불일치가 절대 생기지 않는다”고 가정하지 않는다. 오히려 언젠가는 어긋날 수 있다고 보고, 다시 맞추는 구조를 갖는다.
Internal State
≠
Exchange Reality
If this gap stays invisible,
the bot keeps making decisions
on the wrong version of reality.
봇에는 최소 네 가지 상태가 있다
상태라고 하면 하나의 값처럼 느껴질 수 있지만, 트레이딩 봇 안에는 여러 종류의 상태가 함께 존재한다. 최소한 네 가지는 분리해서 보는 편이 좋다. 시장 상태, 전략 상태, 주문 상태, 포지션 상태다.
Market State
시장 상태는 바깥에서 들어오는 관측값이다. 가격, 캔들, 거래량, 호가, 체결, 변동성 같은 데이터가 여기에 들어간다. 이 상태에서 중요한 질문은 데이터가 충분히 최신인지, 아직 확정되지 않은 값을 확정된 값처럼 쓰고 있지는 않은지, 마지막으로 처리한 데이터가 무엇인지다.
Strategy State
전략 상태는 봇이 시장을 어떻게 해석하고 있는지에 관한 내부 판단이다. 지금이 진입 대기인지, 보유 중인지, 청산 후보인지, 어떤 레짐으로 보고 있는지, 같은 신호를 이미 처리했는지 같은 정보가 여기에 속한다. 전략 상태가 꼬이면 같은 신호를 중복 처리하거나, 이미 지나간 조건을 다시 현재 조건처럼 볼 수 있다.
Order State
주문 상태는 거래소와 주고받는 요청의 상태다. 주문 요청, 접수, 대기, 부분 체결, 전체 체결, 취소, 거절, 만료 같은 정보가 여기에 들어간다. 주문 상태는 특히 조심해야 한다. 주문을 보냈다는 것과 체결되었다는 것은 다르고, 체결되었다는 것과 내부 포지션 상태가 업데이트되었다는 것도 다르다.
Position State
포지션 상태는 실제 계좌 현실에 가까운 정보다. 보유 수량, 방향, 평균 단가, 미실현 손익, 노출 금액, 보호주문과의 연결 상태가 여기에 들어간다. 자동매매에서 최종적으로 가장 민감한 것은 포지션 상태다. 봇이 믿는 포지션과 거래소의 실제 포지션이 다르면, 그 뒤의 모든 판단은 위험해질 수 있다.
이 네 가지 상태를 한 덩어리로 보면 문제를 찾기 어렵다. 가격 데이터가 늦은 문제인지, 전략이 같은 신호를 두 번 처리한 문제인지, 주문이 일부만 체결된 문제인지, 포지션 기록이 실제 계좌와 다른 문제인지 분리하기 어려워진다. 좋은 운영 구조는 상태를 나누어 보고, 어디에서 어긋났는지 좁혀갈 수 있게 만든다.
내부 상태와 거래소 현실은 언제 어긋날까
상태 불일치는 특별한 사건에서만 생기지 않는다. 오히려 평범한 실행 과정의 작은 틈에서 자주 생긴다. 주문이 나가고, 응답이 오고, 체결이 되고, 보호주문이 붙고, 내부 기록이 업데이트되는 사이에는 여러 단계가 있다. 그중 하나만 예상과 다르게 움직여도 내부와 외부는 달라질 수 있다.
상태가 어긋나는 대표적인 순간
- 주문 요청은 성공했지만 실제 체결은 일부만 일어났다.
- 거래소에는 열린 주문이 남아 있는데 내부 기록에는 취소로 남았다.
- 포지션은 청산된 줄 알았지만 거래소에는 잔량이 남아 있다.
- 보호주문이 취소되었는데 내부 상태는 여전히 보호 중이라고 믿는다.
- 서버 재시작 후 마지막 처리 지점을 잘못 복원했다.
- 같은 캔들이 두 번 처리되어 신호가 중복 실행되었다.
- 사람이 거래소에서 수동으로 주문을 수정했지만 봇이 그것을 모른다.
이런 상황은 모두 다른 모양을 가지고 있지만, 공통점은 하나다. 봇이 보고 있는 현실과 실제 계좌의 현실이 다르다는 것이다. 이 차이가 빨리 드러나면 복구할 수 있다. 하지만 조용히 숨어 있으면 봇은 잘못된 상태 위에서 계속 판단한다.
예를 들어 내부 상태는 포지션이 없다고 믿는데 실제로는 작은 롱 포지션이 남아 있다고 해보자. 이 상태에서 새로운 진입 신호가 나오면 봇은 “첫 진입”이라고 생각할 수 있다. 하지만 실제 계좌에서는 기존 잔량에 추가되는 형태가 된다. 반대로 내부는 포지션을 보유 중이라고 믿지만 거래소에는 이미 청산되어 있다면, 봇은 존재하지 않는 포지션을 보호하거나 청산하려고 할 수 있다.
상태 불일치가 무서운 이유는 여기 있다. 하나의 오류가 다음 판단의 입력이 된다. 잘못된 상태는 잘못된 주문을 만들고, 잘못된 주문은 다시 더 복잡한 상태를 만든다.
Source of Truth는 어디에 있어야 할까
상태를 다룰 때 꼭 필요한 질문이 있다. 최종 기준, 즉 source of truth를 어디에 둘 것인가다. 모든 상태가 같은 무게를 갖는 것은 아니다. 내부 캐시는 빠른 판단을 위해 필요하지만, 실제 체결 수량과 포지션에 관해서는 거래소가 더 강한 기준에 가깝다.
예를 들어 봇 내부에는 “포지션 없음”이라고 기록되어 있는데 거래소에는 포지션이 남아 있다면, 실전 운영에서는 거래소 쪽을 더 무겁게 봐야 한다. 내부 기록이 아무리 깔끔해도 자본이 노출되어 있는 곳은 실제 계좌이기 때문이다. 반대로 거래소의 현재 상태만 보고 전략의 의도를 완전히 복원할 수는 없다. 그래서 내부 기록도 필요하다. 중요한 것은 둘 중 하나만 믿는 것이 아니라, 각 상태의 역할을 구분하는 것이다.
내부 상태는 시스템이 어떤 판단을 했는지 설명해준다. 거래소 상태는 실제 계좌가 어떤 위험에 노출되어 있는지 말해준다. 좋은 운영 구조는 이 둘을 분리해서 보되, 주기적으로 다시 맞춘다.
내부 상태는 봇이 믿는 이야기이고, 거래소 상태는 실제 계좌가 놓인 현실이다. 좋은 봇은 이 둘이 계속 같은 방향을 가리키는지 확인한다.
Reconciliation: 좋은 봇은 계속 다시 맞춘다
상태 불일치를 줄이기 위해 필요한 개념이 reconciliation이다. 한국어로는 정합성 맞추기, 상태 재조정, 동기화 정도로 이해할 수 있다. 핵심은 간단하다. 봇 내부가 믿는 상태와 외부 현실을 주기적으로 비교하고, 차이가 있으면 드러내고, 필요하면 복구하는 것이다.
예를 들어 봇은 주기적으로 거래소에서 열린 주문 목록을 다시 읽을 수 있다. 현재 포지션 수량을 다시 확인할 수 있다. 내부에 기록된 보호주문 ID가 실제로 살아 있는지 확인할 수 있다. 마지막으로 처리한 캔들과 현재 데이터의 진행 상황을 비교할 수 있다. 이 과정은 화려한 기능은 아니지만, 운영 신뢰도를 크게 만든다.
Reconciliation은 단순히 “동기화” 버튼 하나를 의미하지 않는다. 어떤 상태를 비교할지, 어떤 차이를 허용할지, 어떤 차이는 즉시 알림으로 보낼지, 어떤 경우에는 거래를 멈출지 같은 판단을 포함한다. 그래서 reconciliation은 기술 기능이면서 동시에 운영 정책이다.
좋은 reconciliation이 확인하는 것
- 내부 포지션 수량과 거래소 포지션 수량이 일치하는가?
- 내부에 기록된 열린 주문과 거래소의 열린 주문이 일치하는가?
- 포지션이 있다면 보호주문도 실제로 살아 있는가?
- 부분 체결 이후 보호 수량과 포지션 수량이 맞는가?
- 마지막으로 처리한 시장 데이터가 중복되거나 누락되지 않았는가?
- 내부 상태와 거래소 상태가 다르면 시스템은 알림을 보내는가?
- 심각한 불일치가 있을 때 새 진입을 멈출 수 있는가?
좋은 봇은 상태 불일치가 생겼을 때 무조건 자동으로 고치려고만 하지 않는다. 어떤 불일치는 자동 보정할 수 있지만, 어떤 불일치는 사람의 확인이 필요하다. 예를 들어 단순히 오래된 주문 기록을 정리하는 것은 자동화할 수 있다. 하지만 실제 포지션 수량이 내부 기록과 크게 다르다면, 무리하게 자동 판단을 이어가기보다 새 거래를 멈추고 사람에게 알리는 편이 더 안전할 수 있다.
조용히 어긋나는 시스템이 가장 위험하다
운영에서 가장 다루기 쉬운 장애는 시끄러운 장애다. 프로세스가 죽고, 에러가 발생하고, 알림이 오고, 대시보드가 빨갛게 바뀌면 적어도 무언가 잘못되었다는 사실은 알 수 있다. 반대로 가장 위험한 장애는 조용한 장애다. 겉으로는 정상처럼 보이지만, 실제로는 중요한 상태가 어긋나 있는 경우다.
상태 불일치는 조용한 장애가 되기 쉽다. 로그에는 큰 에러가 없고, 가격 조회도 되고, 봇도 계속 다음 루프를 돈다. 하지만 내부 포지션과 거래소 포지션이 다르거나, 보호주문이 비어 있거나, 같은 신호가 중복 처리되고 있다면 시스템은 이미 위험한 방향으로 움직이고 있다.
그래서 Operate 관점에서는 “문제가 없었다”는 말을 조심해야 한다. 문제가 없었던 것인지, 아니면 문제를 볼 수 있는 장치가 없었던 것인지 구분해야 한다. 로그가 없으면 실패를 모른다. 알림이 없으면 비정상 상태를 늦게 안다. reconciliation이 없으면 내부와 외부가 언제부터 어긋났는지 알기 어렵다.
좋은 운영 구조는 완벽한 시스템을 약속하지 않는다. 대신 시스템이 틀어졌을 때 빨리 드러나게 만든다. 피해 범위를 좁히고, 사람이 확인할 수 있는 흔적을 남기고, 필요하면 새 거래를 멈출 수 있게 만든다. 자동매매에서 신뢰할 수 있는 시스템은 한 번도 틀리지 않는 시스템이 아니라, 틀어졌을 때 스스로 의심하고 멈출 수 있는 시스템에 가깝다.
운영자는 무엇을 봐야 할까
운영자는 매 순간 모든 로그를 읽을 수 없다. 그래서 좋은 봇은 사람이 봐야 할 상태를 요약해서 보여줄 수 있어야 한다. 가격이 얼마인지보다 더 중요한 것은 시스템이 지금 어떤 상태를 믿고 있는지다. 포지션이 있는지, 보호가 있는지, 마지막으로 거래소와 상태를 맞춘 시간이 언제인지, 불일치가 있었는지, 새 진입이 허용된 상태인지가 중요하다.
이런 관점에서 텔레그램 명령, CLI, 대시보드, 리포트는 단순한 편의 기능이 아니다. 운영자가 시스템 상태를 읽는 인터페이스다. 좋은 인터페이스는 화려할 필요가 없다. 대신 운영자가 빠르게 물어봐야 할 질문에 답해야 한다.
운영자가 확인해야 할 핵심 질문
- 봇은 현재 어떤 포지션을 보유 중이라고 믿는가?
- 거래소의 실제 포지션과 그 믿음이 일치하는가?
- 열린 주문과 보호주문은 실제로 살아 있는가?
- 마지막 reconciliation은 언제 성공했는가?
- 현재 새 진입이 허용된 상태인가, 아니면 safe mode인가?
- 최근 불일치가 있었다면 무엇이었고 어떻게 처리되었는가?
이 질문에 빠르게 답할 수 있다면 운영자는 시스템을 더 잘 다룰 수 있다. 반대로 이 질문에 답하기 위해 여러 로그 파일을 뒤지고, 거래소 화면을 열고, 내부 상태 파일을 직접 확인해야 한다면 운영 부담은 커진다. 자동매매에서 좋은 운영 인터페이스는 사람을 배제하는 것이 아니라, 사람이 개입해야 할 지점을 더 명확하게 만든다.
마무리: 좋은 봇은 자신의 상태를 계속 의심한다
Build 관점에서 우리는 봇을 전략 코드가 아니라 데이터, 상태, 판단, 기록이 분리된 시스템으로 보았다. Execute 관점에서는 그 판단이 실제 주문과 포지션이 되는 순간, 보호주문이 왜 중요한지 보았다. Operate 관점에서는 그 다음 질문을 던져야 한다. 시스템이 지금 믿고 있는 현실은 실제 현실과 같은가?
상태 불일치는 자동매매에서 가장 조용하고 까다로운 위험 중 하나다. 계산식이 틀리면 백테스트나 로그에서 비교적 빨리 드러날 수 있다. 하지만 봇이 믿는 포지션과 거래소의 실제 포지션이 조용히 달라지면, 시스템은 잘못된 현실 위에서 계속 판단할 수 있다.
그래서 좋은 봇은 자신의 상태를 계속 의심한다. 내부 기록을 믿되, 거래소 현실과 다시 맞춘다. 포지션이 있으면 보호가 있는지 확인한다. 주문이 있다고 믿으면 실제로 살아 있는지 확인한다. 마지막으로 처리한 데이터가 무엇인지 기억하고, 중복과 누락을 확인한다. 그리고 심각한 불일치가 생기면 계속 거래하기보다 멈추고 알린다.
자동매매에서 운영은 단순히 봇을 켜두는 일이 아니다. 운영은 봇이 믿는 현실과 실제 시장의 현실을 계속 맞추는 일이다. 이 관점이 생기면 좋은 봇의 기준도 달라진다. 좋은 봇은 단지 살아 있는 봇이 아니다. 좋은 봇은 자신이 무엇을 믿고 있는지 알고, 그 믿음이 틀어졌을 때 빨리 알아차리는 봇이다.
좋은 봇은 한 번도 틀리지 않는 봇이 아니다. 좋은 봇은 자신이 믿는 상태를 계속 검증하고, 현실과 어긋났을 때 조용히 계속 달리지 않는 봇이다.
Data / source
보호주문은 왜 진입 신호보다 중요한가
진입 신호보다 먼저, 포지션이 열린 뒤 손절·익절·감시가 실제로 살아 있는지 봐야 한다. 보호주문을 수익률 장식이 아니라 자동매매의 실행 구조로 읽는다.
Read →트레이딩 봇은 전략 코드가 아니라 시스템이다
자동매매를 처음 볼 때 사람의 시선은 대개 전략으로 먼저 향한다. 어떤 지표를 쓰는지, 언제 진입하고 언제 청산하는지, 백테스트 수익률이 얼마나 좋아 보이는지가 먼저 눈에 들어온다. 그래서 많은 사람이 트레이딩 봇을 “매수·매도 규칙을 코드로 옮긴 것” 정도로 이해한다. 물론 전략은 중요하다. 시장에서 무엇을 할지 결정하는 규칙이 없다면 봇은 아무것도 판단할 수 없다. 하지만 실전으로 조금만 가까이 […]
Read →