자동매매에 거는 안전장치 — 한도는 매수만 막아야 한다

자동매매의 위험은 전략보다 운영에서 옵니다. 주문 한도, 운용 자본, 확인 절차, 생존 감시를 어떻게 설계해야 하는지, 그리고 안전장치가 손절을 막는 역설을 어떻게 피하는지 설명합니다.

최종 수정 2026-09-18

봇은 실수를 반복한다

사람이 주문을 잘못 내면 한 번의 실수로 끝납니다. 봇이 잘못되면 같은 실수를 몇 초마다 반복합니다. 가격 데이터 하나가 잘못 연결되어 수량이 백 배로 계산되면, 사람은 주문 화면에서 이상함을 알아채지만 봇은 그대로 주문합니다.

그래서 자동매매의 안전은 "좋은 전략"이 아니라 "전략이 틀렸거나 코드가 고장 났을 때 얼마나 잃는가"로 설계합니다. 전략의 기대 수익과 무관하게, 최악의 경우에 상한이 있어야 합니다.

네 가지 한도

1회 주문액 상한
한 번의 주문에 나갈 수 있는 최대 금액입니다. 수량 계산이 고장 나도 주문 하나가 계좌를 비우지 못하게 합니다.
하루 주문 수
봇이 무한 루프에 빠져도 하루에 낼 수 있는 주문의 수가 정해져 있습니다.
한계 무엇을 "주문"으로 셀지가 중요합니다. 이 서비스에서는 시세 조회 실패로 증권사에 도달하지도 못한 주문 20건이 하루 한도를 다 써 버려, 장이 열린 내내 정상적인 매수가 막힌 적이 있습니다. 지금은 증권사가 접수한 주문만 셉니다.
동시 보유 종목 수
포지션이 끝없이 늘어나는 것을 막습니다. 이미 보유한 종목의 추가 매수는 종목 수를 늘리지 않으므로 이 한도에 걸리지 않습니다.
운용 자본
계좌 전체 중 봇에 맡기는 몫입니다. 아래에서 따로 설명합니다.

"한 번에 얼마"와 "합쳐서 얼마"는 다르다

이 서비스의 설정 화면에는 한때 "자산의 60%"라는 항목이 있었습니다. 사용자는 이것을 "내 돈의 60%만 봇에 맡긴다"로 읽었습니다. 실제로 그 값은 1회 주문의 상한이었습니다. 한 번에 60%까지 살 수 있다는 뜻이었고, 여러 번 사면 계좌 전체가 쓰였습니다.

기록을 보니 그 한도에 걸린 주문은 0건이었습니다. 가장 큰 주문이 계좌의 5.5%였기 때문입니다. 한 번도 작동한 적 없는 한도는 없는 한도와 같습니다. "봇이 최대 얼마까지 들고 있을 수 있는가"를 정하는 개념 자체가 없었던 것입니다.

지금의 운용 자본 한도는 봇이 넣은 원금의 합으로 셉니다. 현재 평가액으로 세지 않는 데는 이유가 있습니다. 평가액으로 세면 보유 종목이 올라 평가익이 났다는 이유만으로 한도가 차서 새 매수가 막히고, 반대로 평가손이 나면 한도에 여유가 생겨 더 살 수 있게 됩니다. 잃고 있을 때 더 사게 만드는 한도는 사용자가 의도한 것의 정반대입니다.

한도가 손절을 막는 역설

안전장치 설계에서 가장 중요한 원칙입니다. 하루 주문 수 한도가 20건이고 봇이 오늘 20건을 주문했다고 합시다. 그 뒤 보유 종목이 손절선에 닿았습니다. 한도가 모든 주문을 막는다면 손절 주문도 나가지 못합니다. 안전을 위해 만든 장치가 손실을 키웁니다.

그래서 모든 한도는 매수에만 적용합니다. 매도, 손절, 주문 취소는 위험을 줄이는 행동이므로 어떤 한도에도 걸리지 않습니다. 같은 원칙이 여러 곳에 적용됩니다.

  • 악재 공시가 나온 종목의 신규 매수는 일정 기간 막지만, 그 종목의 매도는 막지 않습니다.
  • 체결이 확인되지 않은 주문이 있을 때 매수는 멈추지만, 접수 여부가 불명확한 주문 하나가 같은 종목의 손절을 영원히 막지는 못하게 합니다.
  • 한 주기의 실행이 오류로 실패해도 봇을 멈추지 않습니다. 대부분 일시적인 통신 오류이고, 봇이 멈추면 보유 포지션의 손절이 실행되지 않기 때문입니다.
  • 권한 재확인이 일시적으로 실패하면 매수는 보류하지만 매도는 마지막으로 확인된 권한으로 진행합니다.

셀 수 없으면 막는다

한도를 검사하려면 "오늘 몇 건 주문했는가", "지금 몇 종목 들고 있는가"를 조회해야 합니다. 이 조회가 실패하면 어떻게 해야 할까요. 흔한 구현은 오류가 나면 0을 돌려주는 것입니다. 그러면 "주문 0건, 보유 0종목"이 되어 한도가 통째로 사라집니다.

올바른 방향은 반대입니다. 셀 수 없으면 아주 큰 수를 돌려주어 매수를 막습니다. 확인할 수 없는 한도는 없는 한도이기 때문입니다.

실제로 컬럼 이름 하나가 틀려 보유 포지션 조회가 매번 실패하고 있었고, 오류를 삼키는 코드가 빈 목록을 돌려주고 있었습니다. 위험 점검은 오랫동안 "보유 종목이 하나도 없다"는 전제로 돌아갔습니다. 아무 오류도 보이지 않았습니다.

단, 이 원칙에도 방향이 있습니다. 매수는 "모르면 막는다"이지만, 포지션 삭제는 "모르면 건드리지 않는다"입니다. 잔고 조회가 실패했을 때 그것을 "전량 매도되었다"로 읽으면 멀쩡한 포지션 기록이 지워지고 손절 관리에서 빠집니다.

사람이 개입하는 지점

  • 기본값은 모의입니다. 설정을 빠뜨린 요청이 실계좌 주문이 되는 일이 없도록, 실거래는 항상 명시적으로 켜야 합니다.
  • 실거래 시작에는 확인 문구를 직접 입력합니다. 체크박스는 습관적으로 눌립니다. 문구를 타이핑하는 몇 초가 실수를 거릅니다.
  • 주문 요청은 자동으로 재시도하지 않습니다. 통신 오류처럼 보여도 주문은 이미 접수되었을 수 있고, 재시도는 중복 체결이 됩니다.
  • 사람의 매매가 우선입니다. 사용자가 직접 판 종목을 봇이 여전히 보유 중으로 알고 있으면, 없는 주식을 계속 팔려 하고 새 매수 여력도 잘못 계산합니다. 봇은 주기적으로 실제 잔고에 자기 기록을 맞춥니다.
  • 판정 화면에는 주문 버튼이 없습니다. "진입 조건 통과"를 보자마자 누를 수 있으면 규칙을 검토하는 단계가 사라집니다.

봇이 죽었을 때 알 수 있는가

화면을 닫아도 서버에서 계속 도는 자동매매는 눈에 보이지 않습니다. 프로세스가 죽어도 화면에는 여전히 "위임 중"이라고 표시될 수 있습니다. 사용자는 봇이 일하고 있다고 믿지만 실제로는 진입도, 손절도 실행되지 않습니다.

그래서 실행 프로세스는 주기마다 생존 신호를 남기고, 그 신호가 90초 넘게 끊기면 화면이 가장 눈에 띄는 방식으로 알립니다. 반대 방향의 사고도 있습니다. 같은 프로세스가 실수로 두 벌 뜨면 오류 없이 둘 다 돌면서 주문을 이중으로 냅니다. 새로 뜨는 프로세스는 다른 살아 있는 인스턴스가 있는지 확인하고, 있으면 스스로 시작을 거부합니다.

성공한 주문만 보면 안 된다

봇의 성적표는 보통 체결된 거래의 승률과 수익률을 보여 줍니다. 하지만 실패한 주문은 그 표에 없습니다. 이 서비스의 봇은 한때 매수 주문의 56%가 실패하고 있었는데, 성적 화면 어디에도 그 사실이 없어 데이터베이스를 직접 조회하고서야 발견했습니다.

감시는 원인이 아니라 증상에 걸어야 합니다. 이번 사고의 원인을 막는 검사를 넣어도 다음 사고의 원인은 다릅니다. "주문 실패율이 평소보다 높다", "체결가가 그날 종가와 크게 다르다" 같은 증상은 원인이 무엇이든 드러납니다.

자동매매를 맡긴다는 것은 판단을 맡기는 것이 아니라 실행을 맡기는 것입니다. 얼마까지 잃을 수 있는지, 고장 나면 어떻게 알 수 있는지, 어떻게 멈추는지를 먼저 정하고 나서 전략을 고르는 순서가 맞습니다.

광고