Python을 이용한 트레이딩 전략 백테스팅 (Backtesting): 개발자를 위한 완전 가이드
요약
Python을 활용하여 트레이딩 전략을 백테스팅할 때 발생하는 방법론적 오류와 편향을 방지하는 가이드를 제공합니다. Look-ahead bias, 생존 편향 등 수익률을 왜곡하는 요인을 분석하고 현실적인 비용 모델링의 중요성을 강조합니다.
핵심 포인트
- Look-ahead bias와 생존 편향은 백테스트 수익률을 과도하게 부풀림
- 수수료, 슬리피지, 스프레드 등 거래 비용을 반드시 모델링해야 함
- 90% 이상의 높은 승률은 과적합(Overfitting)의 위험 신호일 수 있음
- 전진 분석과 표본 외 테스트를 통해 전략의 지속 가능성을 검증해야 함
미국 주식 거래량의 약 70%는 알고리즘에 의해 실행되며, 그중 거의 모든 알고리즘은 실제 자금을 투입하기 전 동일한 방식으로 검증되었습니다. 바로 **Python을 이용한 트레이딩 전략 백테스팅 (Backtesting trading strategies in Python)**입니다. 하지만 이를 시도하는 대부분의 개발자는 결국 돈을 잃습니다. 코드가 틀려서가 아니라, 방법론(methodology)이 틀렸기 때문입니다. 깔끔한 전략을 작성하고, 백테스트 결과로 아름다운 자산 곡선(equity curve)을 확인한 뒤 배포했지만, 시장은 3주 만에 이를 처참하게 무너뜨립니다. 시뮬레이션상의 영광과 실전에서의 고통 사이의 이 간극에는 이름이 있습니다. 바로 편향(bias)입니다. 이 가이드는 진실을 말해주는 백테스터를 구축하는 방법을 보여줍니다. 예쁜 곡선을 신뢰하는 것을 멈추십시오. 정직한 곡선을 설계하기 시작하십시오.
핵심 요약 (Key Takeaways)
- **Python을 이용한 트레이딩 전략 백테스팅 (Backtesting trading strategies in Python)**에는 벡터화된 pandas 연산, 현실적인 비용 모델링, 그리고 신호 생성(signal generation)과 실행(execution)의 엄격한 분리가 필요합니다.
- 미래 참조 편향 (Look-ahead bias)과 생존 편향 (Survivorship bias)은 일반적인 개인 투자자 환경에서 백테스트 수익률을 연간 2~4%포인트 부풀립니다.
- 왕복 거래(round trip trade) 비용은 수수료, 슬리피지 (slippage), 스프레드 (spread)를 포함하여 0.2%에서 0.9%에 달합니다. 비용을 무시하면 손실 전략이 가짜 승리 전략으로 변질됩니다.
- 백테스트에서 90% 이상의 승률이 나온다면, 이는 우위 (edge)의 증거가 아니라 과적합 (overfitting)의 위험 신호인 경우가 많습니다.
- 전진 분석 (Walk-forward analysis)과 표본 외 테스트 (Out-of-sample testing)는 한 번만 작동했던 전략과 반복적으로 작동하는 전략을 구분해 줍니다.
목차 (Table of Contents)
- 왜 대부분의 Python 백테스트는 당신에게 거짓말을 하는가
- Pandas를 이용한 벡터화된 백테스터 구축하기
- 비용, 슬리피지 (Slippage), 그리고 실행 현실 모델링하기
- 전략 검증하기: 전진 분석 (Walk-Forward) 및 표본 외 테스트 (Out-of-Sample Testing)
- 백테스터 작성을 멈추고 전략 테스트를 시작해야 할 때
- 결론
- 자주 묻는 질문 (FAQ)
왜 대부분의 Python 백테스트는 당신에게 거짓말을 하는가
당신의 백테스트는 성적표가 아니라 적(adversary)입니다. 백테스트는 존재하지도 않는 수익률을 보여주기 위해 당신의 허술한 코드 한 줄 한 줄을 이용할 것입니다. **Python을 이용한 트레이딩 전략 백테스팅 (backtesting trading strategies in Python)**에서 가장 비용이 많이 드는 두 가지 버그는 런타임 에러(runtime errors)가 아닙니다. 그것은 컴파일은 잘 되면서 아주 그럴싸하게 거짓말을 하는 침묵의 방법론적 오류(methodology errors)입니다.
Look-Ahead Bias (앞서보기 편향): 실제 돈을 잃게 만드는 Off-By-One 에러
Look-ahead bias (앞서보기 편향)란 당신의 전략이 의사결정 시점에는 사용할 수 없었던 정보를 사용하는 것을 의미합니다. 전형적인 사례는 인덱싱(indexing) 실수입니다. 오늘의 종가(close)를 기준으로 신호(signal)를 계산한 다음, 오늘의 종가에 매수하는 것을 시뮬레이션하는 경우입니다. 실시간 트레이딩(live trading)에서는 그렇게 할 수 없습니다. 봉(bar)이 완료되기 전까지는 종가가 존재하지 않기 때문입니다.
해결책은 단 한 줄이며, 이를 건너뛰는 것이 개인 투자자들의 백테스트에서 가장 흔하게 발생하는 실패 요인입니다:
# 잘못된 예: 동일한 봉(bar)에서 신호와 거래를 동시에 수행
df["position"] = np.where(df["sma_fast"] > df["sma_slow"], 1, 0)
...
단 한 줄의 .shift(1) 처리가 전략의 시뮬레이션 연간 수익률을 통상 15%에서 40%까지 깎아먹습니다. 만약 .shift(1)을 추가했을 때 당신의 우위(edge)가 사라진다면, 당신은 애초에 우위를 가졌던 것이 아닙니다. 당신은 그저 타임머신을 가졌던 것뿐입니다.
미묘한 변형들은 명백한 오류보다 더 나쁩니다. 확장 윈도우(expanding window)나 롤링 윈도우(rolling window) 대신 전체 시계열 평균(full series mean)을 사용하여 z-score를 계산하는 것은 모든 봉에 미래 정보를 유출(leak)하는 것과 같습니다. 데이터를 분할(splitting)하기 전에 전체 데이터셋에 대해 sklearn의 fit을 사용하여 피처(features)를 정규화(normalizing)하는 것도 같은 결과를 초래합니다. 시뮬레이션 루프(simulation loop)가 시작되기 전에 전체 컬럼(column)에 접근하는 모든 연산은 의심 대상입니다. SQL 인젝션(SQL injection)을 점검하듯이 이들을 감사(audit)하십시오. 그렇지 않으면 존재한다고 가정해야 합니다.
Survivorship Bias (생존 편향): 승자만을 대상으로 한 테스트
만약 지난 20년 동안 현재의 S&P 500 구성 종목 리스트를 대상으로 백테스팅 (Backtesting)을 수행한다면, 당신은 살아남은 기업들만을 테스트하는 것입니다. 상장 폐지되거나, 파산하거나, 인수된 종목들은 데이터셋에서 사라졌으며, 이들은 실제 전략이 하락 과정에서 매수했을 바로 그 주식들입니다. 학술적 추정치에 따르면 생존 편향 (Survivorship Bias)으로 인한 인플레이션은 유니버스 (Universe)와 기간에 따라 연간 약 1%에서 4%에 달하며, 이는 개인 투자자의 전략이 주장하는 전체 우위 (Edge)보다 큰 경우가 많습니다.
통계적 실패 모드 (Failure modes)에 대해 더 깊이 다루고 싶다면, 전략 코드를 한 줄이라도 더 작성하기 전에 backtesting에 관한 위키피디아 항목을 참고하는 것이 확실한 기준점이 될 것입니다. 한 번 읽어보십시오. 그것이 시장에 지불할 수업료의 4분의 1을 아껴줄 것입니다.
Pandas를 이용한 벡터화된 백테스터 (Vectorized Backtester) 구축
루프 (Loops)는 실시간 트레이딩 (Live trading)을 위한 것입니다. 벡터 (Vectors)는 연구를 위한 것입니다. pandas에서의 벡터화된 백테스트는 10년 치의 일일 데이터를 밀리초 단위로 처리합니다. 이는 정직한 연구란 단 하나의 변수가 아니라 수백 개의 파라미터 (Parameter) 변형을 테스트하는 것을 의미하기 때문에 매우 중요합니다.
다음은 약 40줄로 작성된 완전하고 정직한 이동 평균 교차 (Moving average crossover) 백테스트 예시입니다. 여기에는 대부분의 튜토리얼이 생략하는 시프트 (Shift), 비용 (Costs), 그리고 벤치마크 (Benchmark) 비교가 포함되어 있습니다:
import yfinance as yf
import pandas as pd
import numpy as np
...
세 가지 세부 사항이 이 코드를 장식용이 아닌 정직한 코드로 만듭니다. shift(1)은 앞서보기 (Look-ahead) 편향을 제거합니다. auto_adjust=True 플래그는 주식 분할과 배당을 처리하여 유령 가격 차이 (Phantom price gaps)를 대상으로 거래하지 않도록 합니다. 그리고 하단의 매수 후 보유 (Buy-and-hold) 라인은 당신의 귀무 가설 (Null hypothesis)입니다. 만약 당신의 전략이 아무것도 하지 않는 것보다 나은 성과를 낼 수 없다면, 올바른 거래 횟수는 0이어야 합니다.
데이터 품질이 승패의 절반이다
쓰레기 데이터는 확신에 찬 쓰레기를 만들어냅니다. 어떤 실행 결과도 신뢰하기 전에, 데이터셋에 대해 다음 세 가지를 반드시 확인하십시오. 첫째, 가격은 액면 분할(splits)과 배당(dividends)에 대해 수정(adjusted)되어야 합니다. 수정되지 않은 AAPL 시리즈는 2020년 4대 1 액면 분할 시점에 가짜 75% 폭락을 보여주며, 당신의 전략은 이를 보고
수학적 계산은 냉혹하며 이를 반드시 내재화해야 합니다. 당신의 신호(signal)가 거래당 0.15%의 진정한 총 우위(gross edge)를 가지고 있다고 가정해 봅시다. 이는 지구상의 대부분의 개인용 시스템보다 앞서는 수치입니다. 하지만 왕복 비용(round-trip cost)이 0.20%라면, 당신의 실제 기대값(expectancy)은 거래당 -0.05%가 됩니다. 당신은 실제 우위를 가지고 있음에도 불구하고 여전히 돈을 잃을 것이 확실시됩니다. 이것이 전문가들이 지표(indicator)에 집착하는 아마추어들과 달리 실행(execution)에 집착하는 이유이며, 비용 모델링(cost modeling)이 백테스터(backtester)의 마지막 단계가 아닌 첫 번째 버전부터 포함되어야 하는 이유입니다.
반드시 모델링해야 하는 세 가지 비용
수수료(Commission)는 가장 간단합니다. 미리 알려진 고정 비율 또는 주당 수수료입니다. 스프레드(Spread)는 매수 호가(bid)와 매도 호가(ask) 사이의 차이입니다. 당신은 매도 호가에 사고 매수 호가에 팔기 때문에, 모든 왕복 거래는 스프레드만큼을 마켓 메이커(market maker)에게 기부하게 됩니다. 슬리피지(Slippage)는 당신의 신호를 트리거한 가격과 실제로 체결된 가격 사이의 차이이며, 이는 주문 규모, 변동성(volatility), 그리고 시장이 당신에게 불리하게 움직이는 속도에 따라 커집니다.
2026년 유동성이 높은 미국 대형주(large caps)를 위한 방어 가능한 비용 모델은 다음과 같습니다: 수수료 0.0% ~ 0.05% (대부분의 개인 브로커는 0%), 스프레드 0.01% ~ 0.05%, 슬리피지 0.05% ~ 0.15%. 암호화폐(crypto)의 경우 슬리피지를 3배로 잡으십시오. 소형주(small caps)나 이색 외환 쌍(exotic forex pairs)의 경우 0.3%로 모델링하거나 거래를 포기하십시오.
def apply_costs(returns, trades, commission=0.0005,
spread=0.0003, slippage=0.001):
total_cost = commission + spread + slippage
...
비용 가정에 대한 스트레스 테스트 (Stress Test)
단 하나의 비용 수치만 선택하지 마십시오. 범위를 훑으며 테스트(Sweep)하십시오. 동일한 백테스트를 거래당 0.05%, 0.10%, 0.20%, 0.40%에서 각각 실행하고 네 개의 자산 곡선(equity curves)을 함께 그리십시오. 견고한(robust) 전략은 이 범위 전반에 걸쳐 완만하게 성능이 저하됩니다. 커브 피팅(curve-fit)된 전략은 0.10% 근처 어디에선가 무너질 것이며, 당신은 이를 증권사 거래 내역서가 아닌 pandas에서 발견해야 합니다.
전략의 빈도(frequency)가 판돈을 결정합니다. 월간 리밸런싱(rebalance)은 비용을 거의 체감하지 못합니다. 하루 평균 두 번의 거래를 하는 전략은 전적으로 실행 모델링(execution modeling)에 의해 생사가 결정됩니다. 높은 회전율(turnover)은 낙관론이 아닌 증명을 요구합니다.
전략 검증: 워크 포워드(Walk-Forward) 및 표본 외(Out-of-Sample) 테스트
이 분야 전체를 관통하는 불편한 진실이 하나 있습니다. 90% 이상의 승률을 보이는 백테스트(backtest)는 대개 기능이 아니라 위험 신호(red flag)입니다. 충분한 파라미터(parameter)가 있다면, 어떤 과거 데이터셋이든 완벽하게 맞출 수 있지만 정작 아무것도 예측하지 못하게 됩니다. 전략 연구에서 과적합(Overfitting)은 위험 요소가 아닙니다. 그것은 기본값(default outcome)이며, 검증(validation)은 이에 맞서 싸우는 방법입니다.
시장에 적용하는 익숙한 훈련/테스트 분할 (Train/Test Split)
최소한의 표준은 표본 외(out-of-sample) 테스트입니다. 2015년부터 2021년까지 파라미터를 최적화한 다음, 고정된(frozen) 전략을 2022년부터 2025년까지 정확히 단 한 번만 실행하십시오. 만약 성과가 표본 내(in-sample) 결과의 약 30% 범위 내에서 유지된다면, 무언가 가능성이 있을 수 있습니다. 만약 성과가 증발한다면, 당신은 노이즈(noise)에 맞춘 것입니다. 핵심은 "정확히 단 한 번"에 있습니다. 테스트 세트(test set)를 훔쳐보고 조정할 때마다, 당신은 조용히 그것을 훈련 데이터(training data)로 전환해 버리는 셈입니다.
워크 포워드 분석 (Walk-Forward Analysis): 롤링(Rolling) 버전
워크 포워드 분석은 이를 롤링 방식으로 만듭니다. 3년의 윈도우(window)에서 최적화하고, 다음 6개월을 표본 외(out-of-sample)로 거래한 뒤, 앞으로 슬라이드하여 반복합니다. 이 표본 외 세그먼트(segments)들을 하나로 엮으면, 그 연결된 곡선(concatenated curve)만이 당신이 믿을 수 있는 유일한 성과입니다.
def walk_forward(df, train_years=3, test_months=6):
results = []
start = df.index.min()
...
몬테카를로 (Monte Carlo): 단일 역사의 심문
깔끔한 워크 포워드 결과조차 역사의 단 하나의 경로일 뿐입니다. 몬테카를로 시뮬레이션(Monte Carlo simulation)은 거래 시퀀스(trade sequence)를 수천 번 재표집(resample)하여 결과의 분포를 구축함으로써, 당신이 운 좋게 보지 못했던 드로다운(drawdown)을 드러냅니다. 구현은 열 줄이면 충분합니다. 개별 거래 수익률 리스트를 가져와서, np.random.choice를 사용하여 복원 추출(with replacement) 방식으로 10,000번 셔플 샘플링(shuffle-sample)하고, 각 샘플을 자산 곡선(equity path)으로 복리 계산한 뒤, 결과 분포에서 최대 드로다운(max drawdown)의 5백분위수(5th percentile)를 읽어내면 됩니다. 만약 그 수치가 당신이 감당할 수 없는 수준이라면, 그 전략은 샤프 지수(Sharpe ratio)와 상관없이 실패한 것입니다.
시장 국면(Regime) 또한 그만큼 중요합니다. 2023년부터 2025년 사이의 강세장(Bull run) 내에서만 검증된 전략은 하락장(Bear market), 금리 충격(Rate shock), 또는 VIX 지수를 일시적으로 65 이상으로 끌어올렸던 2024년 8월의 엔 캐리 트레이드 청산(Yen-carry unwind)과 같은 변동성 급증(Volatility spike)을 한 번도 경험해보지 못한 것입니다. 단순한 200일 이동 평균(200-day moving average) 필터나 실현 변동성 버킷(Realized volatility buckets)을 사용하여 백테스트를 국면별로 세분화하십시오. 그리고 전략이 최적의 국면에서만 잔치를 벌이는 것이 아니라, 최소한 최악의 국면에서도 살아남을 수 있는지 요구해야 합니다. 이것이 바로 **Python을 이용한 트레이딩 전략 백테스팅 (Backtesting trading strategies in Python)**에서 취미 활동가와 전문 운용자를 가르는 지점입니다. 취미 활동가는 하나의 수치만을 보고하지만, 운용자는 분포(Distribution)를 보고합니다.
백테스터 작성을 멈추고 전략 테스트를 시작해야 할 때
모든 개발자의 알고리즘 트레이딩(Algo-trading) 여정에는 인프라를 구축하는 데 4개월을 쓰고, 실제 전략을 연구하는 데는 4시간을 썼다는 사실을 깨닫는 순간이 옵니다. 백테스터 자체가 프로젝트가 되어버린 것입니다. 그것은 즐거운 프로젝트입니다. 하지만 엔진을 구축하는 것이 아니라 우위(Edge)를 찾는 것이 목표라면, 대개 잘못된 방향입니다.
직접 구축하는 과정은 그 어떤 플랫폼도 가르쳐줄 수 없는 것들을 알려줍니다. 직접 미래 참조 오류(Look-ahead bug)를 배포해 본 적이 있다면, 다시는 백테스트를 맹목적으로 신뢰하지 않게 될 것입니다. 모든 개발자는 위에서 언급한 40줄짜리 버전을 적어도 한 번은 직접 만들어 보아야 합니다. 하지만 데이터 파이프라인(Data pipelines), 기업 권리 사항 처리(Corporate action handling), 그리스 지표(Greeks)를 포함한 옵션 체인(Options chains), 다중 자산 지원(Multi-asset support), 그리고 몬테카를로(Monte Carlo) 도구 등은 각각 그 자체로 하나의 엔지니어링 프로젝트이며, 이미 해결된 문제들입니다. **Python을 이용한 트레이딩 전략 백테스팅 (Backtesting trading strategies in Python)**을 수동으로 해보는 것은 통과의례와 같지만, 프로덕션 연구 스택(Production research stack)을 유지 관리하는 것만으로도 이미 두 번째 직업을 가진 것이나 다름없습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기