선견 편향 백테스트 제거 방법
선견 편향(look-ahead bias)은 미래 데이터가 백테스트에 유입되는 오류입니다. 동일 막대 결정, 생존 편향, 수정된 수치가 결과를 부풀리는 방식을 확인하고 이를 탐지하는 방법을 설명합니다.
선견 편향(look-ahead bias)은 백테스트에서 아무도 모의 거래가 체결된 시점에 알 수 없었던 정보를 사용하는 것을 말합니다. 이로 인해 자본 곡선은 상승하고 통계는 훌륭해 보이지만, 실제 주문과 마주하면 그 어떤 것도 유효하지 않게 됩니다. 이 페이지는 편향을 정의하고, 이를 발생시키는 세 가지 메커니즘을 설명하며, 실제 시장 데이터를 통해 각각을 측정합니다.
선견 편향이란 무엇인가
백테스트는 어떤 결정에 대한 주장입니다. 즉, 이 타임스탬프에서, 이 정보를 바탕으로, 이 규칙이 이렇게 행동했을 것이라는 주장입니다. 이 주장은 규칙이 읽는 모든 입력값이 해당 타임스탬프 이전에 이미 최종 형태로 존재했을 때만 성립합니다. 선견 편향은 이러한 조건의 위반이며, 조용히 이루어집니다. 스프레드시트나 스크립트에서 어떤 행이 자신이 공급하는 결정보다 늦게 찍힌 값을 반환할 때 불평하는 일은 없습니다.
실제로 나타나는 대부분의 경우는 세 가지 위반 유형으로 설명됩니다. 거래가 이루어지는 가격 막대를 함께 읽는 결정. 오늘날에도 여전히 상장되어 있는 종목명으로 구성된 유니버스. 해당 날짜 이후에 수정된 데이터 필드.
메커니즘 1: 거래하는 막대에서 결정하기
먼저 무해해 보이는 규칙부터 시작합니다. 강세에 매수한다, 즉 펀드가 시가보다 높은 가격에 마감한 세션에서 매수한다는 규칙입니다. 부주의하게 코딩된 경우, 테스트는 해당 세션의 시가에 진입하여 종가에 청산하는 반면, 스크리닝 조건은 동일한 막대의 종가입니다. 개장 종이 울릴 때 아무도 종가를 알지 못합니다.
아래 패널은 SPY(S&P 500 추종 ETF)에 대해 두 가지 버전을 10년 동안 실행한 결과를 보여줍니다. 첫 번째 열은 불가능한 버전입니다. 두 번째 열은 동일한 조건을 단 하나의 세션만 지연시킨 것으로, 자금이 조달된 계좌가 이에 따라 행동할 수 있는 가장 빠른 시점입니다.
모든 수치 뒤에 숨겨진 정확한 SQL
WITH bars AS (
SELECT date,
toFloat64(open) AS o,
toFloat64(close) AS c,
toFloat64(close) > toFloat64(open) AS up_day
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= toDate('2015-12-01')
AND date <= toDate('2025-12-31')
),
lagged AS (
SELECT date, o, c, up_day,
any(up_day) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prior_up
FROM bars
)
SELECT toYear(date) AS year,
countIf(up_day) AS signal_days,
round(100 * avgIf(c / o - 1, up_day), 3) AS same_bar_avg_pct,
round(100 * avgIf(c / o - 1, prior_up), 3) AS lagged_avg_pct,
round(100 * (avgIf(c / o - 1, up_day) - avgIf(c / o - 1, prior_up)), 3) AS gap_pp
FROM lagged
WHERE date >= toDate('2016-01-01')
GROUP BY year
ORDER BY year차이는 미미하지 않습니다. 2016에서 동일 막대 버전은 적격 세션당 평균 0.447%을 기록한 반면, 지연 버전은 평균 -0.008%를 기록하여 일일 0.455%포인트 차이를 보였습니다. 2025에서는 두 값이 각각 0.636%와 -0.03%였습니다. 1년에 약 140개의 적격 세션이 있을 때, 세션당 0.5%포인트의 차이는 어떤 실계좌도 인쇄한 적 없는 자본 곡선으로 복리됩니다.
이 모든 이점은 정의상의 문제입니다. 시가보다 높은 가격에 마감된 세션을 선택한 후, 동일한 세션의 시가-종가 수익률을 측정하는 것은 양수가 보장된 값을 측정하는 것입니다. 규칙은 자신의 필터를 발견한 것입니다.
이러한 변형은 일일 데이터에서 항상 발견됩니다: 오늘의 종가를 포함하고 오늘 거래에 사용되는 이동 평균, 아직 발생하지 않은 분할에 대해 이미 조정된 가격으로 계산된 신호, 아직 인쇄되지 않은 세션 고점에 설정된 장중 손절가. 공통된 형태는 결정 타임스탬프가 자신의 입력값 중 하나보다 더 이른 시점에 있는 것입니다.
메커니즘 2: 오늘날의 생존자로 구성된 유니버스
두 번째 메커니즘은 규칙 자체를 건드리지 않습니다. 규칙이 볼 수 있는 종목명 목록에 있습니다.
현재 데이터 제공업체에서 티커 목록을 가져와 10년 전으로 되돌려 실행하면, 표본은 그 과정에서 파산했거나, 인수되었거나, 상장폐지된 모든 회사를 조용히 제외합니다. 규칙은 패자들을 제공받지 못한 것입니다. 패널은 이 누락의 규모를 측정합니다: 특정 연도에 1달러 이상으로 최소 200세션 동안 거래된 모든 미국 상장 종목, 2026년 7월에도 여전히 가격을 인쇄하고 있는 해당 코호트의 비율, 그리고 전체 코호트의 중간 연간 수익률과 생존자만의 중간 수익률을 비교합니다.
모든 수치 뒤에 숨겨진 정확한 SQL
WITH recent AS (
SELECT DISTINCT ticker
FROM global_markets.stocks_daily_aggs
WHERE date >= toDate('2026-06-15')
AND date <= toDate('2026-07-28')
),
per_name AS (
SELECT toYear(date) AS year,
ticker,
argMin(toFloat64(close), date) AS first_close,
argMax(toFloat64(close), date) AS last_close,
count() AS sessions
FROM global_markets.stocks_daily_aggs
WHERE date >= toDate('2015-01-01')
AND date <= toDate('2024-12-31')
AND close > 1
GROUP BY year, ticker
HAVING sessions >= 200
)
SELECT year,
uniqExact(ticker) AS names_trading,
round(100 * uniqExactIf(ticker, ticker IN (SELECT ticker FROM recent)) / uniqExact(ticker), 1) AS still_listed_pct,
round(100 * quantileDeterministic(0.5)(last_close / first_close - 1, cityHash64(ticker)), 2) AS median_return_all_pct,
round(100 * quantileDeterministicIf(0.5)(last_close / first_close - 1, cityHash64(ticker), ticker IN (SELECT ticker FROM recent)), 2) AS median_return_survivors_pct,
round(100 * (quantileDeterministicIf(0.5)(last_close / first_close - 1, cityHash64(ticker), ticker IN (SELECT ticker FROM recent))
- quantileDeterministic(0.5)(last_close / first_close - 1, cityHash64(ticker))), 2) AS survivor_gap_pp
FROM per_name
GROUP BY year
ORDER BY year2015년에 기준을 통과한 7063개 종목 중 52.5%개가 2026년 7월에도 여전히 거래되고 있었습니다. 2024년 코호트의 경우 이 수치는 87.5%이며, 이는 주로 경과 시간의 문제입니다: 2024년 종목은 사라질 기회가 2년 있었고, 2015년 종목은 11년이 있었습니다.
수익률 열에는 편향 자체가 포함되어 있습니다. 2017년에 중간 종목은 7.51%의 수익률을 기록한 반면, 중간 생존자는 11.32%를 기록하여 3.81%포인트의 차이를 공짜로 얻었습니다. 이 차이는 도표화된 10년 모두에서 양수입니다. 생존자에 대해 테스트된 전략은 단 하나의 결정을 내리기도 전에 이 차이를 물려받으며, 약세를 스크리닝하는 규칙은 중간값보다 더 많은 차이를 물려받는데, 사라진 종목들이 불균형적으로 그 범주에 속하기 때문입니다.
이에 대한 교정은 시점별 유니버스(point-in-time universe)입니다: 각 리밸런싱 날짜 당시의 종목 목록(상장폐지 포함)으로, 상장폐지된 포지션은 기록에서 삭제되는 대신 최종 가격에 청산됩니다.
메커니즘 3: 날짜 스탬프 이후에 수정된 데이터
가격은 한 번 스탬프됩니다. 펀더멘털은 그렇지 않습니다. 분기별 수치는 분기 말일을 가지며, 이후 제출된 서류에 의해 수정되며, 현재 버전만 저장하는 데이터베이스는 기꺼이 수정된 숫자를 원래 날짜에 제공합니다. 해당 숫자를 스크리닝하는 규칙은 도착하는 데 몇 달이 걸린 수정 사항을 읽고 있는 것입니다.
동일한 패턴이 현재 구성 종목으로 재구축된 지수 멤버십 목록, 최종 컨센서스로 덮어쓰여진 애널리스트 예상 이력, 그리고 전체 가격 시리즈에 걸쳐 소급 적용된 기업 액션 조정에서도 나타납니다. 각 경우에 저장된 기록은 과거 시제 날짜를 입고 있는 현재 시제 스냅샷입니다.
이를 방지하는 두 가지 관행이 있습니다. 빈티지(vintage)를 유지하는 소스를 사용하십시오. 즉, 현재 알려진 값이 아니라 각 날짜에 알려진 값을 의미합니다. 빈티지를 사용할 수 없는 경우, 실제 보고 지연을 충분히 커버할 수 있을 만큼 넉넉한 보고 지연을 적용하고, 해당 지연의 길이를 추측할 가치가 있는 세부 사항이 아니라 테스트할 가치가 있는 매개변수로 취급하십시오.
미래가 새어 들어올 때 곡선이 어떻게 보이는가
선견 편향에는 특징이 있습니다: 기초 시장이 제공한 것보다 훨씬 위에 있는 성과입니다. 유용한 참조점은 사후적 천장(hindsight ceiling), 즉 완벽한 하루 전 예측이 단일 상품에서 생성했을 성장률입니다.
모든 수치 뒤에 숨겨진 정확한 SQL
WITH bars AS (
SELECT date,
toFloat64(close) AS c,
any(toFloat64(close)) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prev_c
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= toDate('2015-12-01')
AND date <= toDate('2025-12-31')
),
rets AS (
SELECT toYear(date) AS year,
c / prev_c - 1 AS r
FROM bars
WHERE date >= toDate('2016-01-01')
AND prev_c > 0
)
SELECT year,
count() AS sessions,
round(100 * (exp(sum(log(1 + r))) - 1), 1) AS buy_and_hold_pct,
round(100 * (exp(sumIf(log(1 + r), r < 0.02)) - 1), 1) AS without_big_up_days_pct,
round(exp(sum(log(1 + abs(r)))), 1) AS hindsight_ceiling_multiple
FROM rets
GROUP BY year
ORDER BY year천장은 엄청납니다. 2020년에 SPY에 대한 완벽한 하루 전 예측은 253세션 동안 자본을 27.7배 증가시킨 반면, 단순히 펀드를 보유한 경우는 16.2%배였습니다. 10년 중 가장 조용했던 해인 2017년에도 동일한 완벽한 예측은 자본의 2.2배를 반환했습니다.
중간 열은 이 천장이 얼마나 집중되어 있는지 보여줍니다. 2% 이상 상승한 세션을 제외하면, 2020는 16.2%에서 -44.9%로 바뀝니다. 1년에 그러한 세션 몇 개만 새어 나가도 백테스트는 매우 멀리 이동합니다. 얕은 하락폭, 높은 적중률, 그리고 최고의 날들이 시장의 가장 폭력적인 세션에 집중된 곡선은 감사를 받을 자격이 있습니다.
어느 단일 임계값보다 교정(calibration)이 더 중요합니다. 10년 동안 매끄러운 연 40% 수익률을 주장하는 규칙은 사후적 천장의 큰 부분을 주장하는 것이며, 정직한 질문은 어떤 입력이 무엇이 일어날지 알려주었는가입니다.
선견 편향에 대해 백테스트를 테스트하는 방법
- 모든 것을 하나의 막대만큼 지연시키십시오. 모든 신호를 한 전체 기간만큼 뒤로 이동시키고 다시 실행하십시오. 진정한 우위는 약간 감소합니다. 누출은 붕괴하며, 종종 아무것도 남지 않습니다.
- 미래를 교란시키십시오. 각 결정 타임스탬프 이후의 데이터를 가져와 노이즈나 재섞기로 대체하고 결정 로직을 다시 실행하십시오. 모든 결정은 동일하게 나와야 합니다. 움직이는 결정은 알 수 없었던 무언가를 읽은 것입니다.
- 타임스탬프를 데이터로 취급하십시오. 각 입력에 대해 값이 사용 가능해진 순간을 그것이 설명하는 순간 옆에 저장하십시오. 이 두 열을 비교하지 않는 조인은 규칙을 강제할 수 없습니다.
- 해당 날짜 기준으로 유니버스를 재구축하십시오. 상장폐지 및 인수된 종목을 포함하고, 마지막 가격에 청산하며, 두 유니버스에서 동일한 규칙을 실행하여 생존자 편향 기여도를 측정하십시오.
- 천장에 대해 측정하십시오. 전략의 수익률을 동일한 기간에 대한 매수 후 보유 수치 및 완벽 예측 수치와 나란히 놓으십시오. 천장과의 거리는 샤프 비율만으로는 제공되지 않는 맥락입니다.
포지션 사이징은 이 모든 것의 하류에 있습니다. 부풀려진 승률은 부풀려진 베팅 규모를 낳으며, 이것이 바로 켈리 기준 포지션 사이징이 회계 오류를 하락폭으로 바꾸는 지점입니다. 실제 자본이 움직이기 전에도 동일한 규율이 적용됩니다: 실제 자금 투입 전 모의 거래는 주문 메커니즘을 테스트하며, 자동화된 스택은 기계 속도로 동일한 위험을 지니며, 이는 멀티 에이전트 AI 트레이딩 시스템에서 다룹니다. 저변동성 이상 현상과 같이 신중한 시점별 테스트에서 살아남는 효과는 바로 그 이유 때문에 흥미롭습니다.
FAQ
백테스팅에서 선견 편향이란 무엇인가?
이는 거래가 체결되었을 시점에 이용할 수 없었던 데이터를 모의 거래에 사용하는 것입니다. 일반적인 형태는 거래가 실행되는 가격 막대에서 결정을 내리는 것, 오늘날에도 여전히 상장된 회사들로만 유니버스를 스크리닝하는 것, 그리고 해당 날짜 이후에 수정된 재무 수치를 읽는 것입니다.
선견 편향을 어떻게 탐지할 수 있나?
모든 신호를 한 전체 기간만큼 지연시키고 다시 실행하십시오: 누출은 일반적으로 붕괴하는 반면, 실제 우위는 완만하게 감소합니다. 더 강력한 테스트는 각 결정 타임스탬프 이후의 모든 데이터를 노이즈로 대체하고 모든 결정이 변경되지 않고 나오는지 확인하는 것입니다.
생존자 편향은 선견 편향과 동일한가?
그것들은 별개이지만 함께 움직입니다. 생존자 편향은 살아남은 종목들로 구성된 표본이며, 어떤 종목이 살아남았는지 아는 것 자체가 미래의 정보이므로, 생존자 전용 유니버스는 규칙이 아닌 데이터에 위치한 선견 누출입니다.
선견 편향이 있는 백테스트가 왜 그렇게 좋아 보이는가?
누출은 결과를 예측하는 것이 아니라 선택합니다. 위 패널에서 자신의 종가로 세션을 스크리닝하는 것은 2016년에 지연 버전보다 평균 세션 수익률이 약 0.455%포인트 높았으며, 이는 얻어진 것이 아니라 정의상의 이점입니다.
시점별 데이터(point-in-time data)란 무엇인가?
시점별 데이터는 각 역사적 날짜에 알려진 대로 각 값을 저장하며, 원래 수치와 모든 이후 수정 사항, 그리고 해당일의 증권 유니버스를 포함합니다. 이는 백테스트가 입력값이 결정 이전에 존재했음을 보여주기 위해 필요한 기준입니다.
위의 모든 수치는 실제 일일 막대에 대한 저장된 버전 관리 쿼리입니다. 패널의 SQL을 확장하거나, Strasmore 터미널에서 자신의 규칙에 대한 지연 테스트를 실행하십시오.