백테스트 과최적화 예방하기 - 봉우리 말고 고원을 고르자
우리가 자동매매 시스템을 만들 때 하게 되는 일은 전략을 정하고, 파라미터를 정하고, 백테스트를 돌려보는 것이다. 손절은 몇 %로 할지, 볼린저밴드는 며칠짜리를 쓸지, 진입 점수는 몇 점부터 살지. 이 숫자들을 감으로만 정하면 의미가 없고, 그렇게 만든 전략은 쉽게 실패한다.
지난 글에서 전략이 뭘 어떻게 판단하는지는 백테스트 얘기를 할 다음 편에서 하겠다고 했다. 이번 글이 그 첫 번째다.
예로 들 건 V96이다. 후기 글에서 전략을 세 번 바꿨다고 썼는데, 마지막이 8월 27일 V96이고 초기 손절 기준선을 −9%에서 −12%로 넓혔다. 그때는 "20년치 백테스트에 교란을 줘서 전부 개선되는 걸 확인하고 배포했다"고 한 줄로 넘어갔다. 이 글은 그 한 줄을 풀어쓴 것이다.
먼저 말해둘 건, −12%를 수익이 가장 높아서 고른 게 아니라는 것이다. 가장 높은 값을 고르는 게 오히려 제일 위험하다. 왜 그런지부터 보자.
파라미터는 하나 정해서 끝낼 수 없다
전략은 규칙이고, 파라미터는 그 규칙에 들어가는 숫자다. 손절은 몇 %에서 할지, 볼린저밴드는 며칠짜리를 쓸지, 진입 점수는 몇 점부터 살지. 규칙을 코드에 박아도 이 숫자들은 어딘가에서 정해야 한다.
처음엔 그냥 정했다. 손절은 −9%쯤이 적당해 보였고, 볼린저는 다들 쓰는 20일이었다. 문제는 이렇게 정한 숫자가 시장에 맞는지 아닌지 아무도 모른다는 것이다. 맞는 값이 어딘가에 정해져 있는 게 아니라, 시장이 어떻게 움직이느냐에 따라 달라지기 때문이다. 같은 −9% 손절이 어떤 시장에서는 든든한 안전장치이고, 어떤 시장에서는 정상적인 흔들림에 멀쩡한 종목을 털어내는 선이 된다.
그래서 백테스트를 하는 거다. 내 마음에 드는 숫자가 아니라 시장이 받쳐주는 숫자를 찾으려고. 여기서 방법이 둘로 갈린다.
- 감으로 하나 정한다 : 그 값이 시장에 맞는지 확인할 방법이 없다. 이전 버전에서 쓰던 값을 그냥 들고 오는 것도 여기에 들어간다
- 백테스트에서 제일 높게 나온 값을 고른다 : 확인은 한 것 같지만, 그 값이 시장에 맞는 건지 과거 20년의 우연에 맞은 건지 모른다
둘 다 같은 약점이다. 숫자 하나만 보고 있다는 것. 시장에 맞는 값은 점이 아니라 영역으로 존재한다. 그 근처에서 성적이 안정적으로 나오는 구간이 시장이 받쳐주는 자리고, 점 하나만 유독 높다면 그건 우연이다. 그러니 값을 하나 정하는 게 아니라 값 주변을 흔들어보면서 영역의 모양을 봐야 한다.
백테스트 한 번은 표본 하나다
백테스트는 과거 20년을 한 번 돌려서 숫자 하나를 낸다. 파라미터가 열 개쯤 있는 전략이면 그 숫자를 올리는 방법은 얼마든지 있다. 볼린저 기간을 20일에서 18일로, 손절을 −9에서 −12로, 이런 식으로 하나씩 바꿔보면 어떤 조합이 가장 높게 나온다.
문제는 그 조합이 진짜 좋은 값인지, 과거 20년의 우연한 굴곡에 맞춘 값인지 숫자만으로는 구분이 안 된다는 점이다.
동전을 열 번씩 던져서 앞면이 가장 많이 나온 동전을 "앞면이 잘 나오는 동전"이라고 부를 수는 없다. 100개를 던지면 그중 하나는 8번, 9번 나온다. 후보가 많을수록 최고점은 그냥 우연으로도 높아진다. 파라미터 조합이 수천 개인 그리드서치는 후보 100개짜리 동전 던지기와 같다.
그래서 최고점을 찾은 다음에 물어봐야 하는 건 하나다.
이 값은 구조인가, 우연인가.
파라미터를 흔든다는 건
구조인지 우연인지 가르는 가장 단순한 방법이 값을 흔들어보는 것이다. 최적값에서 한 칸씩 옆으로 가본다.
- 옆으로 가도 성적이 비슷하다 : 이 값이 정확히 맞아서가 아니라, 이 근처가 좋은 영역이라는 뜻이다
- 옆으로 가면 성적이 곤두박질친다 : 이 값은 정확히 이 점에서만 좋다는 뜻이다. 과거의 어떤 우연에 딱 맞은 점일 가능성이 높다
앞의 모양을 고원, 뒤의 모양을 봉우리라고 부른다.

이게 단지 미학의 문제가 아닌 이유가 있다. 라이브에서는 시장이 과거와 똑같이 움직이지 않는다. 내가 백테스트에서 고른 값 그대로 앞으로 20년이 흘러가지 않는다. 시장이 조금 달라지면 사실상 파라미터가 조금 어긋난 것과 같다. 고원이면 조금 어긋나도 버티고, 봉우리면 거기서 무너진다. 시장에 맞는 값을 찾는다는 건 결국 시장이 조금 바뀌어도 버티는 자리를 찾는다는 말이다.
한 파라미터만 흔들어서는 부족하다
한 가지가 더 있다. 손절폭만 −9, −10, −11… 로 바꿔서 곡선을 그렸다고 하자. 그런데 그 곡선은 다른 파라미터들이 지금 값일 때만의 곡선이다.
진입 문턱이나 트레일링 폭이 다른 값이었다면 최적 손절폭도 달라졌을지 모른다. 그러면 "−12%가 좋다"는 결론은 나머지 파라미터가 지금 값이라는 조건에서만 성립하는 얘기가 된다.
그래서 다른 파라미터도 같이 흔들고, 그때마다 손절 곡선을 다시 그린다. 이걸 교란이라고 부른다. 질문이 이렇게 바뀐다.
−12%가 좋은 건 나머지가 이 값이라서인가, 나머지가 뭐든 간에인가.
실제로 해봤다: 손절 −9% vs −12%
V96이 −12%를 고른 근거를 다시 돌려봤다. 손절폭을 −9부터 −16까지 7단계로 바꾸고, 나머지 파라미터를 아래처럼 흔든 8가지 조합에서 각각 Calmar(연수익률 ÷ 최대낙폭)를 쟀다.
- 진입 점수 문턱 : 65 / 80 (현행 72)
- 트레일링 폭 : 8 / 18 (현행 12)
- 강제청산 보유일 : 120 / 400 (현행 240)
- 시장 국면 : 이동평균 15일 + 하락 문턱 −0.30 (현행 16일, 0.80)
지난 글에서는 9번이라고 썼는데, 이번에 하네스를 다시 짜서 재실행하면서 시장 국면 게이트의 매수 차단 조합 하나를 빼고 8가지로 정리했다. 그래서 이 글은 8가지 기준이다.
회색 선이 조합 하나씩이고, 굵은 선이 평균이다.

숫자로 보면 이렇다.
| 손절폭 | -9% | -10% | -11% | -12% | -13% | -14% | -16% |
| 8조합 평균 Calmar | 1.27 | 1.35 | 1.46 | 1.52 | 1.48 | 1.42 | 1.41 |
| 09%보다 나은 조합 수 | - | 6/8 | 8/8 | 8/8 | 8/8 | 8/8 | 8/8 |
읽을 수 있는 건 세 가지다.
하나, 모양이 같다. 회색 선 8개가 서로 높이는 제각각이다. 맨 아래 선은 국면 파라미터를 바꾼 조합인데 Calmar가 1.0 근처까지 내려간다. 그런데 모양은 전부 −9에서 올라가 −11~−13 부근에서 정점을 찍고 내려온다. 높이는 다른 파라미터가 정하고, 어디가 정점인지는 손절폭이 정한다는 얘기다.
둘, 방향이 8/8로 일치한다. −11%부터 −16%까지는 전부 8개 조합 모두에서 −9%를 이긴다. 한두 조합에서 뒤집힌다면 의심해야 하는데 뒤집히는 게 없다. 가장 좋은 점이 −12%인 조합은 8개 중 7개였고, 나머지 하나는 −11%였다.
셋, −12%는 정점이지만 −11%나 −13%로 옮겨도 큰 차이가 없다. 1.46 / 1.52 / 1.48. −12에서 정확히 맞아떨어지는 게 아니라 −11~−13 구간 전체가 평평하다. 그러니 내가 −12라고 쓴 건 정답을 맞춘 게 아니라 고원의 가운데를 골랐다는 것에 가깝다. 라이브에서 시장이 조금 달라져 최적 손절이 −11이나 −13으로 이동해도 문제가 안 된다.
숫자 말고, 이유가 설명되는가
모양과 방향이 일치하는 것만으로는 부족하다. 파라미터를 흔들어서 얻은 근거도 결국 과거 데이터라는 같은 우물에서 나온 것이기 때문이다. 그래서 하나 더 확인한다. 왜 그런지 설명되는가.
−9%는 사실 V46과 V50에서 튜닝된 값이었고, 그때는 시장 국면 게이트가 없었다. 개별 종목 손절이 유일한 리스크 통제 장치였으니 타이트해야 했다. 그런데 V93부터 국면 게이트가 붙었다. 시장이 약세로 돌아서는 날 전 종목을 강제 청산하는 장치다. 시장 리스크는 게이트가 맡게 됐는데 손절만 게이트가 없던 시절의 값을 쓰고 있었다.
그러면 −9%는 리스크를 막는 게 아니라 이길 수 있는 거래를 노이즈로 잘라내는 장치가 된다. 증거가 하나 있다. 손절을 넓혔더니 승률이 42.1%에서 45.0%로 올랐다. 손절을 넓히면 보통 이기는 거래가 줄어들어야 하는데 늘었다. 원래 이길 거래를 손절이 죽이고 있었다는 뜻이다.
정리하면 −12%가 믿을 만한 이유는 곡선 모양 때문만이 아니다. 곡선의 모양, 방향의 일관성, 그 이유를 설명하는 구조적 이야기 세 가지가 같이 맞아떨어졌기 때문이다. 이 중 하나만 있었다면 라이브 전략으로 배포하지 않았을 것이다.
모든 파라미터가 고원인 건 아니다
여기까지만 쓰면 "흔들어보니 다 괜찮았다"로 읽힐까 봐 반대 사례도 같이 적는다. 같은 방식으로 다른 파라미터를 하나씩 흔들어봤다.

위쪽 둘(트레일링 활성 수익률, 진입 점수 문턱)은 고원이다. 활성 수익률은 8~12, 진입 문턱은 68~80에서 거의 평평하다.
아래쪽 둘은 다르다. 볼린저 기간이 20일에서 15일이나 25일로 한 칸만 옮겨도 Calmar가 1.70에서 0.64 / 1.03으로 떨어진다. 표준편차 배수도 2.5에서 2.0으로 가면 0.69다. 이 전략의 진입 신호인 볼린저 돌파 쪽은 날이 선 봉우리다.
솔직히 이건 불편한 발견이다. 전략의 핵심 진입 조건이 봉우리 위에 서 있다는 얘기니까. 이 값은 과거에 튜닝해서 정한 것이고, 게이트가 붙은 뒤에도 여전히 그 값이 최선이었다는 점은 다행이지만, 봉우리인 이상 라이브에서 시장 성격이 조금만 바뀌어도 어긋날 수 있는 리스크다. 그래서 이 축은 건드리지 않되, 라이브 리스크로 따로 기록해둔다. 고치겠다는 게 아니라, 어디가 칼날 위인지 알고 있겠다는 것이다.
고원을 찾는 목적은 "다 안전하다"고 말하는 게 아니라, 어디가 안전하고 어디가 위험한지 지도를 그리는 것이다.
대조군이 없으면 그럴듯한 가설에 속는다
손절폭을 바꾸는 과정에서 하나 더 있었다. 처음 내 가설은 이랬다.
"시장 변동성이 높을 때만 손절을 넓히면 되지 않을까?"
테스트해보니 변동성 20일 값이 15 이상일 때 −13%로 두는 조합이 Calmar 1.661로 가장 좋았다. 기준(1.508)보다 확실히 올랐다. 그럴듯했다.
그런데 무조건 −12%로 두는 대조군이 1.720이었다. 더 높다. 변동성 조건은 아무것도 더해준 게 없었다. 문턱 15는 코스피 변동성의 중앙값이라 절반의 날에 발동하는 조건이었고, 사실상 "손절을 넓혀라"의 부분집합이었다.
대조군을 안 돌렸다면 "고변동성 때만 손절을 넓히는 정교한 규칙"을 만들었다고 뿌듯해하며 배포했을 것이다. 더 복잡하고, 더 나쁜 규칙으로.
이 방법으로도 확신은 못 한다
마지막으로 한계를 적어둔다.
교란 8가지도 같은 20년 데이터 안에서 한 것이다. 파라미터를 흔들었을 뿐 시장은 흔들지 않았다. 20년이 하나의 표본이라는 문제는 그대로다. 고원은 "덜 우연일 뿐" 미래를 보장하지 않는다.
그래서 이 방법이 해주는 건 "맞다"는 증명이 아니라, 틀렸을 때 덜 아프도록 하는 것이다. 옆 값이 비슷하면 값이 조금 어긋나도 버틸 가능성이 높고, 방향이 일관되면 한두 조합이 우연이었을 가능성이 낮고, 이유가 설명되면 우연이 아닐 가능성이 높다. 셋 다 확률을 올릴 뿐이다.
정리 - 백테스트 체크리스트
- 파라미터 하나를 격자로 돌려 곡선을 그린다
- 최고점이 아니라 주변의 평평한 구간을 찾는다
- 다른 파라미터를 바꿔가며 곡선을 다시 그린다 (교란)
- 모든 조합에서 방향이 같은지 본다. 하나라도 뒤집히면 의심한다
- 구간의 가운데를 고른다. 가장자리나 최고점이 아니라
- 왜 그런지 이유가 설명되는지 확인한다. 설명 못 하면 쓰지 않는다
- 더 복잡한 규칙을 만들었다면 단순한 대조군과 비교한다
- 봉우리로 남는 파라미터는 숨기지 말고 따로 기록한다
백테스트에서 가장 높은 숫자는 보기 좋지만, 그 숫자가 어디에 서 있는지를 모르면 쓸 수 없다. 높은 곳이 봉우리인지 고원인지는 한 칸만 옆으로 걸어보면 알 수 있다.
이 글은 내 전략의 백테스트 기록이고 투자 권유가 아니다. 백테스트 결과는 과거 데이터를 계산한 것이며 앞으로의 수익률은 다를 수 있다. 실제 거래에서는 수수료, 슬리피지, 체결 가능 여부 등으로 결과가 달라진다.