슬리피지란 무엇인가?
슬리피지(Slippage)는 주문을 넣은 가격과 실제 체결된 가격의 차이를 말합니다. 백테스트에서 연 50% 수익을 보여주던 전략이 실전에서 10%도 못 내는 주범이 바로 슬리피지입니다. 퀀트 자동매매에서 슬리피지를 이해하고 최소화하는 것은 전략 알파만큼 중요합니다.
슬리피지는 크게 두 가지로 나뉩니다:
- 시장 슬리피지: 호가창의 스프레드와 유동성 부족으로 발생
- 지연 슬리피지: 시그널 생성부터 주문 체결까지의 시간 지연으로 발생
슬리피지가 수익에 미치는 영향
슬리피지의 영향은 거래 빈도에 비례합니다. 파이썬으로 시뮬레이션해봅시다:
import numpy as np
import pandas as pd
def simulate_slippage_impact(
annual_return: float,
trades_per_day: int,
slippage_bps: float,
trading_days: int = 252
):
"""슬리피지가 연수익률에 미치는 영향 시뮬레이션"""
total_trades = trades_per_day * trading_days
slippage_per_trade = slippage_bps / 10000
total_slippage = total_trades * slippage_per_trade
net_return = annual_return - total_slippage
return {
"gross_return": f"{annual_return:.1%}",
"total_trades": total_trades,
"total_slippage": f"{total_slippage:.1%}",
"net_return": f"{net_return:.1%}",
}
# 시나리오 비교
scenarios = [
("저빈도 (일 1회, 5bps)", 1, 5),
("중빈도 (일 10회, 5bps)", 10, 5),
("고빈도 (일 50회, 3bps)", 50, 3),
("고빈도 (일 50회, 10bps)", 50, 10),
]
for name, trades, bps in scenarios:
result = simulate_slippage_impact(0.30, trades, bps)
print(f"{name}: 순수익률 = {result['net_return']}")
결과를 보면 일 50회 거래에 10bps 슬리피지만 발생해도 연 30% 수익이 마이너스로 전환됩니다. 거래 빈도가 높을수록 슬리피지 최적화의 가치가 기하급수적으로 증가합니다.
호가창 분석과 유동성 측정
슬리피지를 줄이려면 먼저 호가창(Order Book)의 유동성을 정량화해야 합니다:
class OrderBookAnalyzer:
"""호가창 기반 슬리피지 예측기"""
def __init__(self, bids: list, asks: list):
"""
bids: [(price, quantity), ...] 내림차순
asks: [(price, quantity), ...] 오름차순
"""
self.bids = sorted(bids, key=lambda x: -x[0])
self.asks = sorted(asks, key=lambda x: x[0])
@property
def spread_bps(self) -> float:
"""비드-애스크 스프레드 (bps)"""
mid = (self.bids[0][0] + self.asks[0][0]) / 2
spread = self.asks[0][0] - self.bids[0][0]
return (spread / mid) * 10000
def estimate_slippage(self, side: str, quantity: float) -> dict:
"""주문 수량에 따른 예상 슬리피지 계산"""
book = self.asks if side == "buy" else self.bids
mid = (self.bids[0][0] + self.asks[0][0]) / 2
filled = 0
cost = 0
levels_used = 0
for price, qty in book:
fill_qty = min(qty, quantity - filled)
cost += fill_qty * price
filled += fill_qty
levels_used += 1
if filled >= quantity:
break
avg_price = cost / filled if filled > 0 else 0
slippage_bps = abs(avg_price - mid) / mid * 10000
return {
"avg_price": round(avg_price, 2),
"slippage_bps": round(slippage_bps, 2),
"levels_used": levels_used,
"filled": filled,
}
# 사용 예시
bids = [(50000, 1.0), (49990, 2.0), (49980, 3.0)]
asks = [(50010, 0.5), (50020, 1.5), (50030, 4.0)]
analyzer = OrderBookAnalyzer(bids, asks)
print(f"스프레드: {analyzer.spread_bps:.1f} bps")
print(f"0.5 BTC 매수: {analyzer.estimate_slippage('buy', 0.5)}")
print(f"3.0 BTC 매수: {analyzer.estimate_slippage('buy', 3.0)}")
주문 수량이 커질수록 호가를 여러 단계 소비하면서 슬리피지가 급격히 증가합니다. 이를 시장 충격(Market Impact)이라고 하며, 대형 주문일수록 주문 분할이 필수적입니다.
주문 분할 알고리즘: TWAP과 VWAP
대량 주문의 슬리피지를 줄이는 가장 효과적인 방법은 주문 분할(Order Splitting)입니다:
import time
from datetime import datetime, timedelta
class TWAPExecutor:
"""TWAP(Time-Weighted Average Price) 주문 분할"""
def __init__(self, total_qty: float, duration_min: int,
num_slices: int):
self.total_qty = total_qty
self.slice_qty = total_qty / num_slices
self.interval = duration_min * 60 / num_slices
self.num_slices = num_slices
def generate_schedule(self) -> list:
"""주문 스케줄 생성"""
schedule = []
now = datetime.now()
for i in range(self.num_slices):
exec_time = now + timedelta(seconds=self.interval * i)
schedule.append({
"slice": i + 1,
"time": exec_time.strftime("%H:%M:%S"),
"quantity": round(self.slice_qty, 6),
})
return schedule
class AdaptiveExecutor:
"""유동성 적응형 주문 분할"""
def __init__(self, total_qty: float, max_participation: float = 0.1):
self.total_qty = total_qty
self.remaining = total_qty
self.max_participation = max_participation
def next_slice(self, current_volume: float,
current_spread_bps: float) -> dict:
"""현재 시장 상황에 따라 다음 주문량 결정"""
# 거래량의 일정 비율 이하로 제한
max_qty = current_volume * self.max_participation
# 스프레드가 넓으면 주문량 축소
spread_factor = max(0.2, 1.0 - current_spread_bps / 50)
adjusted_qty = min(max_qty * spread_factor, self.remaining)
self.remaining -= adjusted_qty
return {
"quantity": round(adjusted_qty, 6),
"remaining": round(self.remaining, 6),
"spread_factor": round(spread_factor, 3),
}
# TWAP 예시: 10 BTC를 30분간 10분할
twap = TWAPExecutor(10.0, duration_min=30, num_slices=10)
for s in twap.generate_schedule()[:3]:
print(s)
TWAP은 시간 균등 분할, VWAP은 거래량 가중 분할입니다. 실전에서는 적응형 분할(Adaptive Executor)이 가장 효과적입니다. 스프레드와 거래량을 실시간으로 모니터링하며 주문 크기를 동적으로 조절합니다.
지정가 vs 시장가: 최적 주문 유형 선택
주문 유형에 따라 슬리피지와 체결 확률의 트레이드오프가 달라집니다:
class SmartOrderRouter:
"""시장 상황에 따른 주문 유형 자동 선택"""
def __init__(self, urgency: float = 0.5):
"""urgency: 0(저긴급)~1(고긴급)"""
self.urgency = urgency
def decide(self, spread_bps: float, volatility: float,
time_remaining_pct: float) -> dict:
"""
spread_bps: 현재 스프레드
volatility: 최근 변동성
time_remaining_pct: 남은 실행 시간 비율 (1→0)
"""
# 긴급도 점수 계산
urgency_score = (
self.urgency * 0.4
+ (1 - time_remaining_pct) * 0.3
+ min(volatility * 100, 1.0) * 0.3
)
if urgency_score > 0.7:
return {
"type": "market",
"reason": "높은 긴급도 - 즉시 체결 우선",
"expected_slippage_bps": spread_bps * 0.6,
}
elif spread_bps < 5:
return {
"type": "limit_aggressive",
"reason": "좁은 스프레드 - 공격적 지정가",
"price_offset_bps": 1,
"expected_slippage_bps": spread_bps * 0.3,
}
else:
return {
"type": "limit_passive",
"reason": "넓은 스프레드 - 수동적 지정가로 비용 절감",
"price_offset_bps": -2,
"expected_slippage_bps": 0,
}
router = SmartOrderRouter(urgency=0.3)
decision = router.decide(spread_bps=8.5, volatility=0.02,
time_remaining_pct=0.7)
print(decision)
핵심 원칙: 급할수록 시장가, 여유 있을수록 지정가. 그러나 변동성이 높은 시장에서 지정가를 고집하면 체결이 안 되어 오히려 더 불리한 가격에 체결될 수 있습니다.
백테스트에 슬리피지 반영하기
백테스트의 함정 중 가장 흔한 것이 슬리피지 미반영입니다. 현실적인 슬리피지 모델을 적용해봅시다:
class RealisticSlippageModel:
"""현실적 슬리피지 모델"""
def __init__(self, base_bps: float = 3.0,
impact_coeff: float = 0.1):
self.base_bps = base_bps
self.impact_coeff = impact_coeff
def estimate(self, order_size: float, avg_volume: float,
volatility: float) -> float:
"""
Square-root 시장 충격 모델
슬리피지 = 기본 + 계수 * 변동성 * sqrt(참여율)
"""
participation = order_size / avg_volume if avg_volume > 0 else 1
impact_bps = (
self.impact_coeff
* volatility * 10000
* np.sqrt(participation)
)
total_bps = self.base_bps + impact_bps
return round(total_bps, 2)
# 시나리오별 슬리피지 추정
model = RealisticSlippageModel()
scenarios = [
("소량 주문", 1000, 1000000, 0.02),
("중량 주문", 50000, 1000000, 0.02),
("대량+고변동성", 100000, 500000, 0.05),
]
for name, size, vol, sigma in scenarios:
bps = model.estimate(size, vol, sigma)
print(f"{name}: {bps} bps")
Square-root 모델은 기관 투자자들이 가장 많이 사용하는 시장 충격 모델입니다. 주문 크기의 제곱근에 비례해 슬리피지가 증가한다는 실증 연구에 기반합니다.
거래소 수수료 최적화
슬리피지 외에 거래 수수료도 수익을 잠식하는 주요 비용입니다:
class FeeOptimizer:
"""거래소 수수료 최적화"""
def __init__(self):
self.fee_tiers = {
"maker": [
(0, 0.0010), # 기본: 0.10%
(1000000, 0.0008), # 월 100만 이상: 0.08%
(5000000, 0.0006), # 월 500만 이상: 0.06%
(10000000, 0.0004), # 월 1000만 이상: 0.04%
],
"taker": [
(0, 0.0015),
(1000000, 0.0012),
(5000000, 0.0010),
(10000000, 0.0008),
],
}
def get_fee(self, monthly_volume: float,
order_type: str = "taker") -> float:
tiers = self.fee_tiers[order_type]
fee = tiers[0][1]
for threshold, rate in tiers:
if monthly_volume >= threshold:
fee = rate
return fee
def annual_savings(self, monthly_volume: float,
trades_per_month: int) -> dict:
"""메이커 전환 시 연간 절감액 계산"""
taker_fee = self.get_fee(monthly_volume, "taker")
maker_fee = self.get_fee(monthly_volume, "maker")
avg_trade_size = monthly_volume / trades_per_month
taker_cost = monthly_volume * taker_fee * 12
maker_cost = monthly_volume * maker_fee * 12
return {
"taker_annual": f"${taker_cost:,.0f}",
"maker_annual": f"${maker_cost:,.0f}",
"savings": f"${taker_cost - maker_cost:,.0f}",
}
optimizer = FeeOptimizer()
print(optimizer.annual_savings(2000000, 500))
메이커(지정가) 주문은 테이커(시장가)보다 수수료가 낮고, 거래소에 따라 리베이트를 주기도 합니다. 긴급하지 않은 주문을 메이커로 전환하는 것만으로 연간 수천만 원의 비용을 절감할 수 있습니다.
실전 체크리스트
- 백테스트 시: 반드시 슬리피지 3~10bps를 반영하고, 거래 수수료도 포함할 것
- 주문 크기: 일평균 거래량의 1% 이하로 유지 (시장 충격 최소화)
- 주문 분할: 1회 주문이 평균 거래량의 0.1%를 넘으면 TWAP/VWAP 분할 적용
- 메이커 비율: 전체 주문의 70% 이상을 지정가로 실행
- 스프레드 모니터링: 스프레드가 평소의 2배 이상이면 주문 보류
- 포지션 사이징과 연계: 슬리피지 비용을 포지션 크기 결정에 반영
결론
퀀트 자동매매에서 알파를 만드는 것과 알파를 지키는 것은 별개의 문제입니다. 아무리 좋은 시그널도 슬리피지와 수수료로 녹아내리면 의미가 없습니다. 슬리피지 최적화는 화려하지 않지만, 실전 수익률을 결정짓는 가장 현실적인 엣지입니다. 호가창 분석, 주문 분할, 스마트 라우팅을 시스템에 통합하면 동일 전략에서도 연 5~15%p의 수익률 개선을 기대할 수 있습니다.