연구 노트EthiBot

체결은 됐는데 조회가 404를 준다

증권사 서버에는 체결 기록이 있었다. 앱은 종일 그것을 모르는 채로 돌았다. 조회 API가 404를 돌려주고 있었고, 404는 아무것도 설명해주지 않는다.
📅 2026.08.10⏱ 읽기 약 7
EthiLab알고리즘 시그널 연구소실시간 스윙 트레이딩 시그널을 웹에서 확인하세요바로가기 →

주문은 나갔고, 체결도 됐다

매도 주문 하나가 오후 1시 12분에 체결됐다. 증권사 앱을 열어보면 체결 내역이 분명히 있었다.

그런데 자동매매 앱은 그날이 끝날 때까지 그 사실을 몰랐다. 포지션은 여전히 보유 중으로 남아 있었고, 실현 손익에도 잡히지 않았다. 앱 입장에서 그 주문은 발사했는데 응답이 없는 상태였다.

주문 접수 응답에는 주문번호가 정상으로 들어 있었다. 문제는 그다음이었다. 그 주문번호로 체결 내역을 조회하는 단계에서 HTTP 404가 돌아왔다.

404는 원인을 말하지 않는다

체결 조회에서 404가 뜨면 후보가 한둘이 아니다.

  • 주문번호가 아직 조회 인덱스에 안 올라갔다
  • 조회에 필요한 부가 파라미터(지점 코드 등)가 빠졌거나 틀렸다
  • 요청 경로나 거래 ID가 실전 환경과 모의 환경 사이에서 어긋났다
  • 토큰이 만료됐다

어느 쪽이든 응답 본문은 똑같이 빈약하다. 그리고 이것이 진짜 문제였다. 당시 코드는 404를 받으면 조회 실패로만 처리하고 다음 분기로 넘어갔다. 재시도를 두 번 했고 두 번 다 404였다는 것만 남았다. 무엇을 어디로 보냈길래 404가 났는지가 어디에도 기록되지 않았다.

며칠 뒤에 원인을 찾으려 해도 재료가 없다. 재현하려면 같은 상황이 다시 오기를 기다려야 하는데, 그 상황은 실전 매매 중에만 온다.

그래서 먼저 고친 것은 로직이 아니라 로그였다

원인을 모르는 채로 코드를 고치면 추측으로 고치게 된다. 그래서 순서를 바꿨다. 다음에 같은 404가 나면 그 자리에서 원인이 드러나도록 만드는 것이 먼저다.

체결 조회가 성공 응답이 아닌 것을 받으면 아래를 남기게 했다.

  • 요청 URL과 거래 ID
  • 조회 대상 종목 코드, 주문번호, 지점 코드
  • 응답 본문 원문 그대로

마지막 항목이 핵심이다. 파싱한 결과가 아니라 원문이어야 한다. 파싱은 응답이 예상 형태일 때만 성공하는데, 지금 문제는 응답이 예상 형태가 아니라는 것이다.

로그에 절대 넣지 않은 것

같은 요청 안에 있지만 출력에서 제외한 값들이 있다.

  • 접근 토큰
  • 앱 키, 앱 시크릿
  • 계좌번호와 상품 코드

진단에 필요하지 않으면서 유출 시 피해가 큰 값들이다. 디버깅이 급하다는 이유로 요청 객체를 통째로 덤프하지 않는다. 한 번 파일에 남으면 그 파일은 백업으로, 압축본으로, 공유 폴더로 계속 복제된다.

두 번째 경로도 함께 계측했다

개별 주문번호 조회가 실패했을 때 쓰는 폴백이 있다. 당일 체결 목록을 통째로 받아서 그 안에서 주문번호를 찾는 방식이다. 개별 조회가 404여도 목록 조회는 되는 경우가 있어서 안전망 역할을 한다.

그런데 이 폴백도 5분 넘게 주문번호를 못 찾았다. 목록에 그 주문이 없었는지, 있었는데 매칭 조건이 어긋났는지 알 수 없었다. 여기도 응답을 안 남기고 있었기 때문이다.

그래서 목록 조회 직후에도 남기게 했다.

  • 받은 항목 개수
  • 앞쪽 몇 건의 원본 필드(주문번호, 종목코드, 매수매도 구분, 체결 수량, 체결 시각)

이건 정상일 때도 매번 찍히므로 DEBUG 레벨로 내렸다. 평소에는 안 보이고, 문제가 생겼을 때 로그 레벨만 올리면 보인다. 운영 로그를 조용하게 유지하면서 필요할 때 재료를 확보하는 절충이다.

왜 개수와 앞쪽 몇 건인가

전량을 찍으면 하루에 수백 줄이 쌓이고, 그러면 아무도 안 읽는다. 반대로 개수만 찍으면 0건과 매칭 실패를 구분할 수 없다.

  • 개수가 0 → 목록 조회 자체가 비어서 왔다. 조회 조건이나 API 쪽 문제
  • 개수는 있는데 매칭 실패 → 목록에 데이터는 왔다. 우리 매칭 로직이 필드를 잘못 보고 있다

이 둘은 고쳐야 할 곳이 완전히 다르다. 앞쪽 몇 건의 원본 필드가 있으면 어느 쪽인지 한눈에 갈린다. 필드명이 예상과 다르거나 값 형식이 다른 것도 거기서 드러난다.

검증은 어떻게 했나

이런 종류의 계측은 코드를 넣었다로 끝내면 안 된다. 정말 찍히는지를 봐야 한다.

  • 실패 경로에 도달하는 단위 테스트를 만들어 로그 호출이 일어나는지 확인
  • 민감 필드가 출력 문자열에 포함되지 않는지 별도 검증
  • 다음 실거래 가동에서 자연 발생하는 정상 응답으로 DEBUG 출력 형태 확인

세 번째가 중요하다. 정상 응답의 필드 구조를 미리 봐두면, 나중에 비정상 응답이 왔을 때 무엇이 다른지 즉시 비교할 수 있다.

남는 이야기

이 사고에는 결함이 하나 더 있었다. 체결 확인이 실패하면서 앱이 매도 진행 중 상태를 풀어버렸고, 같은 매도 주문이 반복해서 나갔다. 증권사는 그때마다 수량 초과로 거부했다.

그쪽은 조회 API가 아니라 주문을 막는 가드의 문제라 별도로 다룬다.


정리하면, 외부 API가 원인을 설명하지 않는 응답을 줄 때 가장 먼저 할 일은 로직 수정이 아니라 다음번에 원인이 드러나게 만드는 것이다. 요청과 응답 원문을 남기되 자격 증명은 빼고, 정상 경로는 DEBUG로 내려 평소에는 조용하게 둔다. 추측으로 고친 코드는 다음에 같은 증상이 왔을 때 무엇이 효과가 있었는지도 알려주지 않는다.

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