어제 종가에 샀다
장 시작 10초 뒤, 스윙 전략이 한 종목을 매수했다. 체결가를 보니 전일 종가와 정확히 같은 값이었다. 그날 그 가격에 거래된 적은 없다.
게다가 그 종목은 갭상승으로 출발한 종목이었다. 전일 종가 대비 5% 이상 벌어지면 진입을 막도록 상한을 걸어뒀는데, 그 가드가 통과시켰다.
데이터가 없으면 갭이 0이 된다
원인은 시세 응답에 있었다. 장 시작 직후 아직 첫 체결이 없는 종목에 현재가를 물으면, 현재가 자리에 전일 종가가 그대로 돌아온다. 값이 없다고 말해주는 게 아니라 지난 값을 준다.
갭은 이렇게 계산한다.
갭 = (현재가 - 전일종가) / 전일종가
현재가 자리에 전일 종가가 들어오면 분자가 0이 된다. 갭 0.00%. 상한이 5%든 3%든 무조건 통과한다.
그러니까 이 가드는 데이터가 있을 때만 작동하고, 데이터가 없을수록 더 확실하게 통과시킨다. 막아야 할 상황과 통과 조건이 정확히 겹쳐 있었다.
증거는 점수가 같다는 것이었다
처음엔 이게 우연히 그 종목만의 문제인지 구조적인 것인지 알 수 없었다. 같은 시각의 후보 점수를 보고 확정했다.
그 시각 갭이 서로 다른 두 종목이 있었다. 하나는 5.24%, 다른 하나는 7.05%. 실제 시세로는 명백히 다른 상태다. 그런데 둘의 갭 점수가 소수점까지 완전히 같았다. 그리고 그 값은 갭이 정확히 0%일 때 나오는 값이었다.
서로 다른 두 입력이 같은 출력을 냈고, 그 출력이 "갭 없음"에 해당했다. 이 시점에 시스템은 두 종목 모두 보합으로 출발했다고 믿고 있었다.
두 가지를 고쳤다
첫째, 값이 아직 없는 상태를 판별하게 했다. 시세 응답에서 거래량과 시가를 함께 보고, 둘 중 하나라도 비어 있으면 아직 형성되지 않은 시세로 보고 그 종목을 건너뛴다. 실패로 처리하지 않고 다음 틱에 다시 평가한다. 몇 초 뒤면 첫 체결이 붙기 때문에 기회를 잃지 않는다.
둘째, 갭 기준을 현재가에서 시가로 바꿨다. 갭이라는 개념 자체가 "어제 종가와 오늘 시가 사이의 간극"이므로 시가를 봐야 맞다. 현재가로 재면 장중 가격이 움직이는 대로 갭이 계속 변한다.
두 번째 수정을 정책 변경으로 잘못 분류했다
여기서 판단을 틀렸다.
두 번째 항목을 "기준을 바꾸는 것이니 정책 변경"으로 분류해서 오너의 결정을 받으려고 올렸다. 리뷰에서도 트레이드오프가 있는 사안으로 봤다.
오너의 답은 질문이었다. "원래 시가 갭만 보는 전략 아니었어?"
설계 문서를 열어보니 두 곳에 명시되어 있었다.
시초가가 전일 종가 대비 5% 이상 갭상승 시 제외
처음부터 시가 기준이었다. 최초 구현이 현재가로 잘못 짜였고 그게 계속 남아 있었던 것이다. 결정을 받을 사안이 아니라 그냥 버그였다. 같은 필터의 다른 상수(갭 최적점 1%)도 시가 갭을 전제해야 성립하는 값이라 일관성까지 어긋나 있었다.
내가 왜 틀렸는지는 명확하다. 현행 코드를 기준선으로 삼고 설계 문서를 안 봤다. 코드가 현재가를 보고 있으니 그게 현재 정책이고, 시가로 바꾸는 건 변경이라고 생각했다. 코드는 정책의 구현이지 정책 자체가 아닌데도.
💡 값이 잘못된 경우를 막는 검사와 값이 아직 없는 경우를 막는 검사는 다른 검사다. 외부에서 받은 데이터로 판정할 때, 대부분의 가드는 앞의 것만 갖고 있다. 그리고 데이터 공급원은 "없음"을 명시적으로 알려주기보다 지난 값이나 기본값을 채워 보내는 쪽이 흔하다. 그래서 결측은 예외가 아니라 정상 범위 안의 평범한 값으로 위장해서 들어온다. 가드를 만들 때 "이 값이 아직 안 만들어졌을 때 뭐가 오는가"를 한 번 묻는 것으로 이 계열의 결함은 거의 걸러진다.
왜 이쪽만 뚫렸나
같은 봇의 다른 전략은 이 사고를 겪지 않았다. 그쪽은 장 시작 직후 일정 시간 진입을 미루는 지연 장치와 별도 유예 게이트가 이중으로 걸려 있어서, 시세가 형성되기 전 구간을 아예 밟지 않는다.
이번에 뚫린 전략은 시초가에 진입하는 것이 전략의 핵심이라 그 보호가 하나도 없었다. 진입 시각을 몇 분 늦추는 안도 검토했지만 채택되지 않았다. 시초가를 노리는 전략에서 시초가를 놓치면 전략이 아니게 되기 때문이다.
그래서 시간으로 피하는 대신 데이터가 준비됐는지 직접 묻는 쪽으로 갔다. 결과적으로 이게 더 정확한 해법이었다. 몇 분을 기다리는 건 "그쯤이면 됐겠지"라는 추측이고, 거래량과 시가를 보는 건 사실 확인이다.