암호화폐 거래소를 위한 실시간 시세 데이터 연동은 고빈도 트레이딩 시스템의 핵심입니다. 저는 최근 OKX 거래소의 주문서 데이터를 HolySheep AI 게이트웨이를 통해 실시간으로 수집·분석하는 시스템을 구축했습니다. 이번 튜토리얼에서는 OKX API의 마켓 데이터 구조부터 WebSocket 실시간 구독, 그리고 AI 기반 주문서 분석까지 전 과정을 다룹니다.

주문서 데이터란 무엇인가

주문서(Order Book)는 특정 자산에 대한 미체결 매수/매도 주문을 가격별로 정리한 데이터입니다. 각 가격 수준에서 대기 중인 수량(Bid: 매수, Ask: 매도)을 실시간으로 확인할 수 있으며, 시장의 현재供需 균형과 잠재적 지지·저항선을 파악하는 데 필수적인 데이터입니다.

OKX는 최대 400depth까지 주문서 데이터를 제공하며, REST API와 WebSocket 두 가지 방식으로 접근 가능합니다. HolySheep AI를 통해 단일 API 키로 다양한 AI 모델을 결합하면, 주문서 데이터에서 이상 패턴을 탐지하거나 거래 신호를 생성하는 시스템을 구축할 수 있습니다.

OKX API 기본 설정

OKX API 사용을 위해서는 먼저 API 키를 발급받아야 합니다. OKX 웹사이트의 API 관리 페이지에서 키를 생성하고, 필요한 권한(읽기 전용 또는 거래)을 설정합니다. 본 튜토리얼에서는 공개 마켓 데이터만 사용하므로 읽기 권한만 필요합니다.

REST API로 주문서 조회하기

// Node.js - OKX REST API로 주문서 데이터 조회
const axios = require('axios');

class OKXOrderBookClient {
    constructor() {
        this.baseUrl = 'https://www.okx.com';
    }

    async getOrderBook(instId = 'BTC-USDT-SWAP', sz = 20) {
        try {
            const response = await axios.get(${this.baseUrl}/api/v5/market/books, {
                params: {
                    instId,
                    sz
                }
            });

            const data = response.data.data[0];
            
            // 매수 주문서 (Bid) - 가격, 수량, 체결량
            const bids = data.bids.map(bid => ({
                price: parseFloat(bid[0]),
                size: parseFloat(bid[1]),
                volume: parseFloat(bid[2])
            }));

            // 매도 주문서 (Ask) - 가격, 수량, 체결량
            const asks = data.asks.map(ask => ({
                price: parseFloat(ask[0]),
                size: parseFloat(ask[1]),
                volume: parseFloat(ask[2])
            }));

            return {
                timestamp: parseInt(data.ts),
                instrumentId: data.instId,
                lastPrice: parseFloat(data.last),
                bids,
                asks,
                // 스프레드 분석
                spread: asks[0].price - bids[0].price,
                spreadPercent: ((asks[0].price - bids[0].price) / asks[0].price * 100).toFixed(4)
            };
        } catch (error) {
            console.error('주문서 조회 실패:', error.message);
            throw error;
        }
    }

    // 전체 시장 티켓 조회
    async getTickers() {
        const response = await axios.get(${this.baseUrl}/api/v5/market/tickers, {
            params: { instType: 'SWAP' }
        });
        return response.data.data;
    }
}

// 사용 예시
async function main() {
    const client = new OKXOrderBookClient();
    
    // BTC/USDT永续期货 주문서 조회
    const orderBook = await client.getOrderBook('BTC-USDT-SWAP', 50);
    
    console.log([${new Date(orderBook.timestamp).toISOString()}]);
    console.log(현재가: $${orderBook.lastPrice});
    console.log(스프레드: $${orderBook.spread} (${orderBook.spreadPercent}%));
    console.log('\n매수 Top 5:');
    orderBook.bids.slice(0, 5).forEach((bid, i) => {
        console.log(  ${i+1}. $${bid.price} | 수량: ${bid.size} BTC);
    });
    console.log('\n매도 Top 5:');
    orderBook.asks.slice(0, 5).forEach((ask, i) => {
        console.log(  ${i+1}. $${ask.price} | 수량: ${ask.size} BTC);
    });
}

main().catch(console.error);

WebSocket로 실시간 주문서 구독

// Node.js - OKX WebSocket로 실시간 주문서 스트리밍
const WebSocket = require('ws');

class OKXRealtimeOrderBook {
    constructor(apiKey = '', apiSecret = '', passphrase = '', useSandbox = false) {
        this.wsUrl = useSandbox 
            ? 'wss://wspap.okx.com:8443/ws/v5/business' 
            : 'wss://ws.okx.com:8443/ws/v5/public';
        this.ws = null;
        this.orderBookCache = new Map();
        this.subscribers = new Set();
        this.reconnectAttempts = 0;
        this.maxReconnectAttempts = 5;
    }

    connect() {
        return new Promise((resolve, reject) => {
            this.ws = new WebSocket(this.wsUrl);

            this.ws.on('open', () => {
                console.log('✅ OKX WebSocket 연결됨');
                this.reconnectAttempts = 0;
                // 기존 구독 재연결
                this.subscribers.forEach(args => this.subscribe(...args));
                resolve();
            });

            this.ws.on('message', (data) => {
                const message = JSON.parse(data);
                this.handleMessage(message);
            });

            this.ws.on('error', (error) => {
                console.error('WebSocket 오류:', error.message);
                reject(error);
            });

            this.ws.on('close', () => {
                console.log('⚠️ WebSocket 연결 종료, 재연결 시도...');
                this.reconnect();
            });
        });
    }

    subscribe(instId, channel = 'books', depth = 400) {
        const args = [{ instId, channel, depth }];
        
        const subscribeMsg = {
            op: 'subscribe',
            args
        };

        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(JSON.stringify(subscribeMsg));
            console.log(📡 구독 시작: ${instId} - ${channel});
        }
        
        this.subscribers.add([instId, channel, depth]);
    }

    unsubscribe(instId, channel = 'books') {
        const unsubscribeMsg = {
            op: 'unsubscribe',
            args: [{ instId, channel }]
        };

        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(JSON.stringify(unsubscribeMsg));
        }
        
        this.subscribers.delete([instId, channel, 400]);
    }

    handleMessage(message) {
        // 구독 확인 메시지
        if (message.arg && message.arg.channel === 'books') {
            console.log(📥 주문서 데이터 수신: ${message.arg.instId});
        }

        // 주문서 데이터
        if (message.data && message.data[0]) {
            const data = message.data[0];
            const instId = message.arg.instId;
            
            // 전체 주문서 업데이트
            if (data.action === 'snapshot' || !this.orderBookCache.has(instId)) {
                this.orderBookCache.set(instId, {
                    bids: new Map(data.bids.map(b => [b[0], {
                        price: parseFloat(b[0]),
                        size: parseFloat(b[1]),
                        orders: parseInt(b[2]) || 0
                    }])),
                    asks: new Map(data.asks.map(a => [a[0], {
                        price: parseFloat(a[0]),
                        size: parseFloat(a[1]),
                        orders: parseInt(a[2]) || 0
                    }])),
                    timestamp: parseInt(data.ts)
                });
            } 
            //增量 업데이트
            else {
                const book = this.orderBookCache.get(instId);
                
                // 매수 주문 업데이트
                data.bids?.forEach(bid => {
                    const price = bid[0];
                    const size = parseFloat(bid[1]);
                    if (size === 0) {
                        book.bids.delete(price);
                    } else {
                        book.bids.set(price, {
                            price: parseFloat(price),
                            size,
                            orders: parseInt(bid[2]) || 0
                        });
                    }
                });

                // 매도 주문 업데이트
                data.asks?.forEach(ask => {
                    const price = ask[0];
                    const size = parseFloat(ask[1]);
                    if (size === 0) {
                        book.asks.delete(price);
                    } else {
                        book.asks.set(price, {
                            price: parseFloat(price),
                            size,
                            orders: parseInt(ask[2]) || 0
                        });
                    }
                });

                book.timestamp = parseInt(data.ts);
            }

            // 상위 10단계 데이터만 출력
            this.displayTopLevels(instId);
        }
    }

    displayTopLevels(instId) {
        const book = this.orderBookCache.get(instId);
        if (!book) return;

        const sortedBids = [...book.bids.values()]
            .sort((a, b) => b.price - a.price)
            .slice(0, 5);
        const sortedAsks = [...book.asks.values()]
            .sort((a, b) => a.price - b.price)
            .slice(0, 5);

        console.clear();
        console.log(=== ${instId} 실시간 주문서 ===);
        console.log(업데이트: ${new Date(book.timestamp).toISOString()});
        console.log('\n  매도 (Ask)        │        매수 (Bid)');
        console.log('  가격      수량    │    수량      가격');

        for (let i = 0; i < 5; i++) {
            const ask = sortedAsks[i] || { price: '-', size: '-' };
            const bid = sortedBids[i] || { price: '-', size: '-' };
            console.log(
                  ${String(ask.price).padStart(10)} ${String(ask.size).padStart(8)} │ ${String(bid.size).padStart(8)} ${String(bid.price).padStart(10)}
            );
        }

        // 스프레드 표시
        const bestAsk = sortedAsks[0]?.price || 0;
        const bestBid = sortedBids[0]?.price || 0;
        const spread = bestAsk - bestBid;
        const spreadPct = bestAsk > 0 ? (spread / bestAsk * 100).toFixed(4) : 0;
        console.log(\n  스프레드: $${spread.toFixed(2)} (${spreadPct}%));
    }

    async reconnect() {
        if (this.reconnectAttempts >= this.maxReconnectAttempts) {
            console.error('❌ 최대 재연결 횟수 초과');
            return;
        }

        this.reconnectAttempts++;
        const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
        
        console.log(${delay/1000}초 후 재연결 시도... (${this.reconnectAttempts}/${this.maxReconnectAttempts}));
        
        await new Promise(r => setTimeout(r, delay));
        
        try {
            await this.connect();
        } catch (error) {
            console.error('재연결 실패:', error.message);
        }
    }

    getOrderBook(instId) {
        return this.orderBookCache.get(instId);
    }

    disconnect() {
        if (this.ws) {
            this.ws.close();
            console.log('WebSocket 연결 해제됨');
        }
    }
}

// 사용 예시
async function main() {
    const client = new OKXRealtimeOrderBook();
    
    try {
        await client.connect();
        
        // BTC/USDT永续期货 주문서 구독
        client.subscribe('BTC-USDT-SWAP', 'books', 400);
        
        // 1분 후 자동 종료 (실제로는 무한 루프로 유지)
        setTimeout(() => {
            console.log('\n구독 종료 및 연결 해제');
            client.disconnect();
            process.exit(0);
        }, 60000);
        
    } catch (error) {
        console.error('연결 실패:', error.message);
    }
}

main();

HolySheep AI 게이트웨이 연동

주문서 데이터를 실시간으로 수집했다면, 다음 단계는 AI를 활용한 분석입니다. HolySheep AI 게이트웨이를 사용하면 단일 API 키로 GPT-4.1, Claude, Gemini, DeepSeek 등 다양한 모델을 주문서 분석에 활용할 수 있습니다. 특히 DeepSeek V3.2는 $0.42/MTok의 저렴한 가격으로 대량 데이터 처리에 적합합니다.

// Node.js - HolySheep AI로 주문서 데이터 AI 분석
const axios = require('axios');

// HolySheep AI 게이트웨이 설정
const HOLYSHEEP_BASE_URL = 'https://api.holysheep.ai/v1';
const HOLYSHEEP_API_KEY = 'YOUR_HOLYSHEEP_API_KEY'; // HolySheep에서 발급받은 API 키

class OrderBookAnalyzer {
    constructor(apiKey) {
        this.apiKey = apiKey;
        this.baseUrl = HOLYSHEEP_BASE_URL;
    }

    // 주문서 데이터 포맷팅
    formatOrderBookForAnalysis(orderBook) {
        const topBids = orderBook.bids.slice(0, 10);
        const topAsks = orderBook.asks.slice(0, 10);

        let formatted = '=== BTC/USDT 주문서 분석 ===\n\n';
        formatted += '매수(Bid) 주문:\n';
        topBids.forEach((bid, i) => {
            formatted +=   ${i+1}. $${bid.price.toLocaleString()} | ${bid.size} BTC\n;
        });

        formatted += '\n매도(Ask) 주문:\n';
        topAsks.forEach((ask, i) => {
            formatted +=   ${i+1}. $${ask.price.toLocaleString()} | ${ask.size} BTC\n;
        });

        // 추가 분석 데이터
        const totalBidSize = topBids.reduce((sum, b) => sum + b.size, 0);
        const totalAskSize = topAsks.reduce((sum, a) => sum + a.size, 0);
        const bidAskRatio = totalBidSize / totalAskSize;

        formatted += \n분석 요약:\n;
        formatted += - 최상위 매수: $${topBids[0].price.toLocaleString()}\n;
        formatted += - 최상위 매도: $${topAsks[0].price.toLocaleString()}\n;
        formatted += - 스프레드: $${(topAsks[0].price - topBids[0].price).toFixed(2)}\n;
        formatted += - 매수/매도 비율: ${bidAskRatio.toFixed(2)}\n;
        formatted += - 총 매수 호가 수량: ${totalBidSize.toFixed(4)} BTC\n;
        formatted += - 총 매도 호가 수량: ${totalAskSize.toFixed(4)} BTC\n;

        return formatted;
    }

    // HolySheep AI를 통한 주문서 분석
    async analyzeWithAI(orderBook) {
        const formattedData = this.formatOrderBookForAnalysis(orderBook);
        
        const prompt = `다음 암호화폐 주문서 데이터를 분석하고 거래 시그널을 제공해주세요.

${formattedData}

다음 형식으로 분석해주세요:
1. 시장 심리 (매수우위/매도우위/중립)
2. 주요 지지선과 저항선
3. 단기 거래 시그널 (매수/매도/관망)
4. 리스크 요소
5. 신뢰도 (높음/중간/낮음)`;

        try {
            // DeepSeek V3.2 사용 - 비용 효율적
            const response = await axios.post(
                ${this.baseUrl}/chat/completions,
                {
                    model: 'deepseek-chat',
                    messages: [
                        {
                            role: 'system',
                            content: '당신은 전문 암호화폐 트레이딩 분석가입니다. 주문서 데이터를 기반으로 객관적인 분석을 제공합니다.'
                        },
                        {
                            role: 'user',
                            content: prompt
                        }
                    ],
                    temperature: 0.3,
                    max_tokens: 1000
                },
                {
                    headers: {
                        'Authorization': Bearer ${this.apiKey},
                        'Content-Type': 'application/json'
                    }
                }
            );

            return {
                analysis: response.data.choices[0].message.content,
                usage: {
                    promptTokens: response.data.usage.prompt_tokens,
                    completionTokens: response.data.usage.completion_tokens,
                    totalTokens: response.data.usage.total_tokens
                },
                model: response.data.model
            };
        } catch (error) {
            console.error('AI 분석 실패:', error.response?.data || error.message);
            throw error;
        }
    }

    // Claude를 통한 고급 감성 분석
    async analyzeSentiment(orderBook) {
        const formattedData = this.formatOrderBookForAnalysis(orderBook);
        
        try {
            // Claude API 호출 (Anthropic 호환)
            const response = await axios.post(
                ${this.baseUrl}/messages,
                {
                    model: 'claude-sonnet-4-20250514',
                    max_tokens: 1024,
                    messages: [
                        {
                            role: 'user',
                            content: 거래소 주문서를 기반으로 시장 감성을 분석해주세요:\n\n${formattedData}\n\n단 3가지만 답해주세요: 1) 시장 심리 2) 주요 패턴 3) 단기 전망
                        }
                    ]
                },
                {
                    headers: {
                        'x-api-key': this.apiKey,
                        'Content-Type': 'application/json',
                        'anthropic-version': '2023-06-01'
                    }
                }
            );

            return {
                sentiment: response.data.content[0].text,
                usage: response.data.usage
            };
        } catch (error) {
            console.error('감성 분석 실패:', error.response?.data || error.message);
            throw error;
        }
    }

    // 비용 추정
    estimateCost(promptTokens, completionTokens, model = 'deepseek-chat') {
        const pricing = {
            'deepseek-chat': { input: 0.00000027, output: 0.00000107 }, // $0.27/MTok in, $1.07/MTok out
            'gpt-4.1': { input: 0.002, output: 0.008 },
            'claude-sonnet-4-20250514': { input: 0.003, output: 0.015 }
        };

        const p = pricing[model] || pricing['deepseek-chat'];
        const inputCost = (promptTokens / 1000000) * p.input * 100; // cents
        const outputCost = (completionTokens / 1000000) * p.output * 100;

        return {
            inputCents: inputCost.toFixed(4),
            outputCents: outputCost.toFixed(4),
            totalCents: (inputCost + outputCost).toFixed(4)
        };
    }
}

// 사용 예시
async function main() {
    const analyzer = new OrderBookAnalyzer(HOLYSHEEP_API_KEY);
    
    // 샘플 주문서 데이터
    const sampleOrderBook = {
        lastPrice: 67500,
        spread: 5.50,
        spreadPercent: 0.0081,
        bids: [
            { price: 67497.50, size: 2.5 },
            { price: 67495.00, size: 1.8 },
            { price: 67490.00, size: 3.2 },
            { price: 67485.00, size: 1.5 },
            { price: 67480.00, size: 4.1 },
            { price: 67475.00, size: 2.0 },
            { price: 67470.00, size: 1.2 },
            { price: 67465.00, size: 3.8 },
            { price: 67460.00, size: 2.3 },
            { price: 67455.00, size: 1.9 }
        ],
        asks: [
            { price: 67503.00, size: 1.2 },
            { price: 67505.00, size: 2.8 },
            { price: 67510.00, size: 1.5 },
            { price: 67515.00, size: 3.0 },
            { price: 67520.00, size: 2.2 },
            { price: 67525.00, size: 1.8 },
            { price: 67530.00, size: 4.5 },
            { price: 67535.00, size: 2.1 },
            { price: 67540.00, size: 1.6 },
            { price: 67545.00, size: 2.9 }
        ]
    };

    console.log('🤖 HolySheep AI 주문서 분석 시작...\n');

    // DeepSeek로 분석
    const deepseekResult = await analyzer.analyzeWithAI(sampleOrderBook);
    console.log('=== DeepSeek 분석 결과 ===');
    console.log(deepseekResult.analysis);
    console.log(\n💰 비용: ${deepseekResult.usage.totalTokens} 토큰);
    
    const cost = analyzer.estimateCost(
        deepseekResult.usage.prompt_tokens,
        deepseekResult.usage.completion_tokens,
        'deepseek-chat'
    );
    console.log(💵 예상 비용: $${cost.totalCents} (${cost.totalCents}¢));

    // Claude로 감성 분석
    console.log('\n\n=== Claude 감성 분석 ===');
    try {
        const sentimentResult = await analyzer.analyzeSentiment(sampleOrderBook);
        console.log(sentimentResult.sentiment);
    } catch (error) {
        console.log('Claude 분석은 현재 사용할 수 없습니다. 계속 진행합니다.');
    }
}

main().catch(console.error);

주문서 데이터의 실제 활용 사례

실시간 주문서 데이터는 단순한 가격 조회 그 이상입니다. 저는 최근 거래 봇 시스템 구축 프로젝트를 통해 다음과 같은 활용법을 확인했습니다:

HolySheep AI와 다른 방식의 비교

구분 직접 API 연동 별도 AI 서비스 별도 구매 HolySheep AI 게이트웨이
API 키 관리 OKX + AI 서비스 각각 최소 2개 이상 ✅ HolySheep 단일 키
결제 방식 해외 신용카드 필수 해외 신용카드 필수 ✅ 로컬 결제 지원
DeepSeek 비용 $0.42/MTok $0.42/MTok ✅ 동일 가격 + 무료 크레딧
모델 전환 코드 수정 필요 별도 연동 필요 ✅ 한 줄 변경으로 교체
latency 변동 변동 ✅ 최적화 라우팅
기술 지원 각 서비스 별도 제한적 ✅ 통합 기술 지원

이런 팀에 적합 / 비적합

✅ HolySheep AI가 적합한 경우

❌ HolySheep AI가 적합하지 않은 경우

가격과 ROI

주문서 데이터 AI 분석 시스템의 비용을 실제 사례와 함께 계산해 보겠습니다:

항목 월간 예상 사용량 DeepSeek 비용 GPT-4.1 비용
하루 분석 횟수 500회 500회 500회
월간 분석 횟수 15,000회 15,000회 15,000회
토큰/분석 (평균) 2,000 토큰 2,000 토큰 2,000 토큰
월간 토큰 사용량 30M 토큰 30M 토큰 30M 토큰
월간 비용 - $12.60 $60.00
HolySheep 무료 크레딧 가입 시 제공 $5 ~ $10 크레딧 $5 ~ $10 크레딧
실제 월간 지출 - $2.60 ~ $7.60 $50.00 ~ $55.00

ROI 분석: 월 $7.60 수준이면 전문 트레이딩 신호 서비스를 구독하는 것보다 훨씬 저렴하며, 매일 수천 번의 맞춤 분석이 가능합니다. HolySheep 가입 시 제공하는 무료 크레딧으로 실제 비용 없이도 충분히 테스트할 수 있습니다.

왜 HolySheep를 선택해야 하나

저는 여러 AI API 서비스와 게이트웨이를 사용해 보았지만, HolySheep AI가 특히 암호화폐 API 연동 프로젝트에 적합한 이유는 다음과 같습니다:

  1. 단일 API 키의 편리함: OKX API 키와 AI API 키를 분리 관리해야 하는 부담이 줄어듭니다. HolySheep 하나면 DeepSeek, GPT-4.1, Claude, Gemini 모두 사용 가능.
  2. 비용 효율성: DeepSeek V3.2의 $0.42/MTok는 대량 주문서 분석에 최적. GPT-4.1 ($8/MTok)은 정밀 분석이 필요할 때만 선택적으로 사용.
  3. 로컬 결제 지원: 해외 신용카드 없이도 원활한 결제가 가능하여, 초기 비용 부담 없이 바로 프로젝트 시작 가능.
  4. 신속한 모델 전환: 한 줄의 코드 변경으로 분석 모델을 교체할 수 있어 A/B 테스트와 성능 비교가 간편.

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

1. WebSocket 연결 끊김 (Code: 1006)

// ❌ 오류 발생 시
WebSocket connection closed unexpectedly

// ✅ 해결 방법: 자동 재연결 로직 구현
class OKXRealtimeOrderBook {
    constructor() {
        this.heartbeatInterval = null;
        this.lastPingTime = 0;
    }

    startHeartbeat() {
        // 20초마다 핑 메시지 전송
        this.heartbeatInterval = setInterval(() => {
            if (this.ws && this.ws.readyState === WebSocket.OPEN) {
                this.ws.send(JSON.stringify({
                    op: 'ping',
                    args: []
                }));
                this.lastPingTime = Date.now();
            }
        }, 20000);
    }

    handleMessage(message) {
        // 핑 응답 처리
        if (message.op === 'pong') {
            const latency = Date.now() - this.lastPingTime;
            console.log(🏓 Pong 수신, 지연시간: ${latency}ms);
            return;
        }
        
        // ... 나머지 메시지 처리
    }

    reconnect() {
        // 최대 5회, 지수 백오프 방식으로 재연결
        const maxRetries = 5;
        const baseDelay = 1000;
        
        for (let attempt = 1; attempt <= maxRetries; attempt++) {
            const delay = baseDelay * Math.pow(2, attempt - 1);
            console.log(${attempt}차 재연결 시도, ${delay}ms 후...);
            
            setTimeout(async () => {
                try {
                    await this.connect();
                    this.startHeartbeat();
                    return;
                } catch (error) {
                    console.error(재연결 실패 (${attempt}/${maxRetries}));
                }
            }, delay);
        }
        
        console.error('❌ 최대 재연결 횟수 초과. 수동 개입 필요.');
    }
}

2. API Rate Limit 초과 (HTTP 429)

// ❌ 오류 발생 시
{"code":"50108","msg":"Request too many times"}

// ✅ 해결 방법: 요청 제한 및 지수 백오프
class RateLimitedClient {
    constructor() {
        this.requestQueue = [];
        this.minRequestInterval = 100; // 최소 100ms 간격
        this.lastRequestTime = 0;
        this.maxRetries = 3;
    }

    async throttledRequest(requestFn) {
        const now = Date.now();
        const timeSinceLastRequest = now - this.lastRequestTime;
        
        if (timeSinceLastRequest < this.minRequestInterval) {
            await new Promise(r => 
                setTimeout(r, this.minRequestInterval - timeSinceLastRequest)
            );
        }

        this.lastRequestTime = Date.now();
        return this.executeWithRetry(requestFn);
    }

    async executeWithRetry(requestFn, retryCount = 0) {
        try {
            return await requestFn();
        } catch (error) {
            if (error.response?.status === 429 && retryCount < this.maxRetries) {
                // Rate limit 시 지수 백오프
                const delay = Math.min(1000 * Math.pow(2, retryCount), 30000);
                console.log(Rate limit 도달, ${delay}ms 후 재시도...);
                
                await new Promise(r => setTimeout(r, delay));
                return this.executeWithRetry(requestFn, retryCount + 1);
            }
            throw error;
        }
    }

    // 배치 처리로 Rate Limit 우회
    async batchRequest(requests, batchSize = 5) {
        const results = [];
        
        for (let i = 0; i < requests.length; i += batchSize) {
            const batch = requests.slice(i, i + batchSize);
            const batchResults = await Promise.all(
                batch.map(req => this.throttledRequest(req))
            );
            results.push(...batchResults);
            
            // 배치 간 지연
            if (i + batchSize < requests.length) {
                await new Promise(r => setTimeout(r, 200));
            }
        }
        
        return results;
    }
}

관련 리소스

관련 문서