연구 노트EthiBot

같은 매도 주문이 12번 나갔다

증권사가 수량 초과로 계속 거부했다. 주문을 막는 가드는 있었는데, 그 가드가 체결 확인 실패와 함께 풀리고 있었다.
📅 2026.08.10⏱ 읽기 약 7
EthiLab알고리즘 시그널 연구소실시간 스윙 트레이딩 시그널을 웹에서 확인하세요바로가기 →

거부 로그가 쌓이고 있었다

매도 주문 하나가 증권사에서는 이미 체결됐는데 앱이 그걸 인식하지 못한 날이었다. 그날 로그에는 같은 종목에 대한 주문 거부가 열두 번 넘게 찍혀 있었다. 사유는 전부 같았다. 주문 가능 수량 초과.

당연하다. 보유 수량은 한 번의 매도로 이미 다 나갔다. 그런데 앱은 그 매도가 체결된 줄 모르니 포지션이 아직 있다고 믿었고, 매도 조건이 여전히 충족되니 매 틱마다 다시 주문을 냈다. 틱 간격이 10초대였으므로 몇 분 만에 열 번이 넘었다.

실질 피해는 없었다. 증권사가 전부 거부했으니 이중 매도는 일어나지 않았다. 하지만 이건 운이 좋았던 것이다. 거부해준 쪽은 증권사였고, 우리 코드에는 그걸 막는 것이 없었다.

가드는 있었는데 왜 안 걸렸나

매도 신호가 중복 발사되는 것을 막는 플래그가 이미 있었다. 매도를 시작할 때 켜고, 끝나면 끄는 방식이다.

문제는 끝난다의 정의였다. 이 플래그는 주문 발사 후 체결 응답을 받기까지의 임시 상태였다. 체결 확인이 실패하면 코드는 이 주문을 미확인 대기 목록에 올리고, 플래그를 꺼서 정상 흐름으로 복귀시킨다.

의도 자체는 타당하다. 플래그를 켠 채로 두면 조회 실패 한 번에 그 종목이 영원히 잠긴다. 그래서 풀어준다.

그런데 풀어준 직후 매도 평가 루프가 다시 돌면서 같은 조건이 또 충족됐다. 플래그는 꺼져 있고, 미확인 대기 목록은 아무도 확인하지 않았다. 그래서 또 발사했다.

두 상태는 서로 다른 것을 지킨다

여기서 갈린 것은 상태 하나로 두 가지를 지키려 한 설계였다.

상태지키는 것수명
매도 진행 중 플래그주문 발사와 응답 수신 사이의 중복 발사초 단위
미확인 주문 목록체결 여부가 확정되지 않은 주문분 단위

두 번째가 첫 번째보다 훨씬 오래 간다. 조회가 실패하면 그 주문은 체결됐는지 아닌지 모르는 채로 남는데, 그 상태에서 새 주문을 내는 것은 그 자체로 위험하다. 체결됐다면 수량 초과이고, 안 됐다면 중복 주문이다. 어느 쪽이든 내면 안 된다.

그래서 미확인 주문 목록을 독립적인 차단 조건으로 승격시켰다.

어디에 넣느냐가 절반이다

가드를 추가할 때 위치를 두고 판단이 필요했다. 매도 평가 루프는 대략 이런 순서다.

포지션 순회
  → 진행 중 플래그 확인
  → 현재가 조회          ← 외부 API 호출
  → 매도 조건 평가
  → 주문 발사

새 가드를 현재가 조회 앞에 뒀다. 조건 평가 뒤에 두면 이미 API를 한 번 때린 뒤다. 어차피 주문을 안 낼 종목이라면 시세 조회도 낭비이고, 증권사 호출 한도를 갉아먹는다.

그리고 진행 중 플래그와 별개로 독립 판정하게 했다. 하나가 풀려도 다른 하나가 살아 있어야 이번 사고가 막힌다. 원래 결함이 정확히 "한쪽이 풀리자 아무것도 안 남았다"였다.

영구 잠금은 어떻게 피했나

여기서 처음의 딜레마가 돌아온다. 미확인 주문으로 차단하면, 그 주문이 영원히 미확인이면 그 종목은 영원히 잠긴다.

그건 이미 다른 장치가 해결하고 있었다. 미확인 주문은 일정 시간이 지나면 만료 처리된다. 만료되면 당일 체결 목록을 다시 조회해서 체결 여부를 확정하고, 목록에서 제거한다. 그래서 차단은 그 시간 안에서만 유지된다.

가드를 넣기 전에 이 만료 경로가 실제로 도는지부터 확인했다. 차단만 추가하고 해제 경로가 없으면 사고가 잠금으로 바뀔 뿐이다.

매수는 왜 안 막았나

같은 가드를 매수에도 걸까 고민했지만 걸지 않았다. 미확인 매수 주문이 있는 종목에 매도가 나가는 것은 막을 이유가 없다. 정상적인 시나리오는 아니지만, 막아서 얻는 것보다 잃는 것이 크다. 매수 체결 확인이 지연되는 동안 손절이 걸려야 하는 상황이라면 손절이 나가야 한다.

차단은 같은 방향의 중복만 막으면 된다. 방향 구분 없이 막으면 안전해 보이지만 실제로는 청산 경로를 함께 잠근다.

검증 항목

  • 미확인 매도 주문이 있는 동안 새 매도 신호가 발사되지 않는가
  • 진행 중 플래그가 풀린 뒤에도 차단이 유지되는가
  • 만료 시간이 지나면 차단이 해제되는가
  • 미확인 매수 주문은 매도를 막지 않는가
  • 차단 시 시세 조회가 실행되지 않는가

마지막 항목은 잊기 쉽다. 가드를 넣었는데 위치가 잘못돼 API 호출은 그대로 나가는 경우가 있다. 로그에 조회 흔적이 남는지로 확인했다.

앞 이야기

이 사고의 출발점은 체결 조회가 404를 돌려준 것이었다. 그쪽은 왜 404가 났는지를 다음에 알 수 있게 만드는 이야기라 따로 다뤘다.


정리하면, 상태 플래그 하나로 서로 수명이 다른 두 가지를 지키려 하면 짧은 쪽이 풀릴 때 긴 쪽도 같이 풀린다. 체결 여부가 확정되지 않은 주문은 그 자체로 새 주문을 막아야 하고, 그 차단은 다른 플래그와 독립이어야 하며, 외부 호출 앞에 있어야 하고, 만료 경로와 짝이어야 한다. 이번에는 증권사가 대신 막아줬지만 그건 설계가 아니다.

시그널 전략 상세는 EthiLab에서 확인할 수 있습니다.