backtesting.py 백테스팅 라이브러리 실습 — 개념을 코드로 옮기기 전에 볼 것들
매번 백테스트 함수를 새로 짜는 게 맞을까
백테스팅이란 무엇인가에서 최소한의 백테스트 코드를 직접 짜봤다. 전략 함수에 가격 데이터를 하나씩 넣어가며 수익곡선과 최대낙폭을 계산하는 구조였다. 문제는 전략을 두세 개만 더 테스트해보려 해도, 그때마다 같은 계산 로직(수수료 반영, 낙폭 계산, 거래 기록 저장)을 다시 짜야 한다는 점이다. 이 반복 작업을 대신해주는 게 백테스팅 전용 라이브러리다. 이번 글에서는 파이썬 백테스팅 라이브러리 중 하나인 backtesting.py를 예로, 라이브러리를 쓰는 흐름과 결과 해석 개념을 정리한다.
이 글은 backtesting.py의 정확한 클래스명·메서드 시그니처를 전부 확정해 알려주는 레퍼런스가 아니다. 라이브러리는 버전이 올라가면서 API가 바뀔 수 있으므로, 실제 코드를 짤 때는 반드시 공식 문서에서 최신 사용법을 확인해야 한다. 여기서는 "대략 이런 흐름으로 짜인다"는 개념만 잡는다.
왜 직접 코딩 대신 라이브러리를 쓰면 편한가
007에서 짠 코드는 가격 리스트를 순회하면서 신호를 계산하고, 자산 곡선을 직접 배열에 쌓고, 최대낙폭을 별도 함수로 계산했다. 전략 하나를 검증하는 용도로는 이 정도면 충분하다. 하지만 전략을 여러 개 비교하거나, 수수료·슬리피지 조건을 바꿔가며 재검증하거나, 결과를 그래프로 확인하려면 매번 이 계산 로직을 손으로 관리해야 한다.
백테스팅 라이브러리는 이 반복되는 부분을 표준화해서 대신 처리해준다. 데이터를 넣고 전략 로직만 정의하면, 자산 곡선 계산·수수료 반영·거래 기록·결과 지표 산출까지 라이브러리가 맡는다. 즉 "매매 판단 로직"에만 집중하고, "그 판단을 어떻게 시뮬레이션하고 집계할지"는 검증된 코드에 맡기는 셈이다. 다만 라이브러리가 계산을 대신해준다고 해서 007에서 다룬 함정(미래참조·과최적화·생존편향)이 저절로 사라지는 건 아니다. 이 부분은 뒤에서 다시 짚는다.
데이터를 넣고 전략을 정의하는 흐름 (의사코드)
backtesting.py 계열 라이브러리를 쓸 때 공통적으로 거치는 흐름은 대략 이렇다. 정확한 클래스·메서드 이름은 라이브러리 버전마다 다를 수 있으니, 아래 코드는 "이런 순서로 짠다"는 개념을 보여주는 의사코드로 봐야 한다.
python 코드 보기
# 개념 흐름을 보여주는 의사코드 — 정확한 클래스/메서드명은 공식 문서 확인
# 1. OHLCV 데이터 준비 (시가/고가/저가/종가/거래량, 시간순 정렬)
price_data = load_ohlcv_dataframe("some_symbol")
# 2. 전략 클래스 정의
class MyStrategy:
def setup(self):
# 여기서 이동평균 같은 지표를 미리 계산해 둔다
self.short_ma = moving_average(self.data.close, window=SHORT_WINDOW)
self.long_ma = moving_average(self.data.close, window=LONG_WINDOW)
def on_bar(self, i):
# 매 봉(캔들)마다 호출되는 판단 로직
if crossed_above(self.short_ma, self.long_ma, i) and not self.has_position():
self.buy()
elif crossed_below(self.short_ma, self.long_ma, i) and self.has_position():
self.sell()
# 3. 백테스트 실행 — 데이터 + 전략 + 초기자본 + 수수료율을 넣는다
engine = BacktestEngine(
data=price_data,
strategy=MyStrategy,
initial_cash=INITIAL_CASH,
commission=COMMISSION_RATE,
)
result = engine.run()
# 4. 결과 확인
print(result.summary())
result.plot()여기서 실제로 눈여겨봐야 할 건 클래스 이름이나 메서드명이 아니라 구조다. 지표 계산은 준비 단계(setup 성격의 메서드)에서 한 번만 하고, 매매 판단은 봉마다 반복 호출되는 메서드(on_bar 성격)에 넣는다는 역할 분리가 핵심이다. 초기자본·수수료율처럼 실전과 괴리를 만드는 값들은 엔진을 실행할 때 명시적으로 넣어줘야 한다는 점도 중요하다. backtesting.py를 포함해 실제 라이브러리마다 이 클래스·메서드의 정확한 이름과 인자 구성은 다르므로, 코드를 그대로 베껴 쓰지 말고 공식 문서의 예제를 기준으로 삼아야 한다.
결과를 어떻게 읽어야 하는가 — 수익곡선과 MDD
백테스트를 돌리면 라이브러리가 여러 지표를 리포트로 뽑아준다. 그중 가장 먼저 봐야 하는 두 가지가 수익곡선(equity curve)과 최대낙폭(MDD, Maximum Drawdown)이다.
수익곡선은 시간이 지나면서 계좌 자산이 어떻게 변했는지를 그린 선이다. 총수익률 하나만 보면 "최종적으로 얼마나 불었는가"만 알 수 있지만, 수익곡선을 보면 그 과정에서 꾸준히 우상향했는지, 아니면 중간에 크게 흔들리다 막판에 겨우 회복했는지가 드러난다. 같은 최종 수익률이라도 곡선의 모양이 다르면 실제로 그 전략을 실전에서 버틸 수 있는지가 완전히 달라진다.
최대낙폭(MDD)은 수익곡선이 고점에서 저점까지 얼마나 크게 떨어졌는지를 나타내는 지표다. 007의 의사코드에서도 _max_drawdown 함수로 직접 계산했던 그 개념이다. MDD가 크다는 건 그 전략을 실전에서 운용할 때 계좌가 한 번에 그만큼 줄어드는 구간을 견뎌야 한다는 뜻이다. 백테스트 결과의 최종 수익률이 아무리 좋아 보여도, MDD가 감당하기 어려운 수준이라면 그 전략을 실제로 끝까지 유지하기 어렵다. 라이브러리가 뽑아주는 승률·거래 횟수·평균 손익 같은 다른 지표들도, 결국은 "이 수익곡선이 어떤 과정을 거쳐 만들어졌는가"를 여러 각도에서 보여주는 보조 지표로 이해하면 된다.
라이브러리를 써도 과최적화는 저절로 해결되지 않는다
백테스트 과최적화 피하는 법에서 다룬 것처럼, 과최적화는 파라미터를 특정 구간 데이터에만 맞게 계속 조정할 때 생긴다. 이 문제는 백테스팅을 손으로 짜든 라이브러리로 짜든 똑같이 발생한다. 라이브러리를 쓴다고 전략 로직 자체의 함정이 사라지는 게 아니라, 계산과 집계를 대신 해줄 뿐이다.
오히려 라이브러리에서 제공하는 파라미터 최적화 기능(여러 파라미터 조합을 자동으로 돌려 가장 성과가 좋은 조합을 찾아주는 기능)은 조심해서 써야 한다. 이 기능은 편리한 만큼, 전체 데이터에 대해 "가장 잘 맞는 조합"을 기계적으로 찾아준다는 점에서 과최적화를 부추기기 쉽다. 결과가 좋게 나온 조합이 그 데이터 구간에만 우연히 맞은 것인지, 아니면 실제로 안정적인 전략인지는 별도로 확인해야 한다. 023에서 다룬 학습·검증 구간 분리와 워크포워드 검증은, 라이브러리를 쓸 때도 그대로 적용해야 하는 절차다.
자주 하는 실수
- 미래참조 데이터 누수. 지표를 미리 전체 데이터로 계산해두고 나중에 구간을 나누면, 검증 구간을 계산할 때 이미 미래 데이터가 섞여 들어간 상태가 된다. 라이브러리가 봉 단위로 판단 함수를 호출해주는 구조를 쓰더라도, 지표를 준비하는 단계에서 전체 데이터를 미리 다 써버리면 이 함정을 그대로 재현하게 된다.
- 수수료·슬리피지를 반영하지 않는다. 라이브러리마다 수수료·슬리피지의 기본값과 반영 방식이 다르다. 기본값을 그대로 두고 결과만 보면, 실제 거래비용이 빠진 채로 지나치게 좋은 성과가 나올 수 있다. 백테스트를 돌리기 전에 수수료·슬리피지 관련 설정값을 직접 확인하고 현실적인 값으로 맞춰야 한다.
- 결과를 보면서 파라미터를 계속 바꾸는 것 자체가 과최적화라는 걸 모른다. "결과가 별로니 이동평균 기간을 조금만 더 바꿔보자"를 반복하는 과정 자체가, 그 데이터에만 전략을 맞춰가는 과최적화 과정이다. 파라미터를 눈으로 보고 손으로 조정하는 것과, 023에서 다룬 워크포워드 검증처럼 구조적으로 검증 구간을 분리하는 것은 전혀 다른 일이다.
- 최적화 기능이 찾아준 '최고 조합'을 그대로 실전 파라미터로 채택한다. 라이브러리의 최적화 기능이 찾아낸 조합은 그 백테스트 구간에서 가장 성과가 좋았던 조합일 뿐이다. 이를 검증 구간이나 워크포워드 절차 없이 바로 실전에 넣으면, 과최적화된 파라미터를 그대로 실거래에 노출시키는 셈이 된다.
오늘의 정리
- 백테스팅 라이브러리는 자산 곡선 계산·수수료 반영·결과 집계처럼 반복되는 계산 로직을 대신 처리해줘서, 전략 로직 자체에 집중할 수 있게 해준다.
- 라이브러리를 쓸 때도 흐름은 데이터 준비 → 전략 클래스 정의(지표 준비 단계와 매매 판단 단계 분리) → 실행(초기자본·수수료 설정) → 결과 확인 순서다. 정확한 클래스·메서드명은 라이브러리 버전마다 다르므로 공식 문서를 기준으로 삼아야 한다.
- 수익곡선은 자산이 어떤 과정을 거쳐 변했는지, 최대낙폭(MDD)은 그 과정에서 계좌가 고점 대비 얼마나 줄어드는 구간을 견뎌야 하는지를 보여주는 지표다.
- 라이브러리를 쓴다고 과최적화 문제가 저절로 해결되지 않는다. 특히 자동 파라미터 최적화 기능은 편리한 만큼 과최적화를 부추기기 쉬워, 학습·검증 구간 분리와 워크포워드 검증을 그대로 적용해야 한다.
- 미래참조 데이터 누수와 수수료·슬리피지 미반영은 라이브러리를 쓰더라도 직접 확인하지 않으면 그대로 재현되는 실수다.
이 글은 백테스팅 라이브러리의 일반적인 사용 개념을 정리한 것이며, 정확한 API 사용법은 해당 라이브러리의 공식 문서에서 확인해야 합니다. 백테스트 성적이 좋아도 미래 수익을 보장하지 않으며, 투자 조언이 아니고, 투자 손실의 책임은 투자자 본인에게 있습니다.