저는 HolySheep AI의 기술팀에서 프로덕션 레벨 AI 파이프라인 구축을 담당하고 있습니다. 이번 튜토리얼에서는 HolySheep AI를 활용해 Tardis API에서 OKX 옵션 Greeks 데이터를 수집하고, 역사적 아카이빙을 구성하며, 변동성 곡면(Volatility Surface)을 구축하는 전체 아키텍처를 다루겠습니다.

1. 아키텍처 개요

OKX 옵션 마켓 데이터는 전통 금융보다 높은 변동성과 24/7 거래 특성으로 인해 실시간 처리와 배치 아카이빙 모두에서 특별한 설계가 필요합니다. 전체 파이프라인은 세 개의 핵심 레이어로 구성됩니다.

2. 환경 구성

# requirements.txt

================

holysheep>=0.1.8 tardis-client>=2.0.0 asyncpg>=0.29.0 pandas>=2.1.0 numpy>=1.26.0 pydantic>=2.5.0 httpx>=0.26.0 websockets>=12.0 plotly>=5.18.0 ta>=0.10.2

설치 명령

pip install -r requirements.txt
# config.py

=========

import os from dataclasses import dataclass @dataclass class HolySheepConfig: """HolySheep AI API 설정""" base_url: str = "https://api.holysheep.ai/v1" api_key: str = os.getenv("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY") model: str = "gpt-4.1" max_tokens: int = 4096 temperature: float = 0.3 @dataclass class TardisConfig: """Tardis OKX API 설정""" api_key: str = os.getenv("TARDIS_API_KEY", "YOUR_TARDIS_API_KEY") exchange: str = "okx" channel: str = "options_greeks" inst_type: str = "OPTION" @dataclass class DatabaseConfig: """TimescaleDB 설정""" host: str = os.getenv("TIMESCALE_HOST", "localhost") port: int = 5432 database: str = "options_data" user: str = os.getenv("DB_USER", "postgres") password: str = os.getenv("DB_PASSWORD", "")

HolySheep API 클라이언트 인스턴스 생성

HOLYSHEEP = HolySheepConfig()

검증

assert HOLYSHEEP.base_url == "https://api.holysheep.ai/v1", \ "HolySheep base_url이 올바르지 않습니다"

3. HolySheep AI 통합: Greeks 데이터 분석기

옵션 Greeks 데이터는 Delta, Gamma, Vega, Theta, Rho 등 다차원 지표를 포함합니다. HolySheep AI를 활용하면 이 복잡한 데이터를 자연어로 분석하고, 이상치를 자동 탐지하며, 변동성 예측 모델을 생성할 수 있습니다.

# holysheep_client.py

===================

import httpx import json from typing import Optional, Dict, Any, List from dataclasses import dataclass from config import HOLYSHEEP @dataclass class GreeksAnalysis: """Greeks 분석 결과""" delta: float gamma: float vega: float theta: float rho: float implied_volatility: float anomaly_score: Optional[float] = None analysis_text: Optional[str] = None class HolySheepGreeksClient: """HolySheep AI API를 활용한 Greeks 데이터 분석 클라이언트""" def __init__(self, config: HolySheepConfig): self.base_url = config.base_url self.api_key = config.api_key self.model = config.model self.max_tokens = config.max_tokens self.temperature = config.temperature def _build_headers(self) -> Dict[str, str]: return { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" } async def analyze_greeks_anomaly( self, greeks_data: Dict[str, float], market_context: str = "" ) -> GreeksAnalysis: """ Greeks 데이터에서 이상치 탐지 및 분석 수행 Args: greeks_data: Greeks 지표 딕셔너리 market_context: 시장 상황 설명 Returns: 분석 결과가 포함된 GreeksAnalysis 객체 """ prompt = f"""OKX 옵션 Greeks 데이터를 분석하고 이상치를 탐지하세요. 현재 Greeks 데이터: - Delta (δ): {greeks_data.get('delta', 0):.4f} - Gamma (γ): {greeks_data.get('gamma', 0):.6f} - Vega (ν): {greeks_data.get('vega', 0):.4f} - Theta (θ): {greeks_data.get('theta', 0):.4f} - Rho (ρ): {greeks_data.get('rho', 0):.4f} - 내재 변동성 (IV): {greeks_data.get('implied_volatility', 0):.2%} 시장 컨텍스트: {market_context} 다음 형식으로 JSON 응답: {{ "anomaly_score": 0.0~1.0 (이상치 확률), "analysis": "간결한 분석 텍스트 (50자 이내)", "recommendations": ["권장 조치사항 1", "권장 조치사항 2"] }}""" async with httpx.AsyncClient(timeout=30.0) as client: response = await client.post( f"{self.base_url}/chat/completions", headers=self._build_headers(), json={ "model": self.model, "messages": [ {"role": "system", "content": "당신은 전문 옵션 퀀트 애널리스트입니다."}, {"role": "user", "content": prompt} ], "max_tokens": self.max_tokens, "temperature": self.temperature } ) if response.status_code != 200: raise Exception(f"API 오류: {response.status_code} - {response.text}") result = response.json() content = result["choices"][0]["message"]["content"] # JSON 파싱 (실제 구현에서는 더 강력한 파싱 필요) try: analysis = json.loads(content) return GreeksAnalysis( delta=greeks_data.get('delta', 0), gamma=greeks_data.get('gamma', 0), vega=greeks_data.get('vega', 0), theta=greeks_data.get('theta', 0), rho=greeks_data.get('rho', 0), implied_volatility=greeks_data.get('implied_volatility', 0), anomaly_score=analysis.get("anomaly_score", 0), analysis_text=analysis.get("analysis", "") ) except json.JSONDecodeError: # Fallback: 단순 분석 반환 return GreeksAnalysis( **greeks_data, anomaly_score=0.5, analysis_text="분석 파싱 실패" ) async def generate_volatility_forecast( self, historical_iv_data: List[Dict[str, Any]], strike: float, expiry: str ) -> Dict[str, Any]: """ 역사적 IV 데이터를 기반으로 변동성 예측 생성 Returns: 예측 결과 딕셔너리 (예측 IV, 신뢰구간, 분석) """ iv_series = [d['iv'] for d in historical_iv_data[-20:]] # 최근 20개 데이터 timestamps = [d['timestamp'] for d in historical_iv_data[-20:]] prompt = f"""OKX 옵션 변동성 시계열 데이터를 분석하여 단기 예측을 수행하세요. Striker Price: ${strike} 만기: {expiry} 최근 IV 시계열: {iv_series} 타임스탬프: {timestamps} 다음 JSON 형식으로 응답: {{ "predicted_iv": 0.0~1.0 (예측 IV), "confidence_lower": 0.0~1.0 (신뢰구간 하한), "confidence_upper": 0.0~1.0 (신뢰구간 상한), "trend": "bullish|bearish|neutral", "key_factors": ["영향 요인 1", "영향 요인 2"] }}""" async with httpx.AsyncClient(timeout=45.0) as client: response = await client.post( f"{self.base_url}/chat/completions", headers=self._build_headers(), json={ "model": self.model, "messages": [ {"role": "system", "content": "당신은 변동성 거래 전문가입니다."}, {"role": "user", "content": prompt} ], "max_tokens": self.max_tokens, "temperature": 0.2 } ) return response.json()["choices"][0]["message"]["content"]

클라이언트 인스턴스 생성

holysheep_client = HolySheepGreeksClient(HOLYSHEEP)

4. Tardis OKX Greeks 실시간 수집기

# tardis_collector.py

====================

import asyncio import json import logging from datetime import datetime, timezone from typing import Dict, Any, Optional, Callable, List import httpx from config import HOLYSHEEP, TardisConfig logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class TardisOKXGreeksCollector: """ Tardis API에서 OKX 옵션 Greeks 실시간 데이터 수집 HolySheep AI와 연계하여 실시간 이상치 탐지 및 분석 수행 """ def __init__(self, tardis_config: TardisConfig, holysheep_client): self.api_key = tardis_config.api_key self.exchange = tardis_config.exchange self.channel = tardis_config.channel self.inst_type = tardis_config.inst_type self.holysheep = holysheep_client self.base_url = "https://api.tardis.dev/v1" self._running = False self._buffer: List[Dict[str, Any]] = [] async def _fetch_historical( self, start_date: str, end_date: str, symbols: Optional[List[str]] = None ) -> List[Dict[str, Any]]: """역사적 Greeks 데이터 배치 수집""" params = { "exchange": self.exchange, "channel": self.channel, "dateFrom": start_date, "dateTo": end_date, "format": "json" } if symbols: params["symbols"] = ",".join(symbols) headers = {"Authorization": f"Bearer {self.api_key}"} async with httpx.AsyncClient(timeout=300.0) as client: response = await client.get( f"{self.base_url}/historical", params=params, headers=headers ) if response.status_code == 200: data = response.json() logger.info(f"역사적 데이터 {len(data)}건 수신 완료") return data else: raise Exception(f"Historical fetch 실패: {response.status_code}") async def stream_greeks( self, symbols: List[str], on_data: Optional[Callable] = None, buffer_size: int = 100 ): """ 실시간 Greeks 스트림 구독 Args: symbols: 구독할 옵션 심볼 리스트 on_data: 데이터 수신 시 콜백 함수 buffer_size: HolySheep 배치 처리를 위한 버퍼 크기 """ self._running = True ws_url = f"wss://api.tardis.dev/v1/stream" headers = {"Authorization": f"Bearer {self.api_key}"} async with httpx.AsyncClient() as client: async with client.ws_connect(ws_url, headers=headers) as ws: # 구독 메시지 전송 subscribe_msg = { "action": "subscribe", "exchange": self.exchange, "channel": self.channel, "symbols": symbols } await ws.send_json(subscribe_msg) logger.info(f"구독 시작: {symbols}") while self._running: try: msg = await asyncio.wait_for(ws.receive_json(), timeout=30.0) if msg.get("type") == "data": greeks_record = self._parse_greeks(msg["data"]) self._buffer.append(greeks_record) # 버퍼가 채워지면 HolySheep로 분석 if len(self._buffer) >= buffer_size: await self._process_buffer() # 콜백 실행 if on_data: await on_data(greeks_record) except asyncio.TimeoutError: # 하트비트: 연결 유지 logger.debug("하트비트 체크") except Exception as e: logger.error(f"스트림 오류: {e}") await asyncio.sleep(5) # 재연결 대기 def _parse_greeks(self, raw_data: Dict[str, Any]) -> Dict[str, Any]: """Tardis 데이터 파싱 및 정규화""" return { "timestamp": datetime.now(timezone.utc).isoformat(), "symbol": raw_data.get("instId", ""), "underlying": raw_data.get("uly", ""), "strike": float(raw_data.get("strike", 0)), "expiry": raw_data.get("exp", ""), "delta": float(raw_data.get("delta", 0)), "gamma": float(raw_data.get("gamma", 0)), "vega": float(raw_data.get("vega", 0)), "theta": float(raw_data.get("theta", 0)), "rho": float(raw_data.get("rho", 0)), "iv_bid": float(raw_data.get("bidIv", 0)), "iv_ask": float(raw_data.get("askIv", 0)), "iv_last": float(raw_data.get("lastIv", 0)), "mark_price": float(raw_data.get("markPx", 0)), "open_interest": float(raw_data.get("oi", 0)), "volume": float(raw_data.get("vol24h", 0)) } async def _process_buffer(self): """버퍼된 데이터를 HolySheep AI로 배치 분석""" if not self._buffer: return # 샘플링: HolySheep 비용 최적화를 위해 10% 샘플링 sample_size = max(1, len(self._buffer) // 10) sampled = self._buffer[-sample_size:] tasks = [] for record in sampled: greeks = { "delta": record["delta"], "gamma": record["gamma"], "vega": record["vega"], "theta": record["theta"], "rho": record["rho"], "implied_volatility": (record["iv_bid"] + record["iv_ask"]) / 2 } # HolySheep AI 이상치 분석 task = self.holysheep.analyze_greeks_anomaly( greeks_data=greeks, market_context=f"Underlying: {record['underlying']}, Strike: {record['strike']}" ) tasks.append((record, task)) # 병렬 처리 results = await asyncio.gather(*[t[1] for t in tasks], return_exceptions=True) # 결과 로깅 for i, (record, result) in enumerate(zip(sampled, results)): if isinstance(result, Exception): logger.error(f"분석 실패: {record['symbol']} - {result}") elif result.anomaly_score > 0.7: logger.warning( f"고이상치 탐지: {record['symbol']} " f"(anomaly_score={result.anomaly_score:.2f})" ) # 버퍼 초기화 self._buffer = [] def stop(self): """스트림 수집 중지""" self._running = False logger.info("스트림 수집 중지 요청")

사용 예시

async def main(): from holysheep_client import holysheep_client collector = TardisOKXGreeksCollector( tardis_config=TardisConfig(), holysheep_client=holysheep_client ) # OKX BTC 옵션 Greeks 스트리밍 symbols = [ "BTC-USD-250531-95000-C", # BTC 콜옵션 "BTC-USD-250531-95000-P", # BTC 풋옵션 "BTC-USD-250531-100000-C", "BTC-USD-250531-90000-P" ] try: await collector.stream_greeks(symbols=symbols) except KeyboardInterrupt: collector.stop() if __name__ == "__main__": asyncio.run(main())

5. 역사적 아카이빙과 TimescaleDB 연동

# timescale_archiver.py

=====================

import asyncpg import asyncio import logging from datetime import datetime, timedelta from typing import List, Dict, Any, Optional from config import DatabaseConfig logger = logging.getLogger(__name__) class GreeksArchiver: """TimescaleDB 기반 Greeks 데이터 아카이빙""" CREATE_TABLE_SQL = """ CREATE TABLE IF NOT EXISTS okx_options_greeks ( time TIMESTAMPTZ NOT NULL, symbol TEXT NOT NULL, underlying TEXT NOT NULL, strike DOUBLE PRECISION NOT NULL, expiry TEXT NOT NULL, delta DOUBLE PRECISION, gamma DOUBLE PRECISION, vega DOUBLE PRECISION, theta DOUBLE PRECISION, rho DOUBLE PRECISION, iv_bid DOUBLE PRECISION, iv_ask DOUBLE PRECISION, iv_mid DOUBLE PRECISION, iv_last DOUBLE PRECISION, mark_price DOUBLE PRECISION, open_interest DOUBLE PRECISION, volume DOUBLE PRECISION, anomaly_score DOUBLE PRECISION, PRIMARY KEY (time, symbol) ); -- TimescaleDB 하이퍼테이블 변환 SELECT create_hypertable('okx_options_greeks', 'time', if_not_exists => TRUE, migrate_data => TRUE ); -- 인덱스 생성 CREATE INDEX IF NOT EXISTS idx_greeks_symbol ON okx_options_greeks (symbol); CREATE INDEX IF NOT EXISTS idx_greeks_expiry ON okx_options_greeks (expiry); CREATE INDEX IF NOT EXISTS idx_greeks_strike ON okx_options_greeks (strike); """ def __init__(self, config: DatabaseConfig): self.config = config self.pool: Optional[asyncpg.Pool] = None async def connect(self): """데이터베이스 풀 연결""" self.pool = await asyncpg.create_pool( host=self.config.host, port=self.config.port, database=self.config.database, user=self.config.user, password=self.config.password, min_size=5, max_size=20 ) logger.info("TimescaleDB 연결 완료") async def initialize_schema(self): """스키마 초기화""" async with self.pool.acquire() as conn: await conn.execute(self.CREATE_TABLE_SQL) logger.info("스키마 초기화 완료") async def insert_greeks( self, records: List[Dict[str, Any]], anomaly_scores: Optional[List[float]] = None ): """Greeks 데이터 배치 삽입""" if not records: return insert_sql = """ INSERT INTO okx_options_greeks ( time, symbol, underlying, strike, expiry, delta, gamma, vega, theta, rho, iv_bid, iv_ask, iv_mid, iv_last, mark_price, open_interest, volume, anomaly_score ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18 ) ON CONFLICT (time, symbol) DO UPDATE SET delta = EXCLUDED.delta, gamma = EXCLUDED.gamma, vega = EXCLUDED.vega, theta = EXCLUDED.theta, rho = EXCLUDED.rho, iv_mid = EXCLUDED.iv_mid, anomaly_score = COALESCE(EXCLUDED.anomaly_score, okx_options_greeks.anomaly_score) """ async with self.pool.acquire() as conn: async with conn.transaction(): for i, record in enumerate(records): iv_mid = (record.get("iv_bid", 0) + record.get("iv_ask", 0)) / 2 score = anomaly_scores[i] if anomaly_scores else None await conn.execute( insert_sql, datetime.fromisoformat(record["timestamp"].replace("Z", "+00:00")), record["symbol"], record["underlying"], record["strike"], record["expiry"], record["delta"], record["gamma"], record["vega"], record["theta"], record["rho"], record["iv_bid"], record["iv_ask"], iv_mid, record.get("iv_last", iv_mid), record["mark_price"], record["open_interest"], record["volume"], score ) logger.info(f"{len(records)}건 데이터 삽입 완료") async def query_volatility_surface( self, expiry: str, underlying: str = "BTC-USD", start_time: Optional[datetime] = None, end_time: Optional[datetime] = None ) -> List[Dict[str, Any]]: """변동성 곡면 생성을 위한 IV 데이터 조회""" where_clauses = ["expiry = $1", "underlying = $2"] params = [expiry, underlying] param_idx = 3 if start_time: where_clauses.append(f"time >= ${param_idx}") params.append(start_time) param_idx += 1 if end_time: where_clauses.append(f"time <= ${param_idx}") params.append(end_time) sql = f""" SELECT time_bucket('1 minute', time) as bucket, strike, AVG(iv_mid) as avg_iv, STDDEV(iv_mid) as iv_std, AVG(delta) as avg_delta, AVG(gamma) as avg_gamma, COUNT(*) as sample_count FROM okx_options_greeks WHERE {' AND '.join(where_clauses)} GROUP BY bucket, strike ORDER BY strike, bucket """ async with self.pool.acquire() as conn: rows = await conn.fetch(sql, *params) return [ { "time": row["bucket"], "strike": row["strike"], "avg_iv": row["avg_iv"], "iv_std": row["iv_std"], "avg_delta": row["avg_delta"], "avg_gamma": row["avg_gamma"], "sample_count": row["sample_count"] } for row in rows ] async def get_moneyness_buckets(self, expiry: str) -> Dict[str, float]: """만기별 moneyness별 IV 통계""" sql = """ WITH strikes AS ( SELECT DISTINCT strike FROM okx_options_greeks WHERE expiry = $1 ), moneyness AS ( SELECT g.*, (SELECT percentile_cont(0.5) FROM okx_options_greeks WHERE expiry = $1 AND strike = g.strike AND time > NOW() - INTERVAL '1 hour') as current_price FROM okx_options_greeks g WHERE expiry = $1 ) SELECT CASE WHEN strike / current_price > 1.1 THEN 'OTM_10%' WHEN strike / current_price > 1.05 THEN 'OTM_5%' WHEN strike / current_price BETWEEN 0.95 AND 1.05 THEN 'ATM' WHEN strike / current_price > 0.9 THEN 'ITM_5%' ELSE 'ITM_10%' END as moneyness_bucket, AVG(iv_mid) as avg_iv, STDDEV(iv_mid) as iv_std, COUNT(*) as count FROM moneyness WHERE time > NOW() - INTERVAL '24 hours' GROUP BY moneyness_bucket ORDER BY moneyness_bucket """ async with self.pool.acquire() as conn: rows = await conn.fetch(sql, expiry) return {row["moneyness_bucket"]: row["avg_iv"] for row in rows} async def continuous_aggregate(self, granularity: str = "1 hour"): """실시간 집계 뷰 생성""" sql = f""" CREATE MATERIALIZED VIEW IF NOT EXISTS greeks_hourly_{granularity.replace(' ', '_')} WITH (timescaledb.continuous) AS SELECT time_bucket('{granularity}', time) as bucket, expiry, strike, AVG(delta) as avg_delta, AVG(gamma) as avg_gamma, AVG(vega) as avg_vega, AVG(theta) as avg_theta, AVG(iv_mid) as avg_iv, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY iv_mid) as median_iv FROM okx_options_greeks GROUP BY bucket, expiry, strike """ async with self.pool.acquire() as conn: await conn.execute(sql) logger.info(f"연속 집계 생성 완료: {granularity}")

6. 변동성 곡면 시각화

# volatility_surface.py

======================

import plotly.graph_objects as go import pandas as pd from typing import List, Dict, Any, Optional from timescale_archiver import GreeksArchiver class VolatilitySurfaceBuilder: """변동성 곡면 빌더 및 시각화""" def build_surface_3d( self, surface_data: List[Dict[str, Any]], title: str = "OKX Options Implied Volatility Surface" ) -> go.Figure: """3D 변동성 곡면 생성""" df = pd.DataFrame(surface_data) # 피벗 테이블 생성 pivot = df.pivot_table( values='avg_iv', index='strike', columns='bucket', aggfunc='mean' ) strikes = pivot.index.tolist() times = [t.isoformat() for t in pivot.columns] fig = go.Figure(data=[ go.Surface( z=pivot.values, x=times, y=strikes, colorscale='Viridis', colorbar=dict( title='IV', titleside='right', titlefont=dict(size=14) ), hovertemplate='Strike: %{y}
Time: %{x}
IV: %{z:.4f}' ) ]) fig.update_layout( title=dict( text=title, font=dict(size=18) ), scene=dict( xaxis_title='Time', yaxis_title='Strike Price', zaxis_title='Implied Volatility', camera=dict( eye=dict(x=1.5, y=1.5, z=1.0) ) ), width=1200, height=800, margin=dict(l=65, r=50, b=65, t=90) ) return fig def build_smile_chart( self, iv_data: List[Dict[str, Any]], expiry: str, timeframe: str = "latest" ) -> go.Figure: """IV Smile/Curve 차트 생성""" df = pd.DataFrame(iv_data) #strike별 IV 차트 fig = go.Figure() for expiry_group in df['expiry'].unique(): subset = df[df['expiry'] == expiry_group] fig.add_trace(go.Scatter( x=subset['strike'], y=subset['avg_iv'] * 100, # % 변환 mode='lines+markers', name=f"만기: {expiry_group}", line=dict(width=2), marker=dict(size=8) )) fig.update_layout( title=f"OKX Options IV Smile - {timeframe}", xaxis_title='Strike Price', yaxis_title='Implied Volatility (%)', hovermode='x unified', legend=dict( yanchor="top", y=0.99, xanchor="left", x=0.01 ), template="plotly_white", width=1000, height=600 ) return fig def build_term_structure( self, archiver: GreeksArchiver, strikes: Optional[List[float]] = None, underlying: str = "BTC-USD" ) -> go.Figure: """만기별 IV Term Structure 차트""" sql = """ SELECT expiry, AVG(iv_mid) as avg_iv, PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY iv_mid) as q25_iv, PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY iv_mid) as q75_iv FROM okx_options_greeks WHERE underlying = $1 AND time > NOW() - INTERVAL '7 days' GROUP BY expiry ORDER BY expiry """ # 실제 구현에서는 archiver.pool로 쿼리 실행 # 예시 데이터 fig = go.Figure() fig.add_trace(go.Scatter( x=['250531', '250628', '250725', '250822'], y=[0.52, 0.58, 0.63, 0.67], mode='lines+markers', name='평균 IV', line=dict(color='blue', width=3), marker=dict(size=10) )) fig.add_trace(go.Scatter( x=['250531', '250628', '250725', '250822'], y=[0.48, 0.52, 0.58, 0.62], mode='lines', name='Q25 IV', line=dict(color='lightblue', width=1, dash='dash') )) fig.add_trace(go.Scatter( x=['250531', '250628', '250725', '250822'], y=[0.56, 0.64, 0.68, 0.72], mode='lines', name='Q75 IV', line=dict(color='lightblue', width=1, dash='dash'), fill='tonexty' )) fig.update_layout( title=f"OKX {underlying} IV Term Structure", xaxis_title='Expiry', yaxis_title='Implied Volatility', template="plotly_white", hovermode='x unified' ) return fig

7. 프로덕션 배포: Docker와 모니터링

# docker-compose.yml
version: '3.8'

services:
  greeks-collector:
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      - HOLYSHEEP_API_KEY=${HOLYSHEEP_API_KEY}
      - TARDIS_API_KEY=${TARDIS_API_KEY}
      - TIMESCALE_HOST=timescaledb
      - DB_USER=postgres
      - DB_PASSWORD=${DB_PASSWORD}
    depends_on:
      - timescaledb
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 4G

  timescaledb:
    image: timescale/timescaledb:latest-pg16
    environment:
      - POSTGRES_DB=options_data
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - timeseries_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    restart: unless-stopped

  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}

volumes:
  timeseries_data:
  grafana_data:
# metrics.py - Prometheus 메트릭스

=================================

from prometheus_client import Counter, Histogram, Gauge import time

메트릭스 정의

GREEKS_RECORDS_COLLECTED = Counter( 'greeks_records_collected_total', 'Total Greeks records collected', ['symbol', 'exchange'] ) GREEKS_LATENCY = Histogram( 'greeks_api_latency_seconds', 'HolySheep API latency', ['operation'], buckets=[0.1, 0.25, 0.5, 1.0, 2.5, 5.0] ) ANOMALY_DETECTED = Counter( 'greeks_anomaly_detected_total', 'Total anomalies detected', ['severity'] ) IV_VOLATILITY = Gauge( 'iv_current_volatility', 'Current implied volatility', ['symbol', 'expiry'] ) class MetricsCollector: """Prometheus 메트릭스 수집기""" @staticmethod @GREEKS_LATENCY.time() async def call_holysheep(operation: str, func, *args, **kwargs): start = time.time() try: result = await func(*args