끊어진 데이터 파이프라인 에러 노드 트레이딩 시스템 버그 개념

자동매매 시스템에서 가장 무서운 버그가 뭔지 아는가? 틀린 주문을 내는 것? 아니다. 그건 로그에 찍히니까 잡을 수 있다.

진짜 무서운 건 시스템이 모르는 상태다. 주문을 넣었는데 체결이 됐는지 안 됐는지를 시스템 자체가 인식 못 하는 상황. V4.1에서 V4.8로 넘어오기 전, GENIE가 딱 이 상태였다.

체결됐다고 생각하고 다음 포지션을 잡으러 갔는데, 사실은 이전 포지션이 아직 안 잡혀 있었던 것이다. 예수금 계산도 엉켰다. 이걸 고치는 게 V4.8의 핵심이었다.

🔧 키움 API의 체결 구조 — CHEJAN이란

키움 OpenAPI에서 주문이 체결되면 OnReceiveChejanData라는 이벤트가 발생한다. 이걸 줄여서 CHEJAN(체잔)이라고 부른다.

문제는 이 CHEJAN이 두 가지 타입으로 나뉜다는 거다. 구분값(gubun)이 0인 것과 1인 것. 그리고 이 둘은 의미가 완전히 다르다.

구분값 의미 담긴 정보
G0 (gubun=0) 체결 이벤트 체결가, 체결량 (부분 체결 포함, 누적)
G1 (gubun=1) 잔고 확정 이벤트 최종 보유수량, 평균단가 (잔고 확정 후)

V4.1까지는 이 둘을 제대로 분리하지 않고 처리했다. G0가 오면 바로 "체결됐다"고 판단하고 포지션을 업데이트했는데, 문제는 G0가 부분 체결일 수도 있다는 것이다.

100주를 주문했는데 60주만 체결됐어도 G0가 왔고, 시스템은 그걸 전량 체결로 오인했다. 잔고는 60주인데 시스템은 100주라고 알고 있는 상태. 예수금 계산도 당연히 틀렸다.

G0 G1 분리된 체결 데이터 스트림 트레이딩 엔진 아키텍처

🐛 버그 픽스 두 가지

V4.8 로그에는 체결 구조 개편과 함께 두 가지 버그 픽스가 기록돼 있다.

✅ CHEJAN gubun 분리: G0=체결버퍼누적 / G1=잔고확정+pending해제 ✅ [BUG FIX] max_positions: pending_buy 포함 active_slots 체크 ✅ [BUG FIX] cash_est: pending_buy timeout 시 예수금 복구
  1. 포지션 슬롯 버그
    이전 버전은 MAX_POSITIONS(최대 3개)를 체크할 때 실제 체결된 포지션만 셌다. 주문을 넣고 체결 대기 중인 것(pending_buy)은 카운트에 빠졌던 것이다. 그래서 3개 다 찼는데도 추가 주문이 나가는 상황이 생겼다. V4.8에서 pending 상태도 슬롯으로 카운트하도록 수정했다.
  2. 예수금 복구 버그
    주문을 넣으면 일단 그 금액만큼 예수금을 차감해뒀다. 그런데 주문이 timeout되거나 취소됐을 때 차감했던 예수금을 되돌려주는 로직이 없었다. 돈은 안 썼는데 시스템은 쓴 것으로 알고 있었던 거다. 이게 누적되면 실제 살 수 있는데도 "예수금 부족"으로 매수가 차단되는 상황이 생긴다.
밤새 디버깅하는 개발자 모니터 불빛 에러 로그 화면

❓ Q&A

Q. 이런 버그가 실제 거래에서 발생하면 얼마나 위험한가?

생각보다 꽤 위험하다. 포지션 슬롯 버그 같은 경우 MAX_POSITIONS=3인데 4~5개가 동시에 잡힐 수 있다. 그러면 총 투자금이 설정치를 초과하게 된다. 예수금 버그는 살 수 있는 상황에서도 매수를 못 하게 막아버린다. 다행히 V4.8 당시는 모의투자 환경이었지만, 실계좌에서 이 버그를 만났다면 꽤 골치 아팠을 거다. 그래서 실계좌 전환 전에 반드시 모의투자로 충분히 굴려봐야 한다.

✍️ 3편을 마치며

자동매매 개발을 하다 보면 생각지도 못한 곳에서 버그가 나온다. 로직은 맞는데 API가 생각과 다르게 동작하거나, 타이밍 문제로 상태가 꼬이거나.

V4.8은 그냥 버전 번호가 하나 올라간 게 아니었다. 시스템의 가장 기본적인 부분, "체결을 제대로 알고 있는가"를 처음으로 제대로 잡은 버전이었다.

4편에서는 BEAR 장이 길어지던 시기, 시장 필터가 어떻게 작동했고 신호가 얼마나 차단됐는지를 V4.12 로그와 함께 들여다볼 예정이다.

※ 본 글은 자동매매 시스템 개발 과정을 기록한 정보 제공 목적의 개발 일지입니다. 투자 권유나 수익 보장이 아니며, 모든 투자 판단과 책임은 본인에게 있습니다.