파이썬 TWAP 주문 실행 전략

TWAP 주문 실행 전략이란?

TWAP(Time-Weighted Average Price)은 대량 주문을 일정 시간에 걸쳐 균등하게 분할 실행하는 알고리즘 트레이딩 전략입니다. 기관 투자자와 퀀트 트레이더가 시장 충격(Market Impact)을 최소화하기 위해 가장 널리 사용하는 실행 알고리즘 중 하나입니다.

자동매매 봇을 운영할 때, 한 번에 큰 수량을 매수하거나 매도하면 슬리피지가 커지고 호가창에 영향을 줍니다. TWAP은 이 문제를 해결하는 가장 기본적이면서도 효과적인 방법입니다.

TWAP vs VWAP 차이점

TWAP과 VWAP 자동매매 전략은 자주 비교됩니다. 핵심 차이를 정리하면:

구분 TWAP VWAP
분할 기준 시간 균등 분할 거래량 가중 분할
구현 난이도 낮음 중간 (거래량 프로파일 필요)
적합한 시장 24시간 암호화폐, 유동성 낮은 시장 주식시장 등 거래량 패턴이 뚜렷한 시장
시장 충격 예측 가능하게 분산 거래량 많은 시간에 집중

24시간 운영되는 암호화폐 자동매매에서는 거래량 프로파일 예측이 어렵기 때문에 TWAP이 더 실용적인 선택인 경우가 많습니다.

파이썬 TWAP 자동매매 봇 구현

아래는 ccxt 라이브러리를 활용한 TWAP 주문 실행기의 핵심 구현입니다.

import ccxt
import time
import math

class TWAPExecutor:
    """TWAP 주문 실행기: 대량 주문을 시간 균등 분할 실행"""

    def __init__(self, exchange, symbol, side, total_qty, duration_min, num_slices):
        self.exchange = exchange
        self.symbol = symbol
        self.side = side              # 'buy' 또는 'sell'
        self.total_qty = total_qty    # 총 주문 수량
        self.duration_min = duration_min  # 전체 실행 시간(분)
        self.num_slices = num_slices  # 분할 횟수
        self.slice_qty = total_qty / num_slices
        self.interval = (duration_min * 60) / num_slices  # 슬라이스 간격(초)
        self.executed = []

    def execute(self):
        """TWAP 주문 실행 루프"""
        print(f"TWAP 시작: {self.total_qty} {self.symbol}")
        print(f"  분할: {self.num_slices}회, 간격: {self.interval:.0f}초")

        for i in range(self.num_slices):
            try:
                # 시장가 주문으로 슬라이스 실행
                order = self.exchange.create_market_order(
                    symbol=self.symbol,
                    side=self.side,
                    amount=self.slice_qty
                )
                filled_price = order.get('average', order.get('price', 0))
                self.executed.append({
                    'slice': i + 1,
                    'qty': self.slice_qty,
                    'price': filled_price,
                    'timestamp': time.time()
                })
                print(f"  [{i+1}/{self.num_slices}] "
                      f"{self.slice_qty:.4f} @ {filled_price:.2f}")

            except Exception as e:
                print(f"  [{i+1}] 주문 실패: {e}")

            # 마지막 슬라이스가 아니면 대기
            if i < self.num_slices - 1:
                time.sleep(self.interval)

        return self.get_summary()

    def get_summary(self):
        """실행 결과 요약"""
        if not self.executed:
            return {"status": "실패", "filled": 0}

        total_cost = sum(e['qty'] * e['price'] for e in self.executed)
        total_filled = sum(e['qty'] for e in self.executed)
        avg_price = total_cost / total_filled if total_filled else 0

        return {
            "status": "완료",
            "total_filled": total_filled,
            "avg_price": avg_price,
            "num_executed": len(self.executed),
            "fill_rate": len(self.executed) / self.num_slices * 100
        }

랜덤화로 실행 품질 높이기

기본 TWAP은 일정 간격으로 주문하므로 다른 트레이더가 패턴을 감지할 수 있습니다. 실전에서는 시간 랜덤화수량 랜덤화를 추가합니다.

import random

class RandomizedTWAP(TWAPExecutor):
    """랜덤화된 TWAP: 패턴 감지 방지"""

    def __init__(self, *args, time_jitter=0.3, qty_jitter=0.2, **kwargs):
        super().__init__(*args, **kwargs)
        self.time_jitter = time_jitter  # 시간 변동폭 (30%)
        self.qty_jitter = qty_jitter    # 수량 변동폭 (20%)

    def _randomize_interval(self):
        """간격에 랜덤 지터 추가"""
        jitter = self.interval * self.time_jitter
        return self.interval + random.uniform(-jitter, jitter)

    def _randomize_qty(self, remaining, slices_left):
        """수량 랜덤화 (총합은 유지)"""
        base = remaining / slices_left
        jitter = base * self.qty_jitter
        qty = base + random.uniform(-jitter, jitter)
        return max(qty, base * 0.5)  # 최소 50%는 보장

    def execute(self):
        """랜덤화된 TWAP 실행"""
        remaining = self.total_qty
        for i in range(self.num_slices):
            slices_left = self.num_slices - i
            qty = self._randomize_qty(remaining, slices_left)
            qty = min(qty, remaining)  # 남은 수량 초과 방지

            try:
                order = self.exchange.create_market_order(
                    self.symbol, self.side, qty
                )
                filled = order.get('average', 0)
                remaining -= qty
                self.executed.append({
                    'slice': i + 1, 'qty': qty,
                    'price': filled, 'timestamp': time.time()
                })
            except Exception as e:
                print(f"  슬라이스 {i+1} 실패: {e}")

            if i < self.num_slices - 1:
                time.sleep(self._randomize_interval())

        return self.get_summary()

TWAP 실행 시 주의사항 5가지

TWAP 자동매매를 실전에 적용할 때 반드시 고려해야 할 사항들입니다.

1. 최소 주문 수량 확인

슬라이스를 너무 잘게 나누면 거래소의 최소 주문 수량에 미달할 수 있습니다. ccxt의 markets 정보에서 최소 수량을 확인하세요.

market = exchange.market(symbol)
min_amount = market['limits']['amount']['min']
# 슬라이스 수량이 min_amount 이상인지 검증
assert slice_qty >= min_amount, "슬라이스가 최소 주문 수량 미달"

2. 수수료 누적 관리

분할 주문 횟수가 많아지면 수수료가 누적됩니다. 일반적으로 20~50회 분할이 시장 충격 감소와 수수료 사이의 적정 균형점입니다.

3. 급변 시장 대응

TWAP 실행 도중 가격이 급등하거나 급락하면 불리한 가격에 계속 매수할 수 있습니다. 가격 임계값을 설정해서 실행을 일시 중지하는 로직이 필요합니다.

# 가격 급변 감지: 시작 가격 대비 2% 이상 변동 시 중지
start_price = exchange.fetch_ticker(symbol)['last']
THRESHOLD = 0.02

for i in range(num_slices):
    current = exchange.fetch_ticker(symbol)['last']
    change = abs(current - start_price) / start_price
    if change > THRESHOLD:
        print(f"가격 급변 감지 ({change:.1%}), 실행 일시 중지")
        time.sleep(60)  # 1분 대기 후 재평가
        continue
    # ... 주문 실행

4. 네트워크 장애 대비

장시간 실행되는 TWAP은 네트워크 장애를 만날 확률이 높습니다. 재시도 로직과 실행 상태 저장을 반드시 구현하세요.

5. 지정가 주문 활용

시장가 대신 최우선 호가 지정가 주문을 사용하면 슬리피지를 더 줄일 수 있습니다. 다만 미체결 처리 로직이 추가로 필요합니다.

TWAP 성과 벤치마크 측정

TWAP 실행 품질은 도착가격(Arrival Price) 대비 실행 평균가로 측정합니다. 자동매매 로그 분석과 결합하면 체계적인 성과 관리가 가능합니다.

def calculate_implementation_shortfall(arrival_price, avg_exec_price, side):
    """실행 비용(Implementation Shortfall) 계산"""
    if side == 'buy':
        # 매수: 실행가가 도착가보다 높으면 비용 발생
        shortfall = (avg_exec_price - arrival_price) / arrival_price
    else:
        # 매도: 실행가가 도착가보다 낮으면 비용 발생
        shortfall = (arrival_price - avg_exec_price) / arrival_price

    return {
        'arrival_price': arrival_price,
        'avg_exec_price': avg_exec_price,
        'shortfall_bps': shortfall * 10000,  # bps 단위
        'grade': 'Good' if shortfall < 0.001 else 'Fair' if shortfall < 0.005 else 'Poor'
    }

실전 적용: 1 BTC를 1시간에 걸쳐 매수

아래는 실제 바이낸스에서 TWAP으로 1 BTC를 매수하는 예시입니다.

import ccxt

# 거래소 설정
exchange = ccxt.binance({
    'apiKey': 'YOUR_API_KEY',
    'secret': 'YOUR_SECRET',
    'options': {'defaultType': 'spot'}
})

# TWAP 실행: 1 BTC, 60분, 30회 분할 (2분 간격)
twap = RandomizedTWAP(
    exchange=exchange,
    symbol='BTC/USDT',
    side='buy',
    total_qty=1.0,
    duration_min=60,
    num_slices=30,
    time_jitter=0.25,
    qty_jitter=0.15
)

result = twap.execute()
print(f"평균 체결가: {result['avg_price']:.2f} USDT")
print(f"체결률: {result['fill_rate']:.0f}%")

마치며

TWAP은 구현이 단순하면서도 실전에서 검증된 주문 실행 전략입니다. 특히 암호화폐 자동매매에서 대량 주문의 슬리피지를 줄이는 데 효과적입니다. 랜덤화, 급변 시장 대응, 성과 측정까지 구현하면 기관 수준의 실행 품질을 달성할 수 있습니다.

자동매매 봇의 슬리피지 최적화와 함께 적용하면 전체 트레이딩 시스템의 수익성을 크게 개선할 수 있습니다.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux