제헌절에 매매를 시도한 봇
휴장일 캘린더 누락 사고를 계기로 수제 캘린더 위에 증권사 API 검증 계층을 얹은 이틀간의 기록.
금요일 아침부터 텔레그램에 잡 실패 알림이 쌓였다. 제헌절이었다.
하려던 것
자동매매 봇은 맥미니에서 launchd 상주 데몬으로 무인 운전 중이고, 모의투자 계좌로 돈다. 영업일 판정은 손으로 만든 휴장일 캘린더 파일에 의존한다. 그런데 제헌절 아침, 매매 사이클을 포함한 잡 세 개가 실패 알림을 보냈다. Claude Code에 물었다.
오늘 공휴일이라 휴장인데, 왜 매매하려고 했나?
휴장일에 왜 매매를 시도했는지 밝히고, 다른 휴장일도 빠졌는지 확인하고, 재발을 막는 것까지가 목표였다.
어떻게 시켰나
Claude가 캘린더 파일부터 열었다. 7월 휴장일이 하나도 등록돼 있지 않았다. 제헌절이 없으니 시스템은 평범한 영업일인 줄 알고 09:05 매매 사이클을 돌렸고, 시장이 닫혀 시세가 없으니 사이클이 실패로 끝났다. 실주문은 0건 — 잘못된 주문이 나간 게 아니라 헛돌다 실패한 것이라 다행이었다. 이어서 다른 휴장일을 API로 알 수 없는지 물었고, Claude가 증권사 휴장일 API를 찾아 연간 전체를 캘린더와 전수 대조하기 시작했다.
어디서 막혔나
두 번 막혔다. 첫 번째는 AI의 오판. 여섯 개 시점만 샘플 조회하고는 “설날·추석도 API에 없다”는 목록을 내놨다. 설 연휴가 개장일일 리 없다. 다행히 스스로 이상함을 감지하고 정정했다 — 이 API는 호출당 24일치만 반환하는데, 여섯 개 샘플이 그 창 밖의 날짜를 전부 놓친 것이다. 24일씩 걸어 다시 대조하니 384일 전체에서 누락은 제헌절 하나, 잘못 휴장 처리한 날은 0개였다.
두 번째가 진짜 반전이었다. 이런 API가 있는데 왜 손 캘린더를 만들었나 물었더니, 휴장일 API가 모의투자 도메인에서는 차단돼 있었다. 실측하니 모의 쪽은 “모의투자에서 지원하지 않는 TR” 에러, 실전 쪽은 정상 응답. 매매가 모의계좌로 도니 쓸 수 없었고, 손 캘린더 단독 의존은 설계 문서에 알려진 제약으로 적혀 있었다. 그 제약이 이날 터진 것이다.
어떻게 풀었나
돌파구는 이미 있었다. 다른 관측 기능 때문에 붙여 둔 실전 도메인 읽기 전용 브로커가 있었다. 매매는 모의로 하되 영업일 판정만 실전 API로 검증하면 된다. 캘린더를 버리는 대신 위에 검증 계층을 얹었다. API가 정답, 불일치하면 매매를 건너뛰고 경보(날짜당 1회), API 장애 시엔 캘린더로 폴백. “API가 누락된 휴장일을 덮어쓴다”는 테스트를 포함해 테스트는 112개에서 114개가 됐다.
끝인 줄 알았는데 다음날 토요일 낮에 또 알림이 왔다.
토요일인데 가상 매매 했다고 메시지 왔는데, 확인해봐.
같은 증상이라 어제 그 문제인가 싶었지만, 전혀 다른 버그였다. 여섯 개 잡 중 매매 사이클에만 영업일 가드가 빠져 있었다. 다른 잡들은 휴장일이면 건너뛰는데 매매 사이클만 매일 돌았고, 첫 주말 운전이라 이날 처음 드러났다. LLM이 매일 판단하는 Claude 방식 트랙이 토요일에 국고채 ETF 3주를 가상 체결한 기록 1건을 남겼다. 가드를 넣고 “휴장일엔 매매 로직이 호출되면 안 된다”는 회귀 테스트를 추가했다.
배운 점
- 손으로 관리하는 목록은 언젠가 빠진다. 정답 소스가 있으면 수제 데이터는 폴백으로 내리고 검증 계층을 얹는 편이 낫다.
- AI의 전수 대조도 그대로 믿으면 안 된다. 페이지네이션 함정에 걸린 첫 결론은 “설날도 개장일”이라는 엉터리였다.
- “왜 API를 안 썼나”는 물어볼 가치가 있었다. 안 쓴 데는 이유(도메인 차단)가 있었고, 그 제약은 다른 기능이 만들어 둔 우회로로 이미 풀려 있었다.
- 같은 증상이라고 같은 원인이 아니다. 이틀 연속 “휴장일 매매 알림”이었지만 하나는 캘린더 누락, 하나는 영업일 가드 누락이었다.