[빌드로그 #01] 백테스트는 왜 갑자기 나빠졌나, 엔트리 엣지 버그와 베이스라인 재정의

어떤 날은 전략을 개선하는 일이 숫자를 끌어올리는 작업이 아니라, 숫자를 다시 믿을 수 있게 만드는 작업이 된다.

이번 기록이 딱 그랬다. 시작은 평범했다. 익숙하게 알고 있던 baseline 성과를 다시 확인하고 있었고, 당연히 비슷한 숫자가 나올 거라고 생각했다. 그런데 화면에 뜬 결과는 내가 기억하는 값과 꽤 달랐다. 처음에는 흔한 의심부터 했다. 데이터 구간이 달라졌나, 캐시가 섞였나, 설정을 잘못 건드렸나. 확인을 거듭할수록 문제는 시장이나 데이터가 아니라, 전략이 “언제 새 진입 이벤트가 발생했다고 판단하는가”에 있었다.

결국 이번 작업은 단순한 튜닝이 아니라, 비교 기준으로 삼고 있던 baseline 자체를 다시 정의하는 여정이 됐다.

기준선이 흔들리면 개선도 개선이 아니다

전략을 손볼 때 가장 먼저 믿어야 하는 건 baseline이다. 이게 고정돼 있어야 그 위에서 “이번 수정이 좋아졌다, 나빠졌다”를 말할 수 있다. 그런데 이번에는 그 기준선 자체가 흔들렸다.

내가 알고 있던 기존 baseline:

📊 기존 Baseline (수정 전)

수익률 +685.5%

Sharpe 2.126 · PF 1.296

MDD -25.41% · Trades 2,053

같은 기본 전략을 다시 돌려본 결과:

📊 재검증 결과

수익률 +620.5% ▼ 65%p

Sharpe 1.898 · PF 1.269

MDD -33.85% ▼ 8.4%p · Trades 2,029

수익이 살짝 줄어든 정도가 아니었다. Sharpe는 내려갔고, drawdown은 더 깊어졌고, 거래 수 자체도 달라졌다. “이번 구간이 좀 안 좋았네” 하고 넘길 수 있는 수준이 아니라, 비교 기준 자체가 뒤틀려 있었던 거다. 마치 자를 대고 있었는데, 그 자가 휘어져 있었다는 느낌이랄까.

범인은 엔트리 조건이 아니라 엔트리 ‘엣지’였다

원인을 따라가다 보니 핵심은 엔트리 조건의 방향 자체가 아니었다. 조건이 꺼져 있다가 켜지는 ‘첫 순간’을 어떻게 감지하느냐가 문제였다.

우리 전략은 ready 상태가 유지되는 동안 매 봉마다 진입하는 구조가 아니다. False에서 True로 바뀌는 그 첫 봉만 잡아야 한다. 상태를 타는 게 아니라 엣지를 타는 전략이다. 비유하자면 문이 열려 있는 동안 계속 들어가는 게 아니라, 문이 열리는 바로 그 찰나에만 들어가야 하는 것이다.

그런데 이 엣지를 계산하는 부분에서 pandas 시리즈 dtype이 미묘하게 꼬일 수 있는 여지가 있었다. boolean negation이 기대와 다르게 해석되는 상황. 문법 오류도 아니고, 실행 중 바로 터지는 예외도 아니었다. 겉으로는 너무 멀쩡해서 더 위험한 종류의 버그였다.

무서운 건 승률이 흔들리는 것만이 아니었다. 어떤 봉을 진입 이벤트로 보느냐 자체가 달라지니, 트레이드의 시작점이 달라지고, 그 위에 쌓이는 성과 지표가 통째로 바뀌는 거다.

백테스트만의 문제가 아니었다

여기서 더 긴장한 이유가 있다. 백테스트와 라이브가 서로 다른 엔트리 함수를 쓰는 구조였다면, “백테스트 숫자가 좀 이상하네” 정도로 끝났을 수 있다. 하지만 우리 시스템은 두 환경이 같은 전략 로직을 공유하고 있고, 엔트리 엣지 계산도 그 공용 경로 안에 있었다.

즉, 라이브에서도 왜곡된 엔트리 이벤트가 발생할 수 있었던 문제였다. 그 순간부터 이건 분석 정리가 아니라 운영 기준을 다시 세우는 일로 바뀌었다. 신호가 잘못 해석되면, 그 뒤에 붙는 손절, 추적, 성과 분석, 최적화 결과까지 모두 잘못된 전제 위에 쌓이니까.

고친 건 작았지만 바꿔야 할 건 컸다

수정 자체는 복잡하지 않았다. 이전 봉의 ready 상태를 확실한 bool로 정리한 뒤, 그 다음에만 엣지 판정을 하도록 흐름을 다듬었다. 코드만 보면 아주 작은 수정이다.

하지만 진짜 비용은 코드 밖에 있었다. 예전에 더 좋아 보이던 숫자를 알고 있는데, 이제 더 덜 예쁜 결과를 새로운 공식 기준으로 받아들여야 한다. 솔직히 심리적으로 조금 불편한 순간이다. 그래도 결국 남겨야 하는 건 보기 좋은 숫자가 아니라 신뢰할 수 있는 숫자다.

그래서 baseline을 새로 세웠다:

✅ 새 공식 Baseline (버그 수정 후)

수익률 +620.5%

Sharpe 1.898 · PF 1.269

MDD -33.85% · Trades 2,029

겉으로 보면 성능이 나빠진 것 같다. 하지만 더 정확한 표현은 이쪽이다.

전략이 실제보다 좋아 보이게 만들던 왜곡을 제거했고, 더 현실적인 baseline이 남았다. 후퇴가 아니라, 개선을 다시 시작할 수 있는 정상적인 바닥을 되찾은 것이다.

같은 실수를 반복하지 않으려면

이런 종류의 버그는 lint로 잡히지 않는다. 문법이 틀린 게 아니라 의미가 어긋난 것이기 때문이다. 그래서 회귀 테스트를 추가했다. 연속된 ready 구간에서 엔트리가 첫 전환 봉에서만 켜지는지 확인하는 테스트다.

전략 코드에서 중요한 건 똑똑한 조건을 더하는 것만이 아니다. 이미 의도한 조건이 내일도 같은 의미로 작동하도록 지키는 것, 그게 전략 개발의 나머지 절반이다.

이번 빌드로그가 남긴 것

이번 작업에서 가장 크게 남은 건 “버그를 고쳤다”는 사실보다, baseline이 얼마나 쉽게 신화가 될 수 있는가였다. 한번 익숙해진 숫자는 자꾸 기준처럼 느껴진다. 하지만 그 숫자가 어떤 전제 위에 만들어졌는지를 잊는 순간, 튜닝은 과학이 아니라 취향이 된다.

Baseline은 단순한 수치가 아니라 일종의 계약이다. 그 위에서 모든 비교가 시작되기 때문에, 기준이 흔들리면 이후의 최적화도 모두 신뢰를 잃는다. 좋아 보이는 과거 성과보다, 신뢰할 수 있는 현재 성과가 더 중요하다. 그리고 한 번 잘 나온 그래프보다, 언제 다시 돌려도 같은 의미로 읽히는 baseline이 진짜 무기다.

가끔은 시스템이 틀린 답을 주는 게 아니라, 비교 기준 자체가 잘못되어 있을 때가 있다. 그럴 때 가장 위험한 건 숫자를 믿는 태도보다도, 그 숫자가 무엇을 전제로 만들어졌는지 묻지 않는 태도다.

이번 빌드로그는 바로 그 전제를 다시 세우는 과정이었다. 보기에는 한 걸음 물러난 것 같지만, 실제로는 그제야 제대로 앞으로 가기 시작한 셈이다. 다음 기록에서는 이 새 baseline 위에서 어떤 순서로 재최적화를 진행했는지, 그리고 수익률보다 drawdown을 더 중요하게 보기 시작한 순간이 언제였는지를 이어서 정리해볼 생각이다.

KEEP READING ON GEONULAB

실전 기록과 개념 정리를 한 흐름으로 이어서 읽어보세요

빌드로그는 실제 작업 기록에, 퀀트지식은 개념 정리에 집중합니다. 처음 방문했다면 Start Here에서 읽는 순서를 먼저 잡는 걸 추천합니다.

최신 글 흐름은 피드에서도 확인할 수 있습니다.

Email Updates

빌드로그와 퀀트지식 새 글이 올라오면 메일로 보내드립니다.

🤞 GeonuLab 글을 메일로 받아보세요

We don’t spam! Read more in our privacy policy

추후 봇 트레이딩 입문 PDF 소식도 가장 먼저 안내드릴게요.

댓글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다