/** * KRW ticker 조회 — 백엔드 /api/markets/tickers 우선 (1회·캐시), 실패 시 /upbit-api 폴백 */ import { API_BASE } from './backendApi'; import { UPBIT_API } from './upbitApi'; export interface UpbitTickerRaw { market?: string; code?: string; trade_price: number; signed_change_rate: number; signed_change_price: number; acc_trade_price_24h: number; acc_trade_volume_24h?: number; opening_price: number; high_price: number; low_price: number; change?: 'RISE' | 'FALL' | 'EVEN'; } const CHUNK_SIZE = 40; const CHUNK_DELAY_MS = 200; const MAX_CHUNK_ATTEMPTS = 4; function sleep(ms: number): Promise { return new Promise(r => setTimeout(r, ms)); } /** 백엔드 일괄 ticker (권장) */ async function fetchTickersFromBackend(markets: string[]): Promise { try { const qs = markets.length > 0 ? `?markets=${encodeURIComponent(markets.join(','))}` : ''; const res = await fetch(`${API_BASE}/markets/tickers${qs}`); if (res.status === 429) { await sleep(1500); const retry = await fetch(`${API_BASE}/markets/tickers${qs}`); if (!retry.ok) return null; const data = (await retry.json()) as UpbitTickerRaw[]; return Array.isArray(data) ? data : null; } if (!res.ok) return null; const data = (await res.json()) as UpbitTickerRaw[]; return Array.isArray(data) ? data : null; } catch { return null; } } /** 레거시: nginx /upbit-api 직접 (청크·지연) */ async function fetchTickersFromUpbitDirect(markets: string[]): Promise { const unique = [...new Set(markets.filter(m => m?.startsWith('KRW-')))]; if (unique.length === 0) return []; const results: UpbitTickerRaw[] = []; for (let i = 0; i < unique.length; i += CHUNK_SIZE) { const chunk = unique.slice(i, i + CHUNK_SIZE); let done = false; for (let attempt = 0; attempt < MAX_CHUNK_ATTEMPTS && !done; attempt++) { try { const res = await fetch(`${UPBIT_API}/ticker?markets=${chunk.join(',')}`); if (res.status === 429) { await sleep(800 * (attempt + 1)); continue; } if (res.ok) { const data = (await res.json()) as UpbitTickerRaw[]; if (Array.isArray(data)) results.push(...data); } done = true; } catch { await sleep(400 * (attempt + 1)); } } if (i + CHUNK_SIZE < unique.length) await sleep(CHUNK_DELAY_MS); } return results; } /** * ticker 조회 — 전 종목은 백엔드 1회, 소량은 markets 쿼리 */ export async function fetchTickersThrottled(markets: string[]): Promise { const unique = [...new Set(markets.filter(m => m?.startsWith('KRW-')))]; const fromBackend = await fetchTickersFromBackend(unique); if (fromBackend != null && fromBackend.length > 0) return fromBackend; if (unique.length === 0) { const all = await fetchTickersFromBackend([]); if (all != null && all.length > 0) return all; } return fetchTickersFromUpbitDirect(unique); }