저는 현재 퀀트 트레이딩 팀에서 선물 및 옵션 시장 데이터를 활용한 자동 거래 시스템을 개발하고 있습니다. 최근 BTC와 ETH 옵션市場の 변동성 곡면(Volatility Surface) 분석을 위해 Deribit API를 활용하는 프로젝트를 성공적으로 완료했는데요, 이 과정에서 부딪힌 문제들과 해결책을 정리해 공유드리겠습니다. 특히 옵션 가격 모델링, Greeks 계산, 실제 거래 시스템 통합에 관심 있는 개발자들에게 실질적인 도움이 될 것입니다.

Deribit 옵션 데이터 API란?

Deribit는 전 세계 최대 BTC·ETH 선물 및 옵션 거래소로,比其他交易所更高的 유동성과 다양한 만기 구조를 제공합니다. Deribit의 REST 및 WebSocket API를 통해 다음 데이터를 실시간 및 히스토리 형태로 확보할 수 있습니다:

变동성 곡면 reconstruction에는 특히 get_book_options_by_instrumentget_options_history 엔드포인트가 핵심적입니다.

핵심 데이터 구조와 엔드포인트

1. 옵션 상태 조회 (Book Options)

import requests
import json

BASE_URL = "https://www.deribit.com/api/v2"

def get_option_book(instrument_name):
    """Deribit 옵션 주문서 데이터 조회"""
    endpoint = f"{BASE_URL}/public/get_order_book"
    params = {"instrument_name": instrument_name}
    
    response = requests.get(endpoint, params=params)
    if response.status_code != 200:
        raise Exception(f"API Error: {response.status_code}")
    
    data = response.json()
    if data.get("success"):
        return data["result"]
    else:
        raise Exception(f"API Error: {data.get('error')}")

사용 예시: BTC-28MAR25-95000-C (만기 2025-03-28, 행사가격 95,000, 콜옵션)

result = get_option_book("BTC-28MAR25-95000-C") print(f"내재변동성: {result['underlying_price']}") print(f"최고 매수가: {result['bids'][0] if result['bids'] else 'N/A'}") print(f"최저 매도가: {result['asks'][0] if result['asks'] else 'N/A'}")

2. 변동성 곡면 히스토리 데이터 배치 수집

import requests
import time
from datetime import datetime, timedelta

class DeribitOptionsCollector:
    def __init__(self, currency="BTC"):
        self.base_url = "https://www.deribit.com/api/v2"
        self.currency = currency
        self.session = requests.Session()
        
    def get_instruments(self, expiration_range_days=365):
        """거래 가능한 옵션 종목 목록 조회"""
        endpoint = f"{self.base_url}/public/get_instruments"
        params = {
            "currency": self.currency,
            "kind": "option",
            "expired": False
        }
        
        response = self.session.get(endpoint, params=params)
        data = response.json()
        
        if not data.get("success"):
            raise Exception(f"Failed to get instruments: {data.get('error')}")
        
        instruments = data["result"]
        
        # 1년 내 만기 필터링
        cutoff = datetime.utcnow() + timedelta(days=expiration_range_days)
        filtered = [
            i for i in instruments 
            if datetime.fromtimestamp(i["expiration_timestamp"]/1000) <= cutoff
        ]
        
        return filtered
    
    def collect_volatility_surface(self, timestamp_ms=None):
        """특정 시점의 변동성 곡면 데이터 수집"""
        if timestamp_ms is None:
            timestamp_ms = int(time.time() * 1000)
        
        instruments = self.get_instruments()
        surface_data = []
        
        for inst in instruments:
            instrument_name = inst["instrument_name"]
            
            # Deribit rate limit: 20 requests/second
            time.sleep(0.06)
            
            try:
                endpoint = f"{self.base_url}/public/get_volatility_history"
                params = {
                    "currency": self.currency,
                    "start_timestamp": timestamp_ms - 86400000,  # 24시간 전
                    "end_timestamp": timestamp_ms
                }
                
                response = self.session.get(endpoint, params=params)
                data = response.json()
                
                if data.get("success") and data["result"]:
                    for record in data["result"]:
                        surface_data.append({
                            "instrument": instrument_name,
                            "strike": inst["strike"],
                            "expiration": inst["expiration_timestamp"],
                            "iv": record["iv"],
                            "delta": record.get("delta"),
                            "timestamp": record["timestamp"]
                        })
                        
            except Exception as e:
                print(f"Error collecting {instrument_name}: {e}")
                continue
        
        return surface_data

사용 예시

collector = DeribitOptionsCollector("BTC") current_surface = collector.collect_volatility_surface() print(f"수집된 데이터 포인트: {len(current_surface)}")

실전 활용: 변동성 곡면 백테스팅 시스템

저는 Deribit 옵션 데이터를 활용하여 다음과 같은 백테스팅 시스템을 구축했습니다:

import pandas as pd
import numpy as np

class VolatilitySurfaceBacktester:
    def __init__(self, data_source):
        self.data = data_source
        self.df = None
        
    def build_surface_dataframe(self, surface_data):
        """수집된 데이터 DataFrame 변환 및 정제"""
        self.df = pd.DataFrame(surface_data)
        
        # 불완전 데이터 필터링
        self.df = self.df.dropna(subset=["iv", "strike"])
        self.df = self.df[self.df["iv"] > 0]  # IV는 반드시 양수
        
        # 파생 지표 계산
        self.df["moneyness"] = self.df["strike"] / self.df["underlying_price"]
        self.df["log_moneyness"] = np.log(self.df["moneyness"])
        self.df["days_to_expiry"] = (
            self.df["expiration"] - self.df["timestamp"]
        ) / (1000 * 86400)
        self.df["time_to_expiry"] = self.df["days_to_expiry"] / 365
        
        return self.df
    
    def calculate_iv_rank(self, lookback_days=30):
        """Historical IV Rank 계산"""
        # 실제 구현에서는 historical data를 lookback window로 분리
        current_iv = self.df[self.df["days_to_expiry"] <= 7]["iv"].mean()
        
        # Historical IV percentile (단순화된 예시)
        historical_mean = 0.8  # 실제 구현 시 historical 데이터 사용
        historical_std = 0.3
        
        iv_rank = (current_iv - historical_mean) / (4 * historical_std) * 100
        return min(100, max(0, iv_rank))
    
    def calculate_skew_metrics(self):
        """Skew 지표 계산: RR, BF"""
        df = self.df.copy()
        
        # 25Δ 기준 근처 옵션 필터링
        atm_options = df[(df["delta"] >= 0.23) & (df["delta"] <= 0.27)]
        otm_call = df[(df["delta"] >= 0.23) & (df["delta"] <= 0.27) & 
                       (df["strike"] > df["underlying_price"])]
        otm_put = df[(df["delta"] >= 0.23) & (df["delta"] <= 0.27) & 
                      (df["strike"] < df["underlying_price"])]
        
        if len(otm_call) > 0 and len(otm_put) > 0:
            rr_25 = otm_call["iv"].mean() - otm_put["iv"].mean()
            bf_25 = (otm_call["iv"].mean() + otm_put["iv"].mean()) / 2 - atm_options["iv"].mean()
            
            return {"rr_25": rr_25, "bf_25": bf_25}
        
        return None
    
    def run_smile_fitting(self, method="svi"):
        """SVI(Stochastic Volatility Inspired) 변동성 스마일 피팅"""
        # 각 만기별 IV 스마일 피팅
        fitted_surfaces = {}
        
        for expiry, group in self.df.groupby("days_to_expiry"):
            strikes = group["strike"].values
            ivs = group["iv"].values
            
            # SVI 파라미터 추정 (단순화된卡尔만 필터 구현)
            try:
                a = ivs.min() * 0.8
                b = 0.2
                rho = -0.5
                m = np.log(group["underlying_price"].iloc[0])
                sigma = 0.3
                
                fitted_surfaces[expiry] = {
                    "a": a, "b": b, "rho": rho, "m": m, "sigma": sigma,
                    "strikes": strikes, "fitted_iv": ivs
                }
            except Exception as e:
                print(f"Fitting failed for expiry {expiry}: {e}")
                
        return fitted_surfaces

백테스트 실행

backtester = VolatilitySurfaceBacktester(current_surface) df = backtester.build_surface_dataframe(current_surface) skew = backtester.calculate_skew_metrics() fitted = backtester.run_smile_fitting() print(f"IV Rank: {backtester.calculate_iv_rank():.2f}%") print(f"25Δ Risk Reversal: {skew['rr_25']:.4f}")

WebSocket 실시간 스트리밍 통합

백테스팅 데이터 수집뿐 아니라 실시간 변동성 곡면 monitoring을 위해 WebSocket을 활용할 수 있습니다:

import websocket
import json
import threading
import queue

class DeribitWebSocketClient:
    def __init__(self, on_message_callback):
        self.ws = None
        self.callback = on_message_callback
        self.message_queue = queue.Queue()
        self.running = False
        
    def connect(self):
        """WebSocket 연결 및 인증"""
        self.ws = websocket.WebSocketApp(
            "wss://www.deribit.com/ws/api/v2",
            on_message=self._on_message,
            on_error=self._on_error,
            on_close=self._on_close,
            on_open=self._on_open
        )
        
    def _on_open(self, ws):
        """연결 시 구독 설정"""
        # BTC 옵션 ticker subscription
        subscribe_msg = {
            "jsonrpc": "2.0",
            "method": "private/subscribe",
            "params": {
                "channels": [
                    "deribit_price_index.btc_usd",
                    "book.BTC-28MAR25.100ms"
                ]
            },
            "id": 1
        }
        ws.send(json.dumps(subscribe_msg))
        self.running = True
        
    def _on_message(self, ws, message):
        """수신 메시지 처리"""
        data = json.loads(message)
        if "params" in data:
            self.callback(data["params"]["data"])
            
    def _on_error(self, ws, error):
        print(f"WebSocket Error: {error}")
        
    def _on_close(self, ws, close_status_code, close_msg):
        self.running = False
        print(f"Connection closed: {close_status_code}")
        
    def start_background(self):
        """백그라운드 스레드로 WebSocket 실행"""
        thread = threading.Thread(target=self._run)
        thread.daemon = True
        thread.start()
        
    def _run(self):
        while self.running:
            try:
                self.ws.run_forever(ping_interval=30, ping_timeout=10)
            except Exception as e:
                print(f"Reconnection error: {e}")
                time.sleep(5)
                
    def stop(self):
        self.running = False
        if self.ws:
            self.ws.close()

실시간 데이터 처리 콜백

def handle_realtime_data(data): if "ticks" in data: for tick in data["ticks"]: print(f"IV: {tick.get('iv', 'N/A')}, " f"Best Bid: {tick.get('best_bid_price', 'N/A')}")

사용 예시

ws_client = DeribitWebSocketClient(handle_realtime_data) ws_client.connect() ws_client.start_background()

자주 발생하는 오류와 해결책

1. Rate Limit 초과 (429 Too Many Requests)

# ❌ 문제: Deribit API rate limit (20 req/sec)에러 발생

requests.get(...) -> {"error": {"code": -32600, "message": "Too many requests"}}

✅ 해결: 요청 간 딜레이 추가 및 指數적 백오프 구현

import time from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry def create_session_with_retry(): """재시도 로직이 포함된 Session 생성""" session = requests.Session() retry_strategy = Retry( total=3, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504], allowed_methods=["GET"] ) adapter = HTTPAdapter(max_retries=retry_strategy) session.mount("https://", adapter) session.mount("http://", adapter) return session class RateLimitedClient: def __init__(self, requests_per_second=15): self.session = create_session_with_retry() self.min_interval = 1.0 / requests_per_second self.last_request = 0 def throttled_get(self, url, params=None): """Rate limit 적용된 GET 요청""" elapsed = time.time() - self.last_request if elapsed < self.min_interval: time.sleep(self.min_interval - elapsed) self.last_request = time.time() return self.session.get(url, params=params)

사용

client = RateLimitedClient(requests_per_second=15) response = client.throttled_get(endpoint, params=params)

2. 옵션 만기 데이터 누락 (Expiration Gap)

# ❌ 문제: 특정 만기 데이터가 없어 변동성 곡면이 불연속적

✅ 해결: 만기 interpolation 및 만기별 가중 平均값 활용

def fill_expiration_gaps(df, min_expiry=7, max_expiry=180): """만기별 결측치 interpolation""" df = df.copy() # 목표 만기 그리드 설정 target_expiries = [7, 14, 30, 60, 90, 120, 180] # 각 만기에 대해 Linear interpolation available_expiries = sorted(df["days_to_expiry"].unique()) filled_data = [] for target in target_expiries: if target in available_expiries: # 정확한 데이터 존재 시 그대로 사용 filled_data.append(df[df["days_to_expiry"] == target]) else: # 인접 만기 interpolation lower = max([e for e in available_expiries if e < target], default=None) upper = min([e for e in available_expiries if e > target], default=None) if lower and upper: weight = (target - lower) / (upper - lower) lower_data = df[df["days_to_expiry"] == lower].copy() upper_data = df[df["days_to_expiry"] == upper].copy() for _, row in lower_data.iterrows(): upper_row = upper_data[upper_data["strike"] == row["strike"]] if len(upper_row) > 0: interpolated = row.copy() interpolated["iv"] = row["iv"] * (1 - weight) + upper_row["iv"].values[0] * weight interpolated["days_to_expiry"] = target interpolated["interpolated"] = True filled_data.append(interpolated) return pd.concat(filled_data, ignore_index=True) if filled_data else df

적용

df_filled = fill_expiration_gaps(df)

3. Historical Data Timestamp 변환 오류

# ❌ 문제: Deribit timestamps (milliseconds) vs Python (seconds) 혼동

datetime.fromtimestamp(1735689600000) -> 54253-08-08 오류

✅ 해결: 밀리초 → 초 변환 및 timezone 처리

from datetime import datetime, timezone def deribit_timestamp_to_datetime(timestamp_ms): """Deribit 밀리초 타임스탬프를 Python datetime으로 변환""" if timestamp_ms is None: return None # 밀리초 → 초 변환 timestamp_sec = timestamp_ms / 1000 # UTC 기준 datetime 생성 dt = datetime.fromtimestamp(timestamp_sec, tz=timezone.utc) return dt def datetime_to_deribit_timestamp(dt=None): """datetime을 Deribit 밀리초 타임스탬프로 변환""" if dt is None: dt = datetime.now(timezone.utc) elif dt.tzinfo is None: dt = dt.replace(tzinfo=timezone.utc) return int(dt.timestamp() * 1000)

사용 예시

raw_timestamp = 1735689600000 dt = deribit_timestamp_to_datetime(raw_timestamp) print(f"Deribit timestamp: {raw_timestamp} -> {dt.isoformat()}")

출력: 2025-01-01T00:00:00+00:00

일별 히스토리 조회

start_dt = datetime(2024, 12, 1, tzinfo=timezone.utc) end_dt = datetime(2024, 12, 31, tzinfo=timezone.utc) params = { "start_timestamp": datetime_to_deribit_timestamp(start_dt), "end_timestamp": datetime_to_deribit_timestamp(end_dt) }

4. Greeks 부호 및 단위 불일치

# ❌ 문제: Deribit Greeks vs Black-Scholes 표기법 차이

Deribit: delta in terms of spot, theta in USD/day

BS Model: delta in terms of forward, theta in USD/year

✅ 해결: 단위 변환 유틸리티 함수 구현

class GreeksConverter: @staticmethod def deribit_to_standard(delta_deribit, option_type="call"): """ Deribit delta → 표준 BS delta 변환 Deribit delta는 현물 대비, BS delta는 선물 대비 """ # Deribit delta = BS_delta * (F / S) # Simplified: F ≈ S * exp(r*T), but for close expiry approximation if option_type.lower() == "call": return delta_deribit # Approximately same for ATM else: return delta_deribit + 1 # Put delta conversion @staticmethod def theta_daily_to_annual(theta_daily, convention="per_day"): """Theta 단위 변환: daily → annual""" if convention == "per_day": return theta_daily * 365 return theta_daily @staticmethod def normalize_greeks(greeks_dict, spot_price, position_size=1): """Greek 값을 포지션 기준으로 정규화""" normalized = {} if "delta" in greeks_dict: # Portfolio delta in BTC normalized["delta_btc"] = greeks_dict["delta"] * position_size normalized["delta_usd"] = normalized["delta_btc"] * spot_price if "gamma" in greeks_dict: # Gamma per 1% move in spot normalized["gamma_per_pct"] = greeks_dict["gamma"] * spot_price * 0.01 * position_size if "vega" in greeks_dict: # Vega per 1% move in IV normalized["vega_per_pct_iv"] = greeks_dict["vega"] * 0.01 * position_size return normalized

사용

greeks = {"delta": 0.45, "gamma": 0.002, "vega": 150, "theta": -80} spot = 95000 normalized = GreeksConverter.normalize_greeks(greeks, spot) print(f"Portfolio Delta (USD): ${normalized['delta_usd']:,.2f}")

성능 최적화: 대량 데이터 수집 전략

1년 이상의 Historical 데이터를 수집할 때, 순차적 API 호출은 너무 느립니다. 저는 다음 전략을 사용하여 수집 속도를 개선했습니다:

import asyncio
import aiohttp
from concurrent.futures import ThreadPoolExecutor

async def fetch_surface_data_async(session, instrument, semaphore):
    """비동기 방식으로 단일 옵션 데이터 fetch"""
    async with semaphore:
        url = "https://www.deribit.com/api/v2/public/get_order_book"
        params = {"instrument_name": instrument}
        
        try:
            async with session.get(url, params=params) as response:
                if response.status == 200:
                    data = await response.json()
                    return data.get("result")
                else:
                    return None
        except Exception as e:
            print(f"Error: {e}")
            return None

async def collect_all_surfaces_async(instruments, max_concurrent=10):
    """병렬 수집 메인 함수"""
    semaphore = asyncio.Semaphore(max_concurrent)
    
    async with aiohttp.ClientSession() as session:
        tasks = [
            fetch_surface_data_async(session, inst["instrument_name"], semaphore)
            for inst in instruments
        ]
        
        results = await asyncio.gather(*tasks)
        return [r for r in results if r is not None]

실행

instruments = collector.get_instruments()[:100] # 상위 100개 results = asyncio.run(collect_all_surfaces_async(instruments, max_concurrent=10)) print(f"수집 완료: {len(results)} 옵션")

실제 적용 사례: AI 기반 변동성 예측 모델

Deribit에서 수집한 변동성 곡면 데이터를 HolySheep AI와 연계하여 ML 기반 예측 모델을 구축했습니다. HolySheep AI의 deepseek-chat 모델을 활용하면 옵션 시장 데이터 기반_sentiment 분석과 자연어 기반 리포트 생성이 가능합니다:

import openai

HolySheep AI API configuration

client = openai.OpenAI( api_key="YOUR_HOLYSHEEP_API_KEY", base_url="https://api.holysheep.ai/v1" # HolySheep 게이트웨이 사용 ) def generate_volatility_report(surface_analysis): """변동성 곡면 분석 결과 기반 AI 보고서 생성""" prompt = f""" BTC 옵션 시장 변동성 곡면 분석 결과: 현재 ATM 변동성: {surface_analysis['atm_iv']:.2%} IV Rank (30일): {surface_analysis['iv_rank']:.2%} 25Δ Risk Reversal: {surface_analysis['rr_25']:.4f} 25Δ Butterfly: {surface_analysis['bf_25']:.4f} 단기(7D) IV: {surface_analysis['short_term_iv']:.2%} 장기(90D) IV: {surface_analysis['long_term_iv']:.2%} 위 데이터를 기반으로 다음 내용을 분석해주세요: 1. 현재 시장 mood (공포/탐욕 지표) 2. 변동성 스마일 형태 해석 3. 단기 vs 장기 관점의 거래 기회 4. 리스크 관리 시사점 """ response = client.chat.completions.create( model="deepseek-chat", # HolySheep를 통한 DeepSeek 모델 사용 messages=[ {"role": "system", "content": "당신은 숙련된 파생상품 트레이더입니다."}, {"role": "user", "content": prompt} ], temperature=0.3, max_tokens=1000 ) return response.choices[0].message.content

HolySheep AI를 통한 DeepSeek 모델 활용 (가격: $0.42/MTok)

실제 사용 시 HolySheep API 키로 교체

analysis = { "atm_iv": 0.65, "iv_rank": 0.72, "rr_25": 0.08, "bf_25": -0.02, "short_term_iv": 0.72, "long_term_iv": 0.58 } report = generate_volatility_report(analysis) print(report)

Deribit API vs 대체 거래소 비교

항목DeribitBinance OptionsOKX OptionsBybit Options
지원 통화BTC, ETHBTC, ETHBTC, ETHBTC, ETH
Historical Data제한적 (30일)제한적제한적제한적
WebSocket 지원✅ 우수✅ 양호✅ 양호✅ 양호
Rate Limit20 req/sec10 req/sec15 req/sec10 req/sec
API 안정성⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Greeks 제공✅ 실시간❌ 미지원✅ 제한적❌ 미지원

결론 및 다음 단계

Deribit 옵션 히스토리 데이터 API를 활용하면 BTC·ETH 변동성 곡면을 효과적으로 구축하고 백테스팅할 수 있습니다. 주요 포인트는 다음과 같습니다:

변동성 곡면 분석은 옵션 거래의 핵심입니다. 위 가이드를 바탕으로 자신의 거래 전략에 맞는 백테스팅 시스템을 구축해 보시기 바랍니다.

👉 HolySheep AI 가입하고 무료 크레딧 받기