파이썬 TWAP 자동매매 전략

TWAP 자동매매란?

TWAP(Time-Weighted Average Price)은 대량 주문을 일정 시간 간격으로 균등 분할하여 체결하는 알고리즘 매매 전략입니다. 기관 투자자와 퀀트 트레이더가 시장 충격(Market Impact)을 최소화하면서 대규모 포지션을 진입·청산할 때 핵심적으로 사용합니다.

VWAP이 거래량 가중 평균을 추종한다면, TWAP은 시간 축을 기준으로 균등 분배하므로 거래량 데이터가 불안정한 암호화폐 시장이나 유동성이 낮은 종목에서 특히 유리합니다.

TWAP vs VWAP 차이점

구분 TWAP VWAP
분배 기준 시간 균등 분할 거래량 가중 분할
필요 데이터 시간 정보만 실시간 거래량 필요
적합 시장 저유동성, 암호화폐 고유동성 주식
구현 난이도 낮음 중간
시장 충격 균등 분산 거래량 집중 시간대 분산

TWAP 알고리즘 핵심 로직

TWAP 전략의 핵심은 단순합니다. 총 주문 수량을 N개의 슬라이스로 나누고, 일정 간격마다 한 슬라이스씩 시장가 또는 지정가로 체결시킵니다.

총_주문량 = 10 BTC
실행_시간 = 60분
슬라이스_수 = 12 (5분 간격)
슬라이스_수량 = 10 / 12 ≈ 0.833 BTC

타이머:
  매 5분마다 → 0.833 BTC 시장가 매수 실행
  미체결 시 → 다음 슬라이스에 잔량 합산

파이썬 TWAP 봇 구현

아래는 ccxt 라이브러리를 활용한 암호화폐 TWAP 자동매매 봇의 핵심 코드입니다. 바이낸스, 업비트 등 주요 거래소에서 바로 사용할 수 있습니다.

import ccxt
import time
import math
from datetime import datetime

class TWAPBot:
    def __init__(self, exchange_id, api_key, secret):
        exchange_class = getattr(ccxt, exchange_id)
        self.exchange = exchange_class({
            'apiKey': api_key,
            'secret': secret,
            'options': {'defaultType': 'spot'}
        })

    def execute_twap(self, symbol, side, total_qty,
                     duration_min, num_slices):
        """
        TWAP 주문 실행
        :param symbol: 거래쌍 (예: 'BTC/USDT')
        :param side: 'buy' 또는 'sell'
        :param total_qty: 총 주문 수량
        :param duration_min: 실행 시간(분)
        :param num_slices: 분할 횟수
        """
        slice_qty = total_qty / num_slices
        interval_sec = (duration_min * 60) / num_slices
        executed = 0
        results = []

        for i in range(num_slices):
            remaining = total_qty - executed
            qty = min(slice_qty, remaining)

            if qty <= 0:
                break

            try:
                order = self.exchange.create_market_order(
                    symbol, side, qty
                )
                filled = order.get('filled', qty)
                avg_price = order.get('average', 0)
                executed += filled

                results.append({
                    'slice': i + 1,
                    'time': datetime.now().isoformat(),
                    'filled': filled,
                    'price': avg_price
                })
                print(f"[슬라이스 {i+1}/{num_slices}] "
                      f"{filled} {symbol} @ {avg_price}")

            except Exception as e:
                print(f"[오류] 슬라이스 {i+1}: {e}")
                # 미체결 잔량은 다음 슬라이스에 합산
                slice_qty += qty / (num_slices - i - 1) 
                    if i < num_slices - 1 else 0

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

        return self._calculate_summary(results, side)

    def _calculate_summary(self, results, side):
        if not results:
            return None
        total_filled = sum(r['filled'] for r in results)
        twap_price = (
            sum(r['filled'] * r['price'] for r in results)
            / total_filled
        )
        return {
            'side': side,
            'total_filled': total_filled,
            'twap_price': twap_price,
            'num_executions': len(results),
            'slices': results
        }

실전 TWAP 최적화 기법

기본 TWAP에 아래 기법을 추가하면 체결 품질을 크게 높일 수 있습니다.

1. 랜덤 지터(Jitter) 추가

정확히 같은 간격으로 주문하면 알고리즘 감지(algo detection)에 노출됩니다. 실행 간격에 ±10~20% 랜덤 편차를 넣으면 패턴을 숨길 수 있습니다.

import random

# 기본 간격 300초에 ±20% 지터
base_interval = 300
jitter = random.uniform(-0.2, 0.2)
actual_interval = base_interval * (1 + jitter)

2. 지정가 주문 + 폴백

시장가 대신 현재가 기준 약간의 슬리피지 허용 범위 내에서 지정가 주문을 먼저 시도하고, 미체결 시 시장가로 전환하면 체결 비용을 줄일 수 있습니다.

def smart_order(self, symbol, side, qty,
                slippage_pct=0.05):
    ticker = self.exchange.fetch_ticker(symbol)
    mid_price = (ticker['bid'] + ticker['ask']) / 2

    if side == 'buy':
        limit_price = mid_price * (1 + slippage_pct / 100)
    else:
        limit_price = mid_price * (1 - slippage_pct / 100)

    order = self.exchange.create_limit_order(
        symbol, side, qty, limit_price
    )
    time.sleep(5)  # 5초 대기

    status = self.exchange.fetch_order(order['id'], symbol)
    if status['remaining'] > 0:
        self.exchange.cancel_order(order['id'], symbol)
        # 잔량 시장가 체결
        self.exchange.create_market_order(
            symbol, side, status['remaining']
        )
    return status

3. 최소 주문 수량 처리

거래소마다 최소 주문 수량이 있습니다. 슬라이스 수량이 최소 수량 미만이면 슬라이스를 합치거나 총 분할 수를 줄여야 합니다.

market = self.exchange.market(symbol)
min_qty = market['limits']['amount']['min']

if slice_qty < min_qty:
    num_slices = math.floor(total_qty / min_qty)
    slice_qty = total_qty / num_slices

TWAP 성과 측정 지표

TWAP 봇의 성과는 단순 수익률이 아니라 실행 품질(Execution Quality)로 평가합니다.

지표 계산 방법 목표
TWAP Slippage (실제 평균가 - 이론 TWAP) / 이론 TWAP 0에 가까울수록 좋음
Implementation Shortfall 결정 시점 가격 대비 실제 체결 비용 차이 최소화
Fill Rate 체결 수량 / 목표 수량 × 100 100%
Market Impact 주문 전후 가격 변동 폭 최소화

실전 운영 체크리스트

TWAP 봇을 프로덕션에 배포하기 전 반드시 확인해야 할 항목들입니다.

  • API Rate Limit 확인 — 거래소별 초당 요청 제한을 넘지 않도록 슬라이스 간격 조정
  • 네트워크 장애 대비 — 연결 끊김 시 미완료 슬라이스 복구 로직 구현
  • 가격 급변 서킷브레이커 — 실행 중 가격이 일정 비율 이상 급변하면 자동 중단
  • 슬리피지 모니터링 — 슬라이스별 체결가와 이론가 편차를 실시간 추적
  • 잔고 사전 확인 — 전체 주문을 체결할 충분한 잔고가 있는지 시작 전 검증
  • 로그 기록 — 모든 슬라이스의 체결 시간, 가격, 수량을 파일/DB에 저장

TWAP 전략이 효과적인 상황

TWAP은 만능이 아닙니다. 다음 상황에서 가장 효과적입니다.

  • 대량 포지션 진입/청산 — 일일 거래량의 10% 이상 매매 시
  • 유동성 낮은 시장 — 알트코인, 소형주 등 호가 스프레드가 넓은 종목
  • 시장 방향성 불확실 — 트렌드 예측 없이 평균 가격에 수렴하고 싶을 때
  • 규제 준수 필요 — 기관 투자자가 Best Execution 의무를 충족해야 할 때

반면, 강한 트렌드가 진행 중이거나 시간 압박이 있는 매매에서는 TWAP보다 적극적인 전략이 적합합니다.

관련 글

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