나는 미래가 과거로 새어 들어오게 만들었다

초기에, 너무 좋아서 나를 기쁘게 했어야 마땅한 백테스트 결과가 하나 있었다. 그런데 몇 분 동안 그것을 들여다보고 나니, 오히려 깊은 불안감이 밀려왔다. 시뮬레이션 속 수익은 크고, 매끄럽고, 지나치게 깔끔했다. 실제의 시끄럽고 적대적인 시장에서는 결코 일어나지 않는 종류의 성과였다. 그리고 그것이 실제 시장에서 일어나지 않는 이유는, 내 테스트가 조용히, 내가 알아차리지 못하는 사이에, 미래를 볼 수 있었기 때문이다.

일부러 그런 것은 아니었다. 나는 다음에 무슨 일이 일어날지 안다면, 부정행위를 하라고 쓴 적이 없다. 미래는 수십 개의 작고, 순진해 보이는 틈새를 통해 과거로 새어 들어왔고, 그 틈새 하나하나가 내 시스템에게, 실제 돈이 걸렸을 때는 결코 가질 수 없을 일종의 천리안에 대해 보상을 주고 있었다.

백테스팅에서 가장 매혹적인 버그

이것을 부르는 일반적인 이름은 룩어헤드 편향(look-ahead bias), 혹은 누수(leakage)다. 시뮬레이션 속의 어떤 결정이, 현실에서라면 그 결정이 내려지는 순간에 아직 존재하지 않았을 정보를 사용하도록 허용될 때마다 이것이 발생한다.

이것은 곱씹어볼 가치가 있다. 들리는 것보다 훨씬 미묘하기 때문이다. 백테스트는 역사의 충실한 재현이어야 한다. 매 순간, 시스템은 그 순간에 실제로 알 수 있었던 것만 알 수 있어야 하며, 오직 그것만을 사용해 행동해야 한다. 누수란 그 벽에 난 모든 균열이다. 내일의 정보 한 조각이 오늘의 결정으로 슬며시 끼어드는 모든 지점이다. 그리고 그 순간, 당신의 백테스트는 “이것이 어땠을까”를 측정하기를 멈추고 “이것이 미래를 엿볼 수 있다면 어떨까”를 측정하기 시작한다.

미래는 어떻게 슬며시 들어오는가

여기서 내 시스템의 구체적인 내용을 설명하지 않도록 조심하고 싶으니, 이 누수들이 취하는 일반적인 형태의 수준에서 이야기를 이어가겠다. 이런 형태들은 놀라울 만큼 흔하기 때문이다.

한 가지 전형적인 형태는, 당신이 결정을 내리는 척하는 그 순간 이후에야 비로소 확정된 값을 결정에 사용하는 것이다. 또 다른 형태는, 데이터를 스케일링하거나 정규화하면서 전체 역사에 걸쳐 계산된 통계량 — 평균, 범위, 전형적인 크기 — 을 사용해 데이터를 준비하는 것이다. 여기에는 당신의 시뮬레이션 시점에는 아직 일어나지 않았던 부분까지 포함된다. 또 다른 형태는, 사후의 이점을 누리며 데이터를 필터링하거나 라벨링하는 것이다. 결과가 어떻게 되었는지 이미 알고 있어야만 가능한 방식으로, 조용히 어떤 경우들을 배제하는 것이다.

이것들 각각은 합리적으로 보이는 단 한 줄의 코드다. 그 어느 것도 작성하는 동안에는 부정행위처럼 느껴지지 않는다. 그리고 그 하나하나가 당신의 시스템에게 미래로부터 오는 작고 꾸준한 정보의 흐름을 건네준다.

왜 그렇게 잡아내기 어려운가

누수에 관해 가장 잔인한 점은, 그것이 버그처럼 보이지 않는다는 것이다. 코드는 오류 없이 실행된다. 아무것도 멈추지 않는다. 그리고 그것이 만들어내는 결과는 경고 신호가 아니다 — 그것은 훌륭한 숫자다. 훌륭한 숫자는 대부분의 사람에게 의심을 불러일으키지 않는다. 그것은 축하를, 스크린샷을, 뭔가를 알아냈을지도 모른다는 조용한 감각을 불러일으킨다.

바로 그 점이 이 부류의 버그를 그토록 위험하게 만든다. 그것은 성공처럼 보이는 결과 안에 숨는다. 이는 버그가 숨기에 가장 최악인 단 하나의 장소다. 왜냐하면 성공이야말로 당신이 가장 따져보고 싶지 않은 단 하나의 결과이기 때문이다. 충돌은 고쳐달라고 요구한다. 아름다운 에쿼티 곡선은 더 이상 들여다보지 말라고 권유한다.

단서: 사실이라기엔 너무 좋은 결과

오랫동안 내가 가진 유일하게 믿을 만한 경보는 나 자신의 불신뿐이었다. 백테스트가 당신이 합리적으로 기대할 수 있는 그 어떤 것보다도 극적으로 더 좋게 — 시장이 허용할 자격이 있는 것보다 더 매끄럽고, 더 수익성 있고, 더 일관되게 — 돌아올 때, 올바른 첫 번째 가설은 “나는 뭔가 기발한 것을 찾아냈다”가 아니다. 그것은 “어딘가에서 정보가 새고 있으며, 나는 그것을 찾아야 한다”이다.

이것은 진정으로 유용한 사전 믿음이며, 나는 이제 이것을 강하게 견지하게 되었다. 백테스트에서 탁월함은 우위(edge)의 증거이기보다는 버그의 증상인 경우가 훨씬 더 많다. 시장은 어렵다. 시장이 쉽다고 암시하는 결과는 대개 시장이 아니라 당신의 코드에 관해 말해주고 있는 것이다.

누수 찾기

누수를 사냥하는 실제 작업은 지루하고 전혀 화려하지 않으며, 그것을 대체할 만한 영리한 지름길을 나는 찾지 못했다. 그 규율은 이렇다. 결정에 투입되는 모든 값 하나하나를 가져와, 그것이 실제 현실에서 알려졌을 정확한 순간까지 거꾸로 추적하라. 그런 다음 단도직입적으로 물어라. “내가 행동하는 척하는 그 순간에, 나는 정말로 이 숫자를 이미 가질 수 있었는가?”

대부분의 누수는, 마침내 궁지에 몰아넣고 보면, 타임스탬프 하나만큼의 부정행위로 드러난다 — 어떤 값이 진정으로 존재했을 시점보다 한순간 먼저 사용되었거나, 어떤 계산이 허용된 것보다 데이터 속으로 한 걸음 더 깊이 손을 뻗은 것이다. 작은 틈새들. 하지만 작은 틈새 하나가 수천 번의 결정에 걸쳐 반복되면, 그것만으로 통째로 상상 속의 재산을 만들어내기에 충분하다.

더 깊은 원리: 시간의 화살을 존중하라

이 모든 것의 밑바닥에는 이제 내가 거의 신성하다시피 여기는 단 하나의 원리가 있다. 시간의 화살을 존중하라. 백테스트는 본질적으로 하나의 약속이다 — 당신이 오로지 과거만을 사용해 현재에 행동했을 뿐, 결코 미래를 사용하지 않았다는 약속. 모든 누수는 작게 깨진 약속이며, 당신의 코드가 스스로를 돕기 위해 조용히 시간 앞으로 손을 뻗은 지점이다.

그래서 신뢰할 수 있는 시뮬레이션을 만드는 일은, 알고 보니 영리한 모델링에 관한 것이라기보다는 훨씬 더 하나의 경계를 방어하는 일에 관한 것이었다. 어떤 순간에 알려진 것과 단지 나중에 알 수 있게 될 것 사이의 선 말이다. 엔지니어링 노력의 대부분, 아무도 감탄하는 글타래를 쓰지 않는 그 부분은, 그저 미래를 엿보려는 당신 코드의 끊임없고 무심한 경향으로부터 그 경계를 지켜내는 일이다.

왜 나는 이제 내 최고의 결과를 불신하는가

이 모든 것이 남긴 지속적인 변화는, 좋은 소식에 대한 나의 관계가 뒤집혔다는 것이다. 실망스러운 백테스트는 그냥 실망스러울 뿐이다. 나는 그것을 보고, 어깨를 으쓱하고, 넘어간다. 이제 눈부신 백테스트는 나를 기쁘게 하기 전에 먼저 나를 불안하게 만든다. 결과가 좋을수록, 나는 그것을 만들어낸 누수를 더 열심히 찾아 나선다.

기쁨이 없는 일처럼 들리고, 때로는 실제로 그렇다. 하지만 그것은 이 특정한 게임이 자기기만을 어떻게 처벌하는지에 대해 힘겹게 얻어낸 존중에서 비롯된다. 시스템이 당신에게 하게 될 가장 위험한 거짓말은 추한 실패들이 아니다. 그것은 화려한 성공들이다 — 당신이 찾기를 바라던 바로 그것처럼 보이는, 그래서 바로 그 이유로 당신이 가장 마지막에야 의심하게 되는 그런 것들이다.

— 시그널 없음, 수익 없음, 투자 자문 아님.