현선물 베이시스 차익거래 전략

베이시스 차익거래란?

베이시스(Basis)는 현물 가격과 선물 가격의 차이를 말합니다. 이론적으로 선물 만기일에 베이시스는 0으로 수렴하므로, 베이시스가 비정상적으로 확대되었을 때 현물 매수 + 선물 매도(Cash and Carry)를 동시에 실행하면 시장 방향과 무관하게 수익을 확보할 수 있습니다.

이 전략은 전통 금융에서 수십 년간 활용된 고전적 차익거래 기법이며, 암호화폐 시장에서는 무기한 선물(Perpetual Futures)의 펀딩비분기 선물의 프리미엄 두 가지 형태로 기회가 발생합니다. 이 글에서는 파이썬으로 베이시스를 모니터링하고 자동으로 차익거래를 실행하는 시스템을 구현합니다.

콘탱고와 백워데이션

베이시스의 방향에 따라 시장 상태를 구분합니다:

상태 조건 의미 전략
콘탱고(Contango) 선물 > 현물 시장 낙관, 캐리 비용 반영 현물 매수 + 선물 매도
백워데이션(Backwardation) 선물 < 현물 시장 비관, 즉각 수요 우세 현물 매도 + 선물 매수 (공매도 가능 시)

암호화폐 시장은 상승장에서 콘탱고가 극단적으로 확대되는 경향이 있어, Cash and Carry 전략의 수익률이 연 20~50%에 달하기도 합니다.

베이시스 계산과 모니터링

import asyncio
import numpy as np
from datetime import datetime, timedelta
from dataclasses import dataclass
from typing import Optional

@dataclass
class BasisData:
    timestamp: datetime
    spot_price: float
    futures_price: float
    basis: float           # 절대 베이시스 (선물 - 현물)
    basis_pct: float       # 비율 베이시스 (%)
    annualized_pct: float  # 연환산 수익률 (%)
    days_to_expiry: int    # 만기까지 잔여일

class BasisMonitor:
    def __init__(self, exchange_client):
        self.exchange = exchange_client
        self.history = []

    async def get_basis(self, spot_symbol, futures_symbol, expiry_date):
        """현재 베이시스 계산"""
        spot = await self.exchange.get_ticker(spot_symbol)
        futures = await self.exchange.get_ticker(futures_symbol)

        spot_price = spot['last']
        futures_price = futures['last']

        basis = futures_price - spot_price
        basis_pct = (basis / spot_price) * 100

        days_to_expiry = (expiry_date - datetime.now()).days
        days_to_expiry = max(days_to_expiry, 1)

        # 연환산 수익률
        annualized = (basis_pct / days_to_expiry) * 365

        data = BasisData(
            timestamp=datetime.now(),
            spot_price=spot_price,
            futures_price=futures_price,
            basis=round(basis, 2),
            basis_pct=round(basis_pct, 4),
            annualized_pct=round(annualized, 2),
            days_to_expiry=days_to_expiry
        )

        self.history.append(data)
        return data

    def get_basis_stats(self, lookback=100):
        """베이시스 통계 (진입/청산 임계값 설정용)"""
        recent = self.history[-lookback:]
        values = [d.basis_pct for d in recent]

        return {
            'mean': round(np.mean(values), 4),
            'std': round(np.std(values), 4),
            'max': round(np.max(values), 4),
            'min': round(np.min(values), 4),
            'current': values[-1] if values else 0,
            'zscore': round(
                (values[-1] - np.mean(values)) / max(np.std(values), 0.0001), 2
            ) if values else 0
        }

연환산 수익률이 핵심 지표입니다. 만기까지 30일 남은 선물의 베이시스가 2%라면, 연환산 약 24%의 무위험 수익률을 의미합니다.

Cash and Carry 자동매매 엔진

베이시스가 임계값을 초과하면 자동으로 현물 매수 + 선물 매도 포지션을 구축합니다.

@dataclass
class CarryPosition:
    spot_qty: float = 0
    futures_qty: float = 0
    entry_basis_pct: float = 0
    entry_spot_price: float = 0
    entry_futures_price: float = 0
    entry_time: Optional[datetime] = None
    status: str = "closed"

class CashCarryEngine:
    def __init__(self, exchange_client, config):
        self.exchange = exchange_client
        self.config = config
        self.monitor = BasisMonitor(exchange_client)
        self.position = CarryPosition()

    async def run(self, spot_symbol, futures_symbol, expiry_date):
        """메인 트레이딩 루프"""
        while True:
            try:
                basis = await self.monitor.get_basis(
                    spot_symbol, futures_symbol, expiry_date
                )

                print(f"[BASIS] {basis.basis_pct:.4f}% "
                      f"(연환산 {basis.annualized_pct:.2f}%) "
                      f"| 만기 D-{basis.days_to_expiry}")

                await self.evaluate(basis, spot_symbol, futures_symbol)
                await asyncio.sleep(self.config['check_interval_sec'])

            except Exception as e:
                print(f"[ERROR] {e}")
                await asyncio.sleep(60)

    async def evaluate(self, basis, spot_symbol, futures_symbol):
        """베이시스 기반 진입/청산 판단"""
        entry_threshold = self.config['entry_annualized_pct']
        exit_threshold = self.config['exit_basis_pct']
        max_position = self.config['max_position_usd']

        # 포지션 없음 → 진입 검토
        if self.position.status == "closed":
            if basis.annualized_pct > entry_threshold and basis.basis_pct > 0:
                await self.open_carry(
                    spot_symbol, futures_symbol, basis, max_position
                )

        # 포지션 있음 → 청산 검토
        elif self.position.status == "open":
            # 베이시스 축소 시 청산 (수렴 완료)
            if basis.basis_pct < exit_threshold:
                await self.close_carry(spot_symbol, futures_symbol, basis)

            # 만기 임박 시 청산 (D-1)
            elif basis.days_to_expiry <= 1:
                await self.close_carry(spot_symbol, futures_symbol, basis)

    async def open_carry(self, spot_sym, futures_sym, basis, max_usd):
        """Cash and Carry 포지션 진입"""
        qty = max_usd / basis.spot_price

        # 현물 매수 + 선물 매도 동시 실행
        spot_order, futures_order = await asyncio.gather(
            self.exchange.market_order(spot_sym, 'buy', qty),
            self.exchange.market_order(futures_sym, 'sell', qty)
        )

        self.position = CarryPosition(
            spot_qty=spot_order['filled'],
            futures_qty=futures_order['filled'],
            entry_basis_pct=basis.basis_pct,
            entry_spot_price=spot_order['avg_price'],
            entry_futures_price=futures_order['avg_price'],
            entry_time=datetime.now(),
            status="open"
        )

        print(f"[OPEN] 현물 매수 {self.position.spot_qty:.4f} "
              f"@ {self.position.entry_spot_price:.2f}")
        print(f"[OPEN] 선물 매도 {self.position.futures_qty:.4f} "
              f"@ {self.position.entry_futures_price:.2f}")
        print(f"[OPEN] 진입 베이시스: {basis.basis_pct:.4f}% "
              f"(연환산 {basis.annualized_pct:.2f}%)")

    async def close_carry(self, spot_sym, futures_sym, basis):
        """포지션 청산"""
        await asyncio.gather(
            self.exchange.market_order(spot_sym, 'sell', self.position.spot_qty),
            self.exchange.market_order(futures_sym, 'buy', self.position.futures_qty)
        )

        pnl_pct = self.position.entry_basis_pct - basis.basis_pct
        print(f"[CLOSE] 수익: {pnl_pct:.4f}% | "
              f"진입 베이시스: {self.position.entry_basis_pct:.4f}% → "
              f"청산 베이시스: {basis.basis_pct:.4f}%")

        self.position = CarryPosition()

현물과 선물을 asyncio.gather로 동시 실행하여 체결 시차(Leg Risk)를 최소화합니다. 이는 주문 실행 전략에서 다룬 것처럼 체결 품질이 수익에 직접적인 영향을 미칩니다.

무기한 선물 펀딩비 전략

분기 선물 외에도 무기한 선물(Perpetual)의 펀딩비를 활용한 캐리 트레이드가 있습니다. 펀딩비가 양수이면 롱이 숏에게 비용을 지불하므로, 현물 매수 + 무기한 선물 매도로 펀딩비를 수취할 수 있습니다.

class FundingRateCarry:
    def __init__(self, exchange_client):
        self.exchange = exchange_client

    async def get_funding_analysis(self, symbol, lookback_days=30):
        """펀딩비 분석"""
        rates = await self.exchange.get_funding_history(
            symbol, limit=lookback_days * 3  # 8시간마다 3회/일
        )

        values = [r['rate'] for r in rates]

        # 연환산 수익률 계산
        avg_rate = np.mean(values)
        annualized = avg_rate * 3 * 365 * 100  # 일 3회 × 365일

        return {
            'avg_funding_rate': round(avg_rate * 100, 4),
            'annualized_pct': round(annualized, 2),
            'positive_ratio': round(
                sum(1 for v in values if v > 0) / len(values) * 100, 1
            ),
            'max_rate': round(max(values) * 100, 4),
            'min_rate': round(min(values) * 100, 4),
            'current_rate': round(values[-1] * 100, 4) if values else 0
        }

    def should_enter(self, analysis, min_annualized=15, min_positive_ratio=70):
        """진입 조건 판단"""
        return (
            analysis['annualized_pct'] > min_annualized and
            analysis['positive_ratio'] > min_positive_ratio and
            analysis['current_rate'] > 0
        )

평균 펀딩비가 연환산 15% 이상이고, 양수 비율이 70% 이상이면 안정적인 캐리 트레이드 기회입니다.

리스크 관리: 레그 리스크와 마진

베이시스 차익거래의 주요 리스크를 관리하는 방법입니다.

class CarryRiskManager:
    def __init__(self, config):
        self.config = config

    def check_margin_safety(self, futures_position, account_balance, 
                             current_price):
        """선물 마진 안전성 검사"""
        notional = abs(futures_position) * current_price
        margin_ratio = account_balance / notional

        # 마진 비율 30% 이하 시 경고
        if margin_ratio < 0.3:
            return {
                'safe': False,
                'action': 'reduce_position',
                'margin_ratio': round(margin_ratio, 4),
                'message': '마진 비율 위험 — 포지션 축소 필요'
            }

        # 마진 비율 50% 이하 시 주의
        if margin_ratio < 0.5:
            return {
                'safe': True,
                'action': 'monitor',
                'margin_ratio': round(margin_ratio, 4),
                'message': '마진 비율 주의 — 모니터링 강화'
            }

        return {
            'safe': True,
            'action': 'none',
            'margin_ratio': round(margin_ratio, 4)
        }

    def calculate_max_position(self, account_balance, spot_price, 
                                 leverage=1, margin_buffer=0.5):
        """최대 포지션 크기 계산"""
        # 현물 50% + 선물 마진 50% 배분
        spot_allocation = account_balance * 0.5
        futures_allocation = account_balance * 0.5

        # 선물은 레버리지 적용하되, 마진 버퍼 확보
        max_futures_notional = futures_allocation * leverage * (1 - margin_buffer)

        max_qty = min(
            spot_allocation / spot_price,
            max_futures_notional / spot_price
        )

        return {
            'max_qty': round(max_qty, 6),
            'max_usd': round(max_qty * spot_price, 2),
            'spot_allocation': round(spot_allocation, 2),
            'futures_margin': round(futures_allocation, 2)
        }

핵심은 선물 마진 관리입니다. 가격이 급등하면 선물 숏 포지션의 미실현 손실이 커져 청산 위험이 생깁니다. 현물 수익으로 상쇄되지만, 마진 부족으로 강제 청산되면 한쪽 포지션만 남게 됩니다.

멀티 거래소 베이시스 스캐너

여러 거래소의 베이시스를 동시에 스캔하여 최적의 기회를 찾습니다.

class MultiExchangeScanner:
    def __init__(self, exchanges):
        self.exchanges = exchanges  # {'binance': client, 'okx': client, ...}

    async def scan_all(self, pairs):
        """전 거래소 베이시스 스캔"""
        results = []

        for exchange_name, client in self.exchanges.items():
            for pair in pairs:
                try:
                    monitor = BasisMonitor(client)
                    basis = await monitor.get_basis(
                        pair['spot'], pair['futures'], pair['expiry']
                    )
                    results.append({
                        'exchange': exchange_name,
                        'pair': pair['name'],
                        'basis_pct': basis.basis_pct,
                        'annualized': basis.annualized_pct,
                        'days_to_expiry': basis.days_to_expiry
                    })
                except Exception as e:
                    print(f"[SKIP] {exchange_name}/{pair['name']}: {e}")

        # 연환산 수익률 기준 정렬
        results.sort(key=lambda x: x['annualized'], reverse=True)

        print("=== 베이시스 스캔 결과 ===")
        for r in results[:10]:
            print(f"  {r['exchange']:10s} {r['pair']:12s} "
                  f"basis={r['basis_pct']:+.4f}% "
                  f"annual={r['annualized']:+.2f}% "
                  f"D-{r['days_to_expiry']}")

        return results

수익률 시뮬레이션

시나리오 베이시스 만기 연환산 비고
보수적 0.5% 30일 ~6% 약세장, 낮은 변동성
일반적 1.5% 30일 ~18% 횡보장, 보통 수요
적극적 3%+ 30일 ~36%+ 강세장, 레버리지 수요 폭증
펀딩비 0.03%/8h 무기한 ~33% 무기한 선물 캐리

실전 체크리스트

  • 거래 비용: 현물 + 선물 양방향 수수료를 베이시스에서 차감하여 순수익 계산
  • 마진 관리: 선물 계좌에 충분한 마진 유지 (최소 50% 버퍼)
  • 동시 체결: 현물/선물 주문 시차를 최소화 → 레그 리스크 제거
  • 만기 롤오버: 분기 선물 만기 전 다음 분기로 포지션 이전
  • 세금: 현물 매도익과 선물 수익의 세무 처리 확인

마무리

현선물 베이시스 차익거래는 시장 방향에 중립적인 저위험 수익을 제공하는 퀀트 전략입니다. 특히 암호화폐 시장의 구조적 특성 — 높은 레버리지 수요, 빈번한 콘탱고 — 덕분에 전통 금융보다 훨씬 높은 수익률 기회가 존재합니다.

핵심은 베이시스 모니터링 자동화, 동시 주문 실행, 그리고 리스크 관리 시스템과의 통합입니다. 샤프 비율이 높은 안정적 수익원으로 포트폴리오에 편입하면 전체 전략의 리스크 대비 수익을 크게 개선할 수 있습니다.

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