"승인 5, 거부 없음"이라는 거짓말

첫 무인 매매에서 주문 5건이 소수점 하나로 전량 거부됐는데, 알림은 성공이라고 보고한 날의 사고 분석.

무인 운전 첫날 아침

어제 자동매매 봇을 launchd 상주 데몬으로 올렸다. 등록하자마자 “무인 운전이 시작됐습니다” 알림이 날아와 얼떨떨했는데, 오늘이 월요일 — 시스템이 사람 손 없이 처음으로 주문을 내는 날이다. 흐름은 단순하다. 장 시작 전에 아침 브리핑을 보내고, 9시에 룰 엔진이 매수 시그널을 만들어 증권사 모의계좌에 주문을 넣고, 결과를 텔레그램으로 보고한다.

9시 6분, 매매 결과 메시지가 왔다. “승인 5, 거부 없음.” 성공이다 — 라고 생각했는데 어딘가 이상했다. 체결됐다는 얘기가 어디에도 없었다.

일단 로그부터 까게 했다

Claude Code에 그대로 물었다.

매수했음? 결과 메시지를 안 보낸 것 같은데. 아침 브리핑도 엄청 늦게 왔고.

Claude는 데몬 로그와 DB 기록부터 열었다. 브리핑과 결과 메시지는 제시간에 발송됐고 폰 알림만 늦게 뜬 거였다. 진짜 문제는 따로 있었다. 몇 분 뒤 보고가 올라왔다 — 매수 주문 5건이 전부 증권사 제출 단계에서 거부됐다고.

전량 거부, 그리고 오보

사고는 두 겹이었다.

첫 번째는 거부 자체. 주문 가격을 120800.0처럼 소수점이 붙은 채로 전송하고 있었다. 증권사 API는 가격을 정수로만 받는다. 시세 조회 쪽은 미리 검증해뒀지만 실제 주문 전송은 오늘이 처음이었고, 처음 타는 경로에서 바로 걸렸다.

두 번째가 더 무서웠다. 알림이 “승인 5, 거부 없음”이라고 보고한 것. 알림 로직이 내부 안전장치의 거부만 집계하고 증권사 쪽 거부는 아예 세지 않고 있었다. 시스템 입장에선 “내가 막은 건 없음”이 맞는 말이지만, 받는 사람은 매수가 다 된 걸로 읽는다. 무인 시스템에서 주문 실패보다 나쁜 건 실패를 성공이라고 보고하는 알림이었다.

수정, 재시도, 그리고 하필 폭락

수정 자체는 금방이었다. 가격을 항상 정수로 보내게 고치고 재발 방지 테스트를 붙였다(전체 테스트 88개 통과). 알림도 증권사 거부까지 집계하게 바꿨다. 09:51 재기동, 09:54 수동 재시도. 이번엔 4종이 바로 체결되고 국고채 하나만 잠시 대기하다 곧 붙었다. 최종 5종 전량 체결 — 국내 주가지수 ETF 10주, 미국 S&P500 22주, 나스닥100 6주, 국고채 15주, 금 현물 36주. 건별 체결 알림도 이참에 추가했다. 설계 때는 “요약 한 방” 원칙으로 묶었지만, 한 달에 몇 건 없는 매매에서 건별 알림이 시끄러울 이유가 없었다.

그런데 하필 이날, 국내 주가지수 ETF가 -9.77% 폭락했다. 서킷브레이커급 하락이다. 첫 매수 날 지수가 -10% 가까이 꽂히는 걸 보고 있자니 룰이고 뭐고 다 팔고 싶어졌다.

올해 코스피는 변동성이 훨씬 심하다. 일주일에 한 번씩 서킷이야.

불안을 그대로 던지고 데이터로 검증하게 했다. 결과는 의외였다. 지수는 -9.77%였지만 포트폴리오 전체는 -2.76%. 국내 주식 비중이 25%뿐이라 지수가 꽂혀도 포트폴리오는 -3% 언저리에서 멈춘다. 올해 서킷급 하락일을 전부 포함한 백테스트에서도 포트폴리오 최악의 하루는 -3.8%, 일일 손실 한도(-6%) 발동일은 0일. 내친김에 급락 자동 매도 룰 같은 대응책 다섯 가지를 2016년부터 10년치 데이터로 변형 백테스트해 비교했는데, 전부 현행 유지보다 못했다. 서킷 다음날 팔면 반등을 놓치는 식이었다. Claude의 결론은 “현행 유지가 데이터로 이깁니다”였다. 팔고 싶은 손을 데이터가 잡았다.

배운 점