97 lines
3.0 KiB
TypeScript
97 lines
3.0 KiB
TypeScript
/**
|
|
* 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<void> {
|
|
return new Promise(r => setTimeout(r, ms));
|
|
}
|
|
|
|
/** 백엔드 일괄 ticker (권장) */
|
|
async function fetchTickersFromBackend(markets: string[]): Promise<UpbitTickerRaw[] | null> {
|
|
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<UpbitTickerRaw[]> {
|
|
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<UpbitTickerRaw[]> {
|
|
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);
|
|
}
|