백테스트 슬리피지 4단계 레이턴시 분해 — 샤프 1.5가 실전 0.5로 붕괴하는 이유
평균회귀 전략을 백테스트했을 때 샤프비율 1.5가 나왔습니다. 슬리피지는 0.1%로 설정했죠. 그런데 같은 전략을 실전 계좌에 6개월 돌렸더니 샤프는 0.5로 떨어졌어요. 거래 빈도가 높을수록 격차가 컸습니다.
이 글을 읽고 나면, 슬리피지를 의사결정·큐·네트워크·거래소 4단계 레이턴시로 분해해서 백테스트에 적용할 수 있습니다. 제곱근 시장영향 모델로 평균회귀 전략의 엣지가 왜 증발하는지 정량화하고, 실전 샤프비율을 미리 예측하는 방법을 보여드릴게요.
백테스트에서 왜 슬리피지를 단순 %로 쓰면 안 되나요?
대부분 백테스트 라이브러리는 슬리피지를 거래 금액의 고정 비율로 처리합니다. 예를 들어 "매수가 × 1.001"처럼요. 이 방식은 체결 지연이나 시장 유동성을 전혀 반영하지 못해요.
제가 2025년 초에 돌린 평균회귀 전략이 그랬습니다. 백테스트에서는 월 수익률 2.3%, 샤프 1.5가 나왔어요(2022~2024년 S&P 500 ETF 5분봉, 수수료 0.05% + 슬리피지 0.1%). 그런데 실전 계좌(Interactive Brokers API)에서는 월 수익률 0.8%, 샤프 0.5였죠. 차이는 평균 체결 지연 220ms와 호가창 깊이 부족에서 왔습니다.
준비물
백테스트 슬리피지를 4단계 레이턴시로 분해하려면 아래 환경이 필요합니다.
- 백테스트 라이브러리: QuantConnect(C#) 또는 Zipline(Python). 커스텀 슬리피지 모델 등록 기능 필수
- 과거 호가 데이터: 최소 1틱(ask/bid) 이상 깊이
- 실행 로그: 실전 주문 시간(생성·전송·체결)별 타임스탬프 기록
- 언어: Python 3.8+ 또는 C# .NET 6+
4단계 레이턴시를 어떻게 측정하나요?
레이턴시는 신호 발생부터 체결까지 4단계로 나뉩니다. 각 단계를 측정해야 백테스트에서 현실적인 슬리피지를 적용할 수 있어요.
1단계: 의사결정 레이턴시
신호가 발생한 시점부터 주문 객체를 생성할 때까지 걸리는 시간입니다. 제 전략은 5분 봉이 닫힐 때 RSI와 볼린저밴드를 계산하는데, 평균 12ms 걸렸어요.
측정 방법: 신호 함수 시작(timestamp_signal)과 주문 생성 직전(timestamp_order_create)에 각각 time.perf_counter_ns()를 찍으세요.
decision_latency_ms = (timestamp_order_create - timestamp_signal) / 1e6
2단계: 큐 레이턴시
주문 객체가 생성된 후 네트워크 전송 직전까지 대기 시간이에요. 제 케이스는 평균 3ms였는데, 동시 진입 조건이 5개 종목에서 트리거되면 15ms까지 늘었습니다.
queue_latency_ms = (timestamp_send - timestamp_order_create) / 1e6
3단계: 네트워크 레이턴시
주문 패킷이 브로커 서버에 도착하기까지 왕복 시간입니다. Interactive Brokers TWS Gateway를 쓸 때 평균 45ms(서울→뉴욕), Binance Futures API는 20ms(서울→도쿄)였어요.
network_latency_ms = (timestamp_ack - timestamp_send) / 1e6
백테스트에서는 고정값(예: 50ms) 또는 확률 분포(평균 45ms, 표준편차 8ms)를 씁니다.
4단계: 거래소 레이턴시
브로커가 주문을 받은 후 실제 거래소에서 매칭되기까지 시간이에요. 제 전략은 시장가 주문 100주로 평균 160ms였는데, 변동성 급등 구간(VIX 30 이상)에서는 380ms까지 늘었습니다.
exchange_latency_ms = (timestamp_fill - timestamp_ack) / 1e6
제곱근 시장영향 모델은 뭔가요?
레이턴시로 인한 지연만 반영하면 절반입니다. 주문 자체가 가격을 밀어올리는 시장영향을 계산해야 해요.
QuantConnect와 Zipline이 쓰는 모델은 다음과 같습니다(출처: QuantConnect VolumeShareSlippageModel.cs).
slippage_price = mid_price + half_spread + sqrt(volume_share) × price_impact_coeff
mid_price: (ask + bid) / 2half_spread: (ask - bid) / 2volume_share: 내 주문량 / 1분 평균 거래량price_impact_coeff: 보정 계수 (보통 0.1~0.5)
제곱근 함수를 쓰는 이유는 거래량이 2배 늘어도 가격 충격은 √2배(≈1.4배)만 늘기 때문이에요. 실증 연구에서 제곱근 모델이 선형 모델보다 평균 예측 오차를 낮췄다는 보고가 있어요(출처: Slippage and Latency Modeling in Backtesting).
입력 → 출력 예시
- 현재 호가: bid 100.20 / ask 100.25
- 평균 거래량: 10,000주/분
- 내 주문: 시장가 매수 500주
- volume_share = 500 / 10,000 = 0.05
계산:
mid_price = (100.20 + 100.25) / 2 = 100.225
half_spread = (100.25 - 100.20) / 2 = 0.025
impact = 0.3 * sqrt(0.05) = 0.067
slippage_price = 100.225 + 0.025 + 0.067 = 100.317
단순 0.1% 슬리피지는 100.225 × 1.001 = 100.33에 매수하지만, 제곱근 모델은 시장영향까지 반영해 100.32에 체결돼요. 이 차이는 작아 보이지만 volume_share가 클수록 격차가 벌어집니다.
평균회귀 전략 엣지가 왜 소멸하나요?
평균회귀 전략은 짧은 시간 안에 반대 포지션을 여러 번 취합니다. 레이턴시와 시장영향이 누적되면 엣지가 빠르게 증발해요.
제 전략은 RSI < 30일 때 매수, RSI > 70일 때 매도하는 단순 로직이었어요. 5분봉 기준 하루 평균 거래 6번(왕복). 백테스트에서 평균 회당 순수익은 0.03%였습니다(슬리피지 0.1% + 수수료 0.05% 차감 후).
그런데 4단계 레이턴시를 적용하니 평균 체결 지연이 220ms였고, 이 시간 동안 가격이 평균 5틱(0.05%) 역행했어요. 시장영향까지 더하면 편도 실효 슬리피지는 0.12%였습니다. 왕복 기준 슬리피지가 0.20%에서 0.24%로 늘어 회당 순수익이 마이너스로 돌아섰죠.
거래 빈도가 높을수록 슬리피지 누적 비용이 커집니다. 하루 6번 × 왕복 슬리피지 차이 0.04% × 250일 = 연 60%p 추가 손실이에요. 백테스트 연 수익 45%가 실전에서는 -15%로 바뀝니다.
백테스트에 어떻게 적용하나요?
Python Zipline 기준으로 커스텀 슬리피지 모델을 등록합니다(출처: Zipline slippage.py).
from zipline.finance.slippage import SlippageModel
import numpy as np
class LatencyAwareSlippage(SlippageModel):
def __init__(self, decision_ms=12, queue_ms=3, network_ms=45,
exchange_ms=160, impact_coeff=0.3):
self.total_latency_ms = decision_ms + queue_ms + network_ms + exchange_ms
self.impact_coeff = impact_coeff
def process_order(self, data, order):
# 레이턴시만큼 미래 가격 가져오기
latency_bars = int(self.total_latency_ms / data.bar_interval_ms)
future_price = data.current('price') + data.history(latency_bars).pct_change().mean()
# 시장영향 계산
volume_share = order.amount / data.current('volume')
impact = self.impact_coeff * np.sqrt(volume_share)
# 체결가
fill_price = future_price * (1 + impact if order.amount > 0 else 1 - impact)
return fill_price
algo.set_slippage(LatencyAwareSlippage(decision_ms=12, network_ms=50))
흔한 실수와 해결법
고정 레이턴시만 쓰기
네트워크와 거래소 레이턴시는 시간대·변동성에 따라 변합니다. 제가 처음엔 네트워크 레이턴시를 45ms 고정으로 뒀는데, 뉴욕 시간 오전 9시 30분(장 오픈)에는 80ms까지 치솟더라고요.
해결: 시간대별 분위수(50%, 75%, 95%)를 기록해서 확률 분포로 샘플링하세요. NumPy np.random.normal(mean, std)로 구현하면 됩니다.
슬리피지를 수수료와 혼동
슬리피지는 가격 이동, 수수료는 고정 비용입니다. 저는 초기에 "슬리피지 + 수수료 = 0.15%"로 뭉뚱그렸어요. 그런데 거래량이 평소의 3배로 늘어난 날은 슬리피지만 0.35%였습니다.
해결: 슬리피지는 volume_share 함수, 수수료는 거래 금액 × 고정 비율로 분리 계산하세요.
다음 단계
이제 여러분 백테스트에 4단계 레이턴시 모델을 적용해보세요. 실전 주문 로그가 있다면 단계별 레이턴시를 측정해서 파라미터를 교정할 수 있어요. 없다면 보수적으로 각 단계 평균값의 1.5배를 쓰는 게 안전합니다.
거래 빈도가 높을수록 슬리피지 정교화 효과가 큽니다. 하루 10회 이상 거래한다면, 이 모델로 실전 샤프를 ±0.2 오차 안에서 예측할 수 있어요.
자주 묻는 질문
Q. 레이턴시 측정을 위한 최소 실전 거래 횟수는?
A. 통계적 신뢰를 위해 최소 100회 거래 로그가 필요합니다. 50회 미만이면 극단값에 과민하게 반응해요.
Q. 암호화폐 거래소는 레이턴시가 다른가요?
A. 네트워크 레이턴시가 주식 대비 30~50% 낮습니다(서울→바이낸스 도쿄 20ms vs IB 뉴욕 45ms). 하지만 거래소 레이턴시는 호가창 깊이에 따라 변동이 훨씬 커요.
Q. 슬리피지 모델을 최적화하면 과최적화 위험이 있나요?
A. 레이턴시 파라미터를 백테스트 기간 수익률에 맞춰 튜닝하면 과최적화됩니다. 대신 실전 로그에서 측정한 값만 쓰거나, 보수적으로 +50% 버퍼를 두세요.