← 코타랩

백테스트, 처음이라면

2026.09.29

백테스트는 예언이 아니라 검산이다

백테스트로 "이 룰이 앞으로도 통한다"를 증명할 수는 없다.

대신 두 가지는 확실히 알려준다. 이 룰이 과거에 어떻게 굴러갔는지. 그리고 내가 룰을 잘못 이해하고 있진 않은지.

나는 두 번째가 더 중요하다고 본다.

6주 동안 백테를 돌리면서 룰은 거의 안 바꿨다. 대신 내 코드가 틀린 걸 다섯 번 찾았다..

백테의 첫 번째 쓸모는 수익률 숫자가 아니었다. 내 머릿속 룰과 실제로 돌아가는 룰이 같은지 확인하는 거였다.

그래서 처음 백테를 돌린다면 목표를 이렇게 잡는 게 낫다. 좋은 숫자 말고, 믿을 수 있는 숫자를 뽑는 것.

연 30%가 나오면? 기뻐하기 전에 의심부터 해야 한다.

이 글은 그 의심을 어디에 둬야 하는지 순서대로 적어본 거다.

데이터가 틀리면 나머지도 다 틀린다

룰을 짜기 전에 데이터부터 보자.

여기서 생기는 오류는 거의 항상 같은 방향이다. 수익률이 실제보다 좋게 나온다.

상폐 종목은 꼭 넣는다

증권사 API는 지금 상장된 종목만 준다.

그 데이터로 10년을 돌리면? "10년 동안 살아남은 종목들" 안에서만 고르는 셈이다. 이걸 생존편향이라고 한다.

신고가·모멘텀 전략은 특히 심하다. 상폐된 종목 중엔 한때 신고가를 찍다가 무너진 종목이 꽤 많다.

근데 우리 룰이 사는 자리가 바로 그 신고가다. 그 손실이 백테에서 통째로 사라지는 거다.

한국 주식은 KRX 자료에서 상폐 종목 일봉을 받을 수 있다(분봉은 없다).

과거 매년 초 상장 리스트를 모아서 합치면, 그 사이에 사라진 종목 코드가 잡힌다.

상폐 종목을 넣으면 룰이 하나 더 필요하다. 들고 있는 중에 상폐되면 어떻게 할 건지.

이게 없으면 그 종목은 영원히 보유 상태로 남는다.

나는 정리매매 첫날 시가에 전량 청산으로 잡는다. 실전에서 이보다 잘 빠져나올 방법이 없으니, 보수적으로 잡은 거다.

수정주가를 쓴다

수정주가는 액면분할·감자·배당을 반영해서 과거 가격을 다시 계산한 가격이다.

이게 없으면 5:1 분할이 80% 폭락으로 보여서 스톱이 터진다.

대부분의 API가 수정주가를 주니까 그걸 쓰면 된다. 참고로 거래대금 필터는 가격×거래량이라 분할 영향을 안 받는다.

거래정지 기간은 매매 불가로 막는다

정지 중엔 스톱이 뚫려도 못 판다. 재개일 시가에 나가게 된다.

실전이 그러니까, 백테도 그렇게 짜야 한다.

트레이딩 모임에서 알게 된 퀀트하시는 분께 이걸 물어봤다. 답은 딱 한 줄이었다.

"상폐 데이터는 꼭 넣으세요. 넣고 안 넣고랑 결과가 아예 다릅니다."

신호와 체결은 다른 문제다

"오늘 신고가를 찍었다"는 신호고, "얼마에 사는가"는 체결이다. 처음 백테를 짜면 이 둘이 자꾸 섞인다.

신호가 난 날 종가에 사는 걸로 짜기 쉽다. 근데 실전에선 종가를 보고 나서 그 종가에 살 수가 없다. 다음 날 시가가 맞다.

스톱도 마찬가지다. 스톱 가격에 딱 체결된 걸로 짜면 갭 하락을 무시하게 된다.

스톱이 10,000원인데 시가가 9,000원에 열리면? 9,000원에 팔린다.

룰은 "스톱 터치 시 매도"지만, 체결은 min(스톱, 그날 시가)로 짜야 한다. 이 한 줄 차이로 수익률이 연 1~2%p 바뀐다.

스톱 판정 기준도 정해야 한다. 장중 저가가 스톱을 건드리면 청산인지, 종가가 스톱 아래서 끝나야 청산인지.

둘은 다른 룰이고 결과도 다르다. 실전에서 내가 쓰는 방식이랑 백테가 같아야 한다.

나는 주식은 장중 터치, 코인은 종가로 본다. 코인은 스톱헌팅이 많아서..

수수료와 슬리피지는 마지막에 의심하라고 배웠다. 상수로 넣으면 두 백테 사이에 차이를 안 만드니까.

그래도 깜빡하진 말자. 1년에 100번 매매하는 전략이면 왕복 0.3%가 연 30%다.

같은 날 신호가 여러 개면, 순서가 결과를 만든다

슬롯은 5개인데 오늘 신호가 12개 떴다. 뭘 살까?

백테는 어떻게든 하나를 골라야 한다. 그리고 그 고르는 규칙이 결과를 크게 바꾼다.

나도 두 엔진을 비교하다가 이걸 알았다. 후보 종목은 99.5% 같은데 연복리가 9.0%와 11.6%로 갈렸다.

그 차이의 큰 부분이 "같은 날 신호 중 누굴 먼저 사느냐"였다.

그래서 백테 결과는 점 하나가 아니라 분포로 적어야 한다. 순서를 무작위로 뽑아 48번 돌리면 연복리가 6.5%에서 11.2%까지 퍼진다.

이 퍼짐은 백테의 오차가 아니다. 실전의 불확실성이다. 실전에서 내가 어떤 종목을 먼저 누를지 미리 알 수 없으니까.

"우리 전략은 연 9%"가 아니라 "연 6~11%, 중앙 9%"가 정직한 표현이다.

반대로 "RS 높은 순서대로 산다" 같은 고정 규칙을 쓰면 결과가 한 점으로 나온다.

근데 그 점이 분포의 어디쯤인지 모르면? 운 좋은 순서를 실력으로 착각하게 된다.

무작위 순서로 분포부터 그리고, 내 규칙이 그 안 어디에 찍히는지 보는 것. 그게 맞는 순서다.

종목 백테와 계좌 백테는 다른 질문에 답한다

대부분의 백테는 종목 단위다. 신호가 뜬 종목을 전부 샀다고 치고 매매당 평균 R, PF(Profit Factor), 승률을 낸다.

"이 자리에 우위가 있나?"를 보는 데는 이걸로 충분하다. 자리별 PF 1.66 같은 숫자가 여기서 나온다.

근데 "내 계좌가 얼마나 빠지나?"는 이걸로 못 잰다.

계좌엔 슬롯이 5개뿐이고 총 리스크 한도가 걸려 있다. 오늘 좋은 신호가 떠도 자리가 없으면 못 산다. 어제 산 종목이 스톱에 걸려야 자리가 난다.

이런 얽힘은 종목을 하나씩 보는 백테엔 아예 없다. 계좌 하나를 첫날부터 마지막 날까지 날짜 순서대로 굴려야만 보인다.

계좌 백테는 이렇게 돈다.

매일 아침 신호를 모은다 → 슬롯과 리스크 한도에 여유가 있는지 본다 → 순서를 뽑는다 → 스톱 거리로 물량을 계산한다 → 그날 스톱에 걸린 종목을 청산한다.

이 루프를 14년 돌리고, 순서를 바꿔서 수십 번 반복한다. 그러면 낙폭이 분포로 나온다.

총 리스크 한도를 3%에서 7%로 올리면 낙폭이 얼마나 커질까? 종목 하나 비중 상한을 30%로 두면 최악의 해가 얼마나 덜 나빠질까?

이런 질문은 전부 계좌 백테가 답해야 한다. 종목 백테로 이걸 풀려고 하면, 답이 안 나오는 게 아니라 틀린 답이 나온다.

숫자가 좋아 보일 때가 제일 위험하다

과최적화는 파라미터를 수백 번 돌려야만 생기는 게 아니다.

전체 기간 결과를 보면서 "이 필터 넣자", "이 손절 쓰자" 하고 결정을 하나씩 내릴 때마다 생긴다. 그 결정 하나하나가 과거 표본에 맞춘 선택이다.

그러니까 나는 최적화 안 한다고 생각하는 사람이 오히려 제일 하고 있기 쉽다.

교과서의 답은 기간을 둘로 나눠서, 한쪽으로 룰을 만들고 다른 쪽에서 검사하라는 거다(인샘플/아웃오브샘플).

근데 오래 해온 실무자들은 이런 조언을 더 한다. 변수는 최소한으로, 최적화는 거의 하지 말고, 왜 통하는지 스스로 납득이 돼야 한다고.

나는 반만 받아들였다. 변수를 적게 가져가는 건 맞다.

근데 그것만으론 위에서 말한 "결정으로 하는 과최적화"를 못 막는다.

그래서 룰을 바꿀 땐 세 가지 검사를 통과해야만 바꾼다.

시장을 바꿔보는 것도 강력하다. 한국 주식으로 만든 룰을 미국 주식에 그대로 돌리면, 그건 진짜로 안 본 표본이다.

거기서도 PF가 1 위로 나오면 룰이 시장 구조를 잡은 거다. 1 근처면? 코스피 표본에 맞춘 거다.

그리고 제일 어려운 검사가 남았다.

바꾸지 않았을 때와 차이가 작으면, 안 바꾼다.

파라미터 스윕을 해봤더니 내 값이 고원 한가운데가 아니었던 적이 있다. 근데 표면에 뚜렷한 구조가 없어서 그냥 뒀다.

무작위에 가까운 표면에서 최고점을 골라 옮기는 것. 그게 과최적화의 가장 순수한 형태다.

내가 겪은 사고, 그리고 두 엔진으로 돌리는 이유

공식 하나 틀리는 건 이런 식으로 온다.

내 RS(상대강도) 공식은 최근 4분기를 가중 합산하는 건데, 백테 코드에선 가중치 0.4짜리 최근 3개월 항이 항상 0이었다. 인덱스 하나 때문에.

에러는 안 났고 숫자는 그럴싸했다. 몇 주 동안 그걸 모르고, 그 위에 상한 스윕, 필터 비교, 손절 비교를 쌓았다.

버그를 찾은 날 그 숫자가 전부 무효가 됐다. 연복리 14.5%는 9.5%로 내려갔고, "지수를 이긴다"는 주장도 같이 사라졌다.

그나마 다행이었던 건 두 가지다. 그 숫자로 바꾼 룰이 없었고, 실전에 쓰는 신호 코드는 백테와 따로여서 맞았다.

백테가 틀린 거지, 매매가 틀린 게 아니었다.

그 뒤로 세 개를 더 찾았다. 모집단에서 신규 상장주를 미리 빼고 있었다. 백분위를 2/3 대신 66.667로 썼다. 피벗 배제 조건에 break 하나가 들어가서 하루만 막고 있었다.

하나하나는 작은데, 합쳐보니 후보 종목의 십몇 퍼센트가 다른 종목이었다.

이걸 어떻게 찾았을까?

같은 룰을 다른 사람(여기선 다른 AI)이 공식부터 새로 짜서 돌렸다. 그리고 날짜별 후보 종목 리스트를 맞춰봤다.

처음엔 70% 정도만 같았다. 다른 종목을 하나씩 열어서 왜 다른지 쫓아가다 보니 위의 버그들이 나왔다. 12라운드를 돌고 나서야 99.5%가 됐다.

백테 코드는 자기가 짜고 자기가 검사하면, 틀린 자리를 거의 반드시 그냥 지나친다.

같은 걸 독립적으로 두 번 만들어서 맞춰보는 것. 내가 아는 유일한 방법이다.

첫 백테스트 전 체크리스트

솔직히 이 중 절반은 내가 틀리고 나서 적은 거다.

첫 백테의 숫자는 거의 확실히 틀려 있다. 그게 정상이다.

그걸 알고 돌리는 것과 모르고 돌리는 것의 차이가 결국 실전 계좌에 나타난다.

댓글

이 사이트의 글은 필자 개인의 연구 기록이며 소속 기관의 견해가 아닙니다. 특정 종목의 매매를 권유하지 않으며, 게시된 수치는 과거 데이터를 이용한 백테스트 결과로 미래 성과를 보장하지 않습니다.

링크가 복사되었습니다