
백테스트가 거짓말을 한 4가지 경로. 전부 숫자를 의심한 곳에서 찾아냈다
요약
AI가 작성한 주식 자동 매매 봇의 백테스트 결과가 실제와 달랐던 4가지 오류 경로를 분석합니다. 데이터 지연, 종가 누락, 데이터 중복 등 수치적 오류가 어떻게 수익률을 왜곡하는지 다룹니다.
핵심 포인트
- 데이터 지연으로 인해 실제 도달 불가능한 고가로 매도된 것으로 계산됨
- 장 마감 직전 데이터 누락으로 인해 잘못된 종가가 반영됨
- 시각 차이가 있는 데이터 중복 입력이 분석을 왜곡함
- 에러 메시지 없이 그럴싸한 숫자를 반환하는 AI 결과의 위험성
이 기사의 초안은 AI가 작성했으며, 제가 전문을 확인했습니다. 수치와 사례는 5개월 반 동안의 작업 기록을 출처로 합니다.
제 bot은, 그 분봉(minute bar)에서 단 한 번도 도달하지 않은 가격으로 주식을 팔아서 이익을 내고 있었습니다.
매도가가 그 1분간의 고가를 상회하고 있었던 것입니다. 실제 시장에서는 절대로 체결될 수 없는 가격입니다.
2건이 발견되었습니다. 그 2건만으로 해당 주의 이익 중 25%를 차지하고 있었습니다.
무엇을 하고 있었나
5개월 반 동안, 주식 자동 매매 bot을 AI에게 작성하게 했습니다. 저는 소프트웨어 엔지니어가 아닙니다. 코드를 쓸 줄도 모르고 읽을 줄도 모릅니다.
제가 한 일은 무엇을 만들게 할지 결정하는 것과, 올라온 숫자를 믿을지 의심할지를 결정하는 것뿐입니다.
지난번에는 "AI의 보고는 12건 중 1건만 맞는다"라는 이야기를 썼습니다. 이번에는, AI가 내놓은 숫자 쪽이 거짓말을 하고 있었던 이야기입니다.
발견된 경로는 4가지입니다. 공통점은 마지막에 쓰겠지만, 미리 말하자면 4가지 모두 "에러(Error)"가 발생하지 않았습니다. 전부 그럴싸한 숫자를 반환했습니다.
거짓말 1: 존재하지 않는 가격으로 팔렸다
과거 데이터로 전략을 시험하는 것을 백테스트 (Backtest)라고 합니다. 가상의 매매를 과거에 대입하여 수익이 났는지 확인하는 것입니다.
제 bot의 백테스트는 매도 시 가격을 "그때 취득할 수 있었던 최신 주가"로 계산하고 있었습니다.
문제는 그 주가가 수 분 늦게 도착하는 것이었다는 점입니다.
주가가 급락하는 상황을 생각해 보십시오. 실제로는 싸게 팔려야 하는데, 늦게 도착한 "아직 높았던 시절의 가격"으로 팔게 된 것입니다. 게다가 지연 덕분에 급락 자체가 완만하게 보여서 보이지 않게 됩니다.
결과적으로, 그 1분간에 한 번도 도달하지 않은 고가로 팔린 것으로 계산된 거래가 2건 있었습니다.
이 2건으로 해당 주의 이익 25%를 차지했습니다. 나아가 "손익 0 근처에서 퇴각한다"라는 방어적인 움직임의 성적을 재검토했더니, 1거래당 **+0.48%에서 -0.32%**로 반전되었습니다. 플러스라고 생각했던 것이 마이너스였습니다.
즉 "지난주는 이기고 있었다"라는 말이 통째로 성립되지 않게 되었습니다.
거짓말 2: 종가가 종가가 아니었다
"아침에 사서 종가(그날의 마지막)에 판다"를 시도했을 때의 이야기입니다.
결과는 하루당 +0.62%. 나쁘지 않습니다. 저는 이것을 2번 보고받았습니다.
거짓말이었습니다.
사용하고 있었던 것은 1분 단위의 주가 데이터입니다. 그 마지막 행을 "종가"로 취급하고 있었습니다. 그런데 이 데이터, 15:24쯤에 끝나 있었습니다. 도쿄증권거래소(TSE)의 거래는 15:30에 끝나므로, 마지막 6분——그것도 그날의 종가를 결정하는 가장 중요한 시간——이 통째로 빠져 있었습니다.
어떤 종목의 특정 날짜를 조사했더니, 데이터상의 "마지막 가격"과 실제 종가가 18% 차이가 났습니다. 거래량이 적은 소형주에서는 이 마지막 몇 분 사이에 가격이 크게 움직입니다. 예외가 아니라 흔히 일어나는 일이었습니다.
올바른 데이터로 다시 측정했더니, **+0.62%는 -0.16%**가 되었습니다. 우위(Edge)는 사라졌습니다.
게다가 이 데이터에는 또 하나의 함정이 있었습니다. 같은 시각의 기록이 9시간 차이로 이중 입력되어 있었던 것입니다. 일반적인 중복 제거로는 시각이 다르기 때문에 삭제되지 않습니다.
40건의 데이터를 대조해 보니, 55%가 2% 이상 차이가 났습니다. 최대 15%까지.
거짓말 3: "0건"은 뭉개진 예외가 만들어냈다
3가지 전략에 대해 "과거 데이터에서 한 번도 발동하지 않았다"라는 결과가 나와 있었습니다.
0건. 즉 쓸모가 없다. 그 근거로 이 3가지는 수개월 동안 "막다른 길"로 취급되어 아무도 재검토하지 않았습니다.
나중에 알게 된 것은 이런 사실이었습니다.
검증용 프로그램이 전략을 호출할 때 전달하는 데이터의 형식을 틀리고 있었습니다. 그래서 모든 호출이 에러로 인해 중단되었습니다. 그런데 그 프로그램에는 "에러가 발생하면 묵묵히 다음으로 넘어간다"라는 식으로 작성되어 있어서, **에러는 단 하나도 표시되지 않은 채 결과는 "발동 0건"**이 되어 있었던 것입니다.
올바르게 다시 호출했더니 3가지 모두 발동했습니다.
여기에는 교훈이 있다고 생각합니다. 부정적인 결론은 아무도 재검증하지 않는다. "잘 되었다"는 사용할 때마다 확인되지만, "막다른 길"은 그 이상 조사할 이유를 스스로 없애버립니다. 그래서 근거가 망가져 있어도 몇 달 동안 눈치채지 못합니다.
지금은 "0건이었습니다"라고 보고받으면, **"몇 건을 시도했고, 에러는 몇 건이었습니까?"**라고 반드시 되묻도록 하고 있습니다. 그것에 답할 수 없다면 0건이라는 결론은 보류입니다.
(또 하나, 이와 유사한 함정이 있었습니다. "아침의 특정 시간대에만 작동한다"는 전략을 시장이 닫혀 있는 시간에 검증하면, 내용과 상관없이 반드시 0건이 됩니다. 프로그램이 "현재 시각"을 보고 판단했기 때문입니다.)
거짓말 4: 결제한 시각을, 산 시각으로 분석하고 있었다
거래 기록에 시각이 하나 포함되어 있었습니다. 그것은 결제(決済)한 시각이었는데, 여러 분석 도구가 그것을 산 시각으로 사용하고 있었습니다.
결과적으로, "몇 시에 사야 성적이 좋은가"를 조사하려던 분석이 전부 **판 시각(매도 시간대)**을 보고 있었던 셈이 됩니다. 거래 도중에 미실현 이익(含み益)이 얼마나 늘어났는지 보는 분석도, 결제 후의 시간대를 추출하고 있었습니다.
6개의 도구가 동일한 실수를 하고 있었습니다. 하나의 도구에서 발생한 버그가 아니라, 기록의 의미가 제대로 전달되지 않음으로써 발생한 계통적인(systematic) 오류였습니다.
이것을 발견하게 된 계기는, 어느 날 제가 "다른 수상한 곳은 없는가"라고 물어본 것이었습니다. 특별한 근거가 있었던 것은 아닙니다.
4가지의 공통점
나열해 보면 명확한 공통점이 있습니다.
4가지 모두, 고장 났을 때 에러를 내지 않았습니다. 그럴듯한 숫자를 반환했습니다.
- 존재하지 않는 가격으로 팔렸을 때도, 성적표는 깔끔하게 나왔습니다.
- 종가(終値)가 입력되어 있지 않아도, "+0.62%"라는 구체적인 숫자가 나왔습니다.
- 모든 호출이 실패해도, "0건"이라는 결론이 나왔습니다.
- 시각을 착각하고 있어도, 시간대별 그래프는 그려졌습니다.
프로그램이 멈춰준다면 차라리 쉽습니다. 작동은 하지만 틀려 있는 것이 가장 질이 나쁩니다. 게다가 나오는 것이 숫자이기 때문에, 겉보기에는 자신만만해 보입니다.
그리고, 코드를 읽을 줄 모르는 사람에게는 내용을 보고 알아챌 방법이 없습니다.
코드를 읽을 수 없다면, 숫자를 의심할 수밖에 없다
그렇다면 무엇을 할 수 있을까요. 4가지 사례가 어떻게 발견되었는지 나열해 보니 답이 나와 있었습니다.
- 거짓말 1을 발견한 계기: "다른 모델로 처음부터 다시 평가해"라고 지시했다
- 거짓말 2: 같은 일을 다른 데이터로도 측정하게 하여, 답이 맞지 않았다
- 거짓말 3: 부정적인 결론의 근거를 다시 점검하게 했다
- 거짓말 4: "다른 수상한 곳은 없는가"라고 물었다
코드는 한 줄도 읽지 않았습니다. 전부, 숫자 쪽을 의심했을 뿐입니다.
지금 정해둔 원칙은 이 4가지입니다.
1. 같은 숫자를, 다른 방식으로 두 번 출력하게 한다
다른 데이터, 다른 계산 경로. 맞지 않는다면 둘 중 하나는 고장 난 것입니다. 실제로 한쪽만 보고 있었을 때는 두 번 모두 "이기고 있다"였습니다.
2. 유리한 숫자일수록 먼저 의심한다
"+0.62%"는 듣는 순간 기분이 좋아지는 숫자입니다. 기쁜 숫자일수록 검산을 거친다.
3. "0건", "해당 없음", "특이 사항 없음"을 답으로 받아들이지 않는다
분모와 실패한 수를 반드시 함께 출력하게 한다. 출력할 수 없다면 결론은 보류입니다.
4. 검증은 만든 본인 이외의 사람에게 시킨다
같은 AI의 같은 대화 흐름에서 시키면 자신의 전제를 그대로 이어받습니다. 별도의 세션, 가능하다면 다른 모델에게 "반증해 보라"고 넘긴다.
4가지 모두 코드를 읽지 못해도 할 수 있는 일입니다. 오히려, 코드를 읽을 수 없기 때문에 숫자 쪽에만 단서가 있다.
요약
- 백테스트가 거짓말을 할 때, 에러는 발생하지 않는다. 그럴듯한 숫자가 나온다.
- 지연된 가격을 사용하면, 시장에 존재하지 않는 가격으로 체결된 것으로 간주된다.
- 분봉(分足) 데이터의 마지막 행은, 종가가 아니다.
- "0건"은, 전부 실패했더라도 나올 수 있다.
- 기록 항목명의 의미를 착각하면, 분석 전체가 조용히 어긋난다.
- 코드를 읽을 줄 모르는 사람이 할 수 있는 품질 관리는, 숫자를 의심하는 것이다. 실제로 4가지 모두 의심을 통해 발견되었다.
정작 중요한 봇(bot)은 이것들을 고친 후에도 이기지 못하고 있습니다. 오히려, 고칠 때마다 "이기고 있었다"가 사라져 갔습니다.
하지만 그것은 올바른 방향입니다. 거짓된 숫자로 이기는 것보다, 실제 숫자로 지는 것이 다음 판단을 내릴 수 있게 합니다.
다음에는 "이길 수 없다는 것을 알기까지 몇 번이나 '이겼다'고 생각했는가"에 대해 쓰겠습니다. 사라진 승리 목록입니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기