저는 최근 암호화폐 자동 거래 봇을 개발하면서 OKX와 Bybit 두 거래소에서 동시에 실시간 시세를 수신해야 하는 상황에 부딪혔습니다. 단일 웹소켓 연결로 두 거래소의 데이터를 통합 관리하면서 지연 시간을 최소화하는 것이 핵심 과제였죠. 이 글에서는 HolySheep AI를 활용하여 안정적으로 두 거래소 API를 연동하는 실무 방안을 공유합니다.

왜 OKX와 Bybit인가: 글로벌 거래량 순위 분석

암호화폐 현물 및 선물 거래에서 OKX와 Bybit는 Binance에 이어 2, 3위 자리를 차지하고 있습니다. 두 거래소의 특징을 비교하면 다음과 같습니다:

항목 OKX Bybit
일일 평균 거래량 약 $25억 (선물) 약 $22억 (선물)
웹소켓 메시지 지연 평균 15~30ms 평균 10~25ms
시세 데이터 주기 100ms (선물), 실시간 (현물) 실시간 (100ms 업데이트)
지원 프로토콜 WebSocket v5, REST WebSocket v5, REST
무료 티어 요청 한도 초당 20회 초당 10회
Python SDK 지원 공식 제공 공식 제공

핵심 요구사항과 아키텍처 설계

실시간 시세 연동에서 제가 경험한 핵심 요구사항은 세 가지였습니다:

OKX 웹소켓 실시간 시세 연동

OKX는 Version 5 API를 제공하며, 웹소켓을 통해 실시간 시세, 호가창, 체결 데이터를 구독할 수 있습니다. 다음은 Python을 사용한 기본 연동 예제입니다:

# okx_realtime.py
import asyncio
import json
import websockets
from datetime import datetime

class OKXMarketData:
    """OKX 거래소 실시간 시세 수신 클래스"""
    
    def __init__(self):
        self.ws_url = "wss://ws.okx.com:8443/ws/v5/public"
        self.websocket = None
        self.ticker_data = {}
        
    async def connect(self):
        """웹소켓 연결 수립"""
        self.websocket = await websockets.connect(self.ws_url)
        print(f"[{datetime.now().strftime('%H:%M:%S.%f')[:-3]}] OKX 웹소켓 연결 완료")
        
    async def subscribe(self, inst_id="BTC-USDT-SWAP", channel="tickers"):
        """시세 채널 구독 요청"""
        subscribe_params = {
            "op": "subscribe",
            "args": [{
                "channel": channel,
                "instId": inst_id
            }]
        }
        await self.websocket.send(json.dumps(subscribe_params))
        print(f"구독 요청 전송: {inst_id} {channel}")
        
    async def receive_data(self):
        """실시간 데이터 수신 루프"""
        async for message in self.websocket:
            data = json.loads(message)
            
            if data.get("event") == "subscribe":
                print(f"구독 확인: {data}")
                continue
                
            if "data" in data:
                ticker = data["data"][0]
                timestamp = datetime.fromtimestamp(
                    int(ticker["ts"]) / 1000
                ).strftime("%H:%M:%S.%f")[:-3]
                
                self.ticker_data = {
                    "exchange": "OKX",
                    "symbol": ticker["instId"],
                    "last_price": float(ticker["last"]),
                    "bid_price": float(ticker["bidPx"]),
                    "ask_price": float(ticker["askPx"]),
                    "volume_24h": float(ticker["vol24h"]),
                    "timestamp": timestamp
                }
                
                print(f"[{timestamp}] {ticker['instId']} | "
                      f"매수: {ticker['bidPx']} | 매도: {ticker['askPx']} | "
                      f"최종: {ticker['last']}")
                      
    async def run(self):
        """메인 실행 함수"""
        await self.connect()
        await self.subscribe(inst_id="BTC-USDT-SWAP", channel="tickers")
        await self.receive_data()

실행

if __name__ == "__main__": okx_client = OKXMarketData() asyncio.run(okx_client.run())

실행 결과는 다음과 같습니다:

[14:32:01.123] OKX 웹소켓 연결 완료
구독 요청 전송: BTC-USDT-SWAP tickers
{'event': 'subscribe', 'args': [{'channel': 'tickers', 'instId': 'BTC-USDT-SWAP'}], 'code': '0', 'msg': ''}
[14:32:01.456] BTC-USDT-SWAP | 매수: 67432.50 | 매도: 67433.20 | 최종: 67433.10
[14:32:01.512] BTC-USDT-SWAP | 매수: 67432.80 | 매도: 67433.50 | 최종: 67433.30
[14:32:01.601] BTC-USDT-SWAP | 매수: 67432.90 | 매도: 67433.30 | 최종: 67433.00

Bybit 웹소켓 실시간 시세 연동

Bybit도 동일한 웹소켓 기반 구조를 사용합니다. 인증 방식이 조금 다르므로 주의가 필요합니다:

# bybit_realtime.py
import asyncio
import json
import websockets
import hmac
import hashlib
from datetime import datetime

class BybitMarketData:
    """Bybit 거래소 실시간 시세 수신 클래스"""
    
    def __init__(self, testnet=False):
        # Bybit는 테스트넷과 메인넷 URL이 다름
        base_url = "wss://stream-testnet.bybit.com" if testnet else "wss://stream.bybit.com"
        self.ws_url = f"{base_url}/v5/public/spot"
        self.websocket = None
        self.reconnect_count = 0
        self.max_reconnect = 5
        
    async def connect(self):
        """웹소켓 연결 수립"""
        try:
            self.websocket = await websockets.connect(
                self.ws_url,
                ping_interval=20,  # 20초마다 핑
                ping_timeout=10
            )
            print(f"[{datetime.now().strftime('%H:%M:%S')}] Bybit 웹소켓 연결 완료")
            self.reconnect_count = 0
        except Exception as e:
            print(f"연결 실패: {e}")
            await self.handle_reconnect()
            
    async def subscribe(self, symbol="BTCUSDT"):
        """시세 구독"""
        subscribe_msg = {
            "op": "subscribe",
            "args": [f"tickers.{symbol}"]
        }
        await self.websocket.send(json.dumps(subscribe_msg))
        print(f"Bybit 구독 요청: {symbol}")
        
    async def parse_ticker(self, raw_data):
        """티커 데이터 파싱 및 정규화"""
        return {
            "exchange": "Bybit",
            "symbol": raw_data.get("symbol"),
            "last_price": float(raw_data.get("lastPrice", 0)),
            "bid_price": float(raw_data.get("bid1Price", 0)),
            "ask_price": float(raw_data.get("ask1Price", 0)),
            "bid_qty": float(raw_data.get("bid1Size", 0)),
            "ask_qty": float(raw_data.get("ask1Size", 0)),
            "volume_24h": float(raw_data.get("volume24h", 0)),
            "turnover_24h": float(raw_data.get("turnover24h", 0)),
            "timestamp": datetime.now().strftime("%H:%M:%S.%f")[:-3]
        }
        
    async def receive_data(self):
        """데이터 수신 및 처리"""
        async for message in self.websocket:
            try:
                data = json.loads(message)
                
                # 구독 확인 메시지 무시
                if data.get("op") == "subscribe":
                    continue
                    
                # 핑-퐁 메시지 처리
                if data.get("topic", "").startswith("pong"):
                    continue
                    
                if "data" in data and data["data"]:
                    ticker = await self.parse_ticker(data["data"])
                    
                    spread = ticker["ask_price"] - ticker["bid_price"]
                    spread_pct = (spread / ticker["bid_price"]) * 100
                    
                    print(f"[{ticker['timestamp']}] {ticker['symbol']} | "
                          f"매수: {ticker['bid_price']:.2f} ({ticker['bid_qty']:.4f}) | "
                          f"매도: {ticker['ask_price']:.2f} ({ticker['ask_qty']:.4f}) | "
                          f"스프레드: {spread:.2f} ({spread_pct:.4f}%)")
                          
            except json.JSONDecodeError:
                print("JSON 파싱 오류")
            except Exception as e:
                print(f"데이터 처리 오류: {e}")
                
    async def handle_reconnect(self):
        """자동 재연결 처리"""
        if self.reconnect_count < self.max_reconnect:
            self.reconnect_count += 1
            wait_time = min(2 ** self.reconnect_count, 30)  # 지수 백오프
            print(f"{wait_time}초 후 재연결 시도 ({self.reconnect_count}/{self.max_reconnect})")
            await asyncio.sleep(wait_time)
            await self.connect()
            await self.subscribe()
        else:
            print("최대 재연결 횟수 초과, 연결 실패")

실행

if __name__ == "__main__": bybit_client = BybitMarketData(testnet=False) asyncio.run(bybit_client.connect()) asyncio.run(bybit_client.subscribe(symbol="BTCUSDT")) asyncio.run(bybit_client.receive_data())

양쪽 거래소 통합 모니터링 시스템

실무에서는 두 거래소의 시세를 동시에 모니터링해야 하는 경우가 많습니다. 다음은 통합 모니터링 시스템입니다:

# unified_market_monitor.py
import asyncio
import json
import websockets
from dataclasses import dataclass, field
from typing import Dict, Optional
from datetime import datetime
import redis.asyncio as redis

@dataclass
class UnifiedTicker:
    """통합 시세 데이터 구조"""
    symbol: str
    exchange: str
    bid_price: float
    ask_price: float
    last_price: float
    volume_24h: float
    timestamp_ms: int
    latency_ms: float = 0.0
    
@dataclass
class PriceSpread:
    """거래소 간 시세 차이"""
    symbol: str
    okx_price: float
    bybit_price: float
    spread_value: float
    spread_percent: float
    better_exchange: str  # "OKX" or "Bybit"
    timestamp: str

class UnifiedMarketMonitor:
    """OKX + Bybit 통합 시세 모니터"""
    
    def __init__(self, redis_client: Optional[redis.Redis] = None):
        self.okx_ws = None
        self.bybit_ws = None
        self.tickers: Dict[str, Dict[str, UnifiedTicker]] = {}
        self.redis = redis_client
        self.running = False
        
    async def connect_okx(self):
        """OKX 웹소켓 연결"""
        try:
            self.okx_ws = await websockets.connect(
                "wss://ws.okx.com:8443/ws/v5/public",
                ping_interval=25
            )
            # BTC, ETH 선물 구독
            symbols = ["BTC-USDT-SWAP", "ETH-USDT-SWAP", "SOL-USDT-SWAP"]
            for symbol in symbols:
                await self.okx_ws.send(json.dumps({
                    "op": "subscribe",
                    "args": [{"channel": "tickers", "instId": symbol}]
                }))
            print("[OKX] 연결 및 구독 완료")
        except Exception as e:
            print(f"[OKX] 연결 오류: {e}")
            
    async def connect_bybit(self):
        """Bybit 웹소켓 연결"""
        try:
            self.bybit_ws = await websockets.connect(
                "wss://stream.bybit.com/v5/public/spot",
                ping_interval=20
            )
            symbols = ["BTCUSDT", "ETHUSDT", "SOLUSDT"]
            for symbol in symbols:
                await self.bybit_ws.send(json.dumps({
                    "op": "subscribe",
                    "args": [f"tickers.{symbol}"]
                }))
            print("[Bybit] 연결 및 구독 완료")
        except Exception as e:
            print(f"[Bybit] 연결 오류: {e}")
            
    async def parse_okx_ticker(self, data: dict) -> Optional[UnifiedTicker]:
        """OKX 데이터 파싱"""
        try:
            ticker_data = data["data"][0]
            return UnifiedTicker(
                symbol=ticker_data["instId"],
                exchange="OKX",
                bid_price=float(ticker_data["bidPx"]),
                ask_price=float(ticker_data["askPx"]),
                last_price=float(ticker_data["last"]),
                volume_24h=float(ticker_data["vol24h"]),
                timestamp_ms=int(ticker_data["ts"]),
                latency_ms=datetime.now().timestamp() * 1000 - int(ticker_data["ts"])
            )
        except Exception as e:
            print(f"OKX 파싱 오류: {e}")
            return None
            
    async def parse_bybit_ticker(self, data: dict) -> Optional[UnifiedTicker]:
        """Bybit 데이터 파싱"""
        try:
            ticker_data = data["data"]
            return UnifiedTicker(
                symbol=ticker_data["symbol"],
                exchange="Bybit",
                bid_price=float(ticker_data["bid1Price"]),
                ask_price=float(ticker_data["ask1Price"]),
                last_price=float(ticker_data["lastPrice"]),
                volume_24h=float(ticker_data["volume24h"]),
                timestamp_ms=int(ticker_data["ts"]),
                latency_ms=datetime.now().timestamp() * 1000 - int(ticker_data["ts"])
            )
        except Exception as e:
            print(f"Bybit 파싱 오류: {e}")
            return None
            
    def calculate_spread(self, symbol: str) -> Optional[PriceSpread]:
        """거래소 간 시세 차이 계산"""
        if symbol not in self.tickers:
            return None
            
        tickers = self.tickers[symbol]
        if "OKX" not in tickers or "Bybit" not in tickers:
            return None
            
        okx = tickers["OKX"]
        bybit = tickers["Bybit"]
        
        # 평균 가격 비교
        okx_avg = (okx.ask_price + okx.bid_price) / 2
        bybit_avg = (bybit.ask_price + bybit.bid_price) / 2
        
        spread_value = abs(okx_avg - bybit_avg)
        spread_percent = (spread_value / min(okx_avg, bybit_avg)) * 100
        better = "OKX" if okx_avg < bybit_avg else "Bybit"
        
        return PriceSpread(
            symbol=symbol,
            okx_price=okx_avg,
            bybit_price=bybit_avg,
            spread_value=spread_value,
            spread_percent=spread_percent,
            better_exchange=better,
            timestamp=datetime.now().strftime("%H:%M:%S")
        )
        
    async def monitor_loop(self):
        """통합 모니터링 루프"""
        await self.connect_okx()
        await self.connect_bybit()
        self.running = True
        
        while self.running:
            try:
                # 동시 수신
                okx_task = asyncio.create_task(self.okx_ws.recv())
                bybit_task = asyncio.create_task(self.bybit_ws.recv())
                
                done, pending = await asyncio.wait(
                    [okx_task, bybit_task],
                    timeout=1.0,
                    return_when=asyncio.FIRST_COMPLETED
                )
                
                for task in done:
                    try:
                        message = task.result()
                        data = json.loads(message)
                        
                        # OKX 데이터 처리
                        if "arg" in data and "okx" in str(data).lower():
                            ticker = await self.parse_okx_ticker(data)
                            if ticker:
                                symbol = ticker.symbol.replace("-USDT-SWAP", "USDT")
                                if symbol not in self.tickers:
                                    self.tickers[symbol] = {}
                                self.tickers[symbol]["OKX"] = ticker
                                
                        # Bybit 데이터 처리
                        elif "topic" in data and "tickers" in data["topic"]:
                            ticker = await self.parse_bybit_ticker(data)
                            if ticker:
                                symbol = ticker.symbol
                                if symbol not in self.tickers:
                                    self.tickers[symbol] = {}
                                self.tickers[symbol]["Bybit"] = ticker
                                
                        # 시세 차이 계산 및 출력
                        for symbol in ["BTCUSDT", "ETHUSDT", "SOLUSDT"]:
                            spread = self.calculate_spread(symbol)
                            if spread and spread.spread_percent > 0.01:
                                print(f"[{spread.timestamp}] {symbol} | "
                                      f"OKX: ${spread.okx_price:.2f} | "
                                      f"Bybit: ${spread.bybit_price:.2f} | "
                                      f"차이: ${spread.spread_value:.2f} ({spread.spread_percent:.4f}%)")
                                      
                    except Exception as e:
                        print(f"메시지 처리 오류: {e}")
                        
                # 남은 태스크 취소
                for task in pending:
                    task.cancel()
                    
            except Exception as e:
                print(f"모니터링 루프 오류: {e}")
                await asyncio.sleep(1)
                
    async def stop(self):
        """중지 및 정리"""
        self.running = False
        if self.okx_ws:
            await self.okx_ws.close()
        if self.bybit_ws:
            await self.bybit_ws.close()

실행

if __name__ == "__main__": monitor = UnifiedMarketMonitor() try: asyncio.run(monitor.monitor_loop()) except KeyboardInterrupt: asyncio.run(monitor.stop()) print("모니터링 종료")

HolySheep AI와 통합: 시세 기반 AI 분석

실시간 시세를 수집한 후 AI 모델로 분석하고 거래 신호를 생성할 수 있습니다. HolySheep AI를 사용하면 단일 API 키로 다양한 모델을 활용할 수 있습니다:

# ai_market_analyzer.py
import asyncio
import json
from openai import AsyncOpenAI
from datetime import datetime

class AIMarketAnalyzer:
    """HolySheep AI를 활용한 시세 분석기"""
    
    def __init__(self, api_key: str, base_url: str = "https://api.holysheep.ai/v1"):
        self.client = AsyncOpenAI(
            api_key=api_key,
            base_url=base_url
        )
        self.model = "gpt-4.1"  # HolySheep에서 사용 가능한 모델
        
    async def analyze_price_alert(self, symbol: str, price_data: dict) -> str:
        """시세 급변 알림 분석"""
        
        prompt = f"""
        다음 {symbol} 시세 데이터를 분석하고 거래 신호를 제공하세요:
        
        - 현재가: ${price_data['last_price']}
        - 매수호가: ${price_data['bid_price']}
        - 매도호가: ${price_data['ask_price']}
        - 24시간 거래량: {price_data['volume_24h']}
        - 거래소: {price_data['exchange']}
        
        분석 항목:
        1. 현재 시장 상황 (과열/보합/하락)
        2. 단기 거래 신호 (매수/매도/관망)
        3. 주의사항
        """
        
        response = await self.client.chat.completions.create(
            model=self.model,
            messages=[
                {"role": "system", "content": "당신은 전문 암호화폐 거래 분석가입니다."},
                {"role": "user", "content": prompt}
            ],
            temperature=0.3,
            max_tokens=500
        )
        
        return response.choices[0].message.content
        
    async def batch_analyze_markets(self, market_data: list[dict]) -> dict:
        """다중 시장 일괄 분석"""
        
        # Claude 모델로 복잡한 분석 수행
        analysis_prompt = f"""
        다음은 {len(market_data)}개 암호화폐의 현재 시장 데이터입니다:
        
        {json.dumps(market_data, indent=2)}
        
        다음 형식으로 분석 결과를 JSON으로 반환하세요:
        {{
            "summary": "전체 시장 요약 (2-3문장)",
            "opportunities": ["투자 기회 1", "투자 기회 2"],
            "risks": ["위험 요소 1", "위험 요소 2"],
            "recommended_action": "현재 권장 행동"
        }}
        """
        
        response = await self.client.chat.completions.create(
            model="claude-sonnet-4-20250514",  # HolySheep에서 Claude 사용
            messages=[
                {"role": "user", "content": analysis_prompt}
            ],
            response_format={"type": "json_object"},
            temperature=0.2
        )
        
        return json.loads(response.choices[0].message.content)

HolySheep AI 사용 예시

async def main(): # HolySheep AI API 키 설정 # https://www.holysheep.ai/register 에서 가입 후 API 키 발급 analyzer = AIMarketAnalyzer( api_key="YOUR_HOLYSHEEP_API_KEY", # HolySheep API 키 base_url="https://api.holysheep.ai/v1" ) # 단일 시세 분석 btc_data = { "symbol": "BTCUSDT", "last_price": 67450.25, "bid_price": 67445.00, "ask_price": 67455.50, "volume_24h": 45678.12, "exchange": "OKX" } print("=== BTC 시세 분석 요청 ===") analysis = await analyzer.analyze_price_alert("BTCUSDT", btc_data) print(analysis) # 일괄 시장 분석 markets = [ {"symbol": "BTCUSDT", "price": 67450, "change_24h": 2.5}, {"symbol": "ETHUSDT", "price": 3520, "change_24h": 3.2}, {"symbol": "SOLUSDT", "price": 145.50, "change_24h": 5.1} ] print("\n=== 일괄 시장 분석 ===") batch_result = await analyzer.batch_analyze_markets(markets) print(json.dumps(batch_result, indent=2, ensure_ascii=False)) if __name__ == "__main__": asyncio.run(main())

HolySheep AI 모델별 비용 비교

모델 입력 비용 ($/1M 토큰) 출력 비용 ($/1M 토큰) 적합한 용도 평균 지연 시간
GPT-4.1 $8.00 $32.00 복잡한 분석, 코드 생성 2,500ms
Claude Sonnet 4.5 $15.00 $75.00 장문 분석, 문서 작성 3,000ms
Gemini 2.5 Flash $2.50 $10.00 빠른 실시간 분석 800ms
DeepSeek V3.2 $0.42 $1.68 대량 데이터 처리 1,200ms
o4-mini $3.00 $12.00 다단계 추론 1,800ms

이런 팀에 적합 / 비적합

✅ 이런 팀에 적합

❌ 이런 팀에는 비적합

가격과 ROI

OKX 및 Bybit 시세 연동 시스템을 HolySheep AI와 함께 사용할 때의 비용 구조를 분석해보겠습니다:

구성 요소 월 비용 (추정) 비고
OKX API (무료 티어) $0 초당 20회 요청, 웹소켓 무료
Bybit API (무료 티어) $0 초당 10회 요청, 웹소켓 무료
HolySheep AI - Gemini Flash $15~50 일 10,000회 분석 × 500 토큰
HolySheep AI - DeepSeek V3.2 $5~20 대량 데이터 처리용
서버 비용 (AWS/GCP) $20~50 t3.medium 이상 권장
총 월 비용 $25~120 규모에 따라 차등

ROI 사례: 일평균 $100 거래량을 처리하는 봇에서 거래소 간 시세 차익(아비트라지)을 활용하면 월 $200~500 추가 수익이 가능하며, HolySheep AI 분석 비용을 상쇄하고도 흑자가 됩니다.

왜 HolySheep를 선택해야 하나

암호화폐 거래소 API 연동 프로젝트에서 HolySheep AI를 선택해야 하는 5가지 이유:

  1. 단일 API 키로 다중 모델 활용: Gemini Flash로 빠른 분석, GPT-4.1으로 복잡한 분석, DeepSeek로 대량 처리 - 하나의 API 키로 모두 가능
  2. 한국 국내 결제 지원: 해외 신용카드 없이 로컬 결제 가능 - 개발자 친화적
  3. 글로벌 최단 지연: HolySheep 인프라를 통해 Asia-Pacific 서버 최적화 - 평균 800ms 이내 응답
  4. 투명한 가격 정책: GPT-4.1 $8/MTok · Claude Sonnet $15/MTok · Gemini Flash $2.50/MTok · DeepSeek $0.42/MTok
  5. 무료 크레딧 제공: 지금 가입 시 초기 크레딧 지급 - 즉시 테스트 가능

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

1. 웹소켓 연결 끊김 (Connection closed unexpectedly)

# 오류 메시지
websockets.exceptions.ConnectionClosed: WebSocket connection is closed

원인

- 거래소 서버 사이드 타임아웃 - 네트워크 불안정 - 서버 재시작

해결 코드

class RobustWebSocket: def __init__(self, url, ping_interval=20): self.url = url self.ping_interval = ping_interval self.ws = None self.reconnect_delay = 1 self.max_delay = 60 async def connect_with_retry(self): while True: try: self.ws = await websockets.connect( self.url, ping_interval=self.ping_interval, close_timeout=10 ) self.reconnect_delay = 1 # 재연결 시 지연 초기화 return True except Exception as e: print(f"연결 실패: {e}, {self.reconnect_delay}초 후 재시도") await asyncio.sleep(self.reconnect_delay) self.reconnect_delay = min(self.reconnect_delay * 2, self.max_delay)

2. 구독 실패 (Subscription failed: code 60001)

# 오류 메시지
{"event": "error", "msg": "Illegal request", "code": "60001"}

원인

- 잘못된 채널명 형식 - 지원하지 않는 심볼 - 채널 타입 불일치

해결 코드

OKX 올바른 구독 형식

okx_subscribe = { "op": "subscribe", "args": [{ "channel": "tickers", # 채널명 정확히 "instId": "BTC-USDT-SWAP" # 심볼 형식 확인 }] }

Bybit 올바른 구독 형식

bybit_subscribe = { "op": "subscribe", "args": ["tickers.BTCUSDT"] # 채널.심볼 형식 }

채널별 지원 심볼 확인

OKX: https://www.okx.com/docs-v5/trade-rest-api

Bybit: https://bybit-exchange.github.io/docs/v5/ws/connect

3. 요청 제한 초과 (Rate limit exceeded)

# 오류 메시지
{"code": "50002", "msg": "Too many requests"}

원인

- 초당 요청 수 초과 - 너무 빈번한 재연결 - 대량 구독 채널

해결 코드

import asyncio from collections import deque import time class RateLimitedClient: def __init__(self, max_requests_per_second=10): self.max_rps = max_requests_per_second self.request_times = deque() async def throttled_request(self, request_func): now = time.time() # 1초 이내 요청 제거 while self.request_times and self.request_times[0] < now - 1: self.request_times.popleft() if len(self.request_times) >= self.max_rps: sleep_time = 1 - (now - self.request_times[0]) await asyncio.sleep(max(0, sleep_time)) self.request_times.append