/** * 업비트 최근 체결 (호가창 좌측 체결 목록용) */ import { useState, useEffect, useRef, useCallback } from 'react'; import { subscribeUpbitWs } from '../utils/upbitWsBroker'; export interface RecentTrade { price: number; volume: number; side: 'ask' | 'bid'; time: number; } const MAX_TRADES_DEFAULT = 12; interface UpbitTradeRaw { market: string; trade_price: number; trade_volume: number; ask_bid: 'ASK' | 'BID'; timestamp: number; } interface UpbitTradeWs { type: string; code: string; trade_price: number; trade_volume: number; ask_bid: 'ASK' | 'BID'; timestamp: number; } export function useUpbitRecentTrades(market: string, enabled = true, maxTrades = MAX_TRADES_DEFAULT) { const [trades, setTrades] = useState([]); const [strength, setStrength] = useState(null); const maxRef = useRef(maxTrades); maxRef.current = maxTrades; const mountedRef = useRef(true); const marketRef = useRef(market); marketRef.current = market; const pushTrade = useCallback((t: RecentTrade) => { setTrades(prev => { const cap = maxRef.current; const next = [t, ...prev].slice(0, cap); const buyVol = next.filter(x => x.side === 'bid').reduce((s, x) => s + x.volume, 0); const sellVol = next.filter(x => x.side === 'ask').reduce((s, x) => s + x.volume, 0); if (sellVol > 0) setStrength((buyVol / sellVol) * 100); else if (buyVol > 0) setStrength(100); return next; }); }, []); const fetchSnapshot = useCallback(async () => { try { const res = await fetch(`/upbit-api/v1/trades/ticks?market=${marketRef.current}&count=${maxRef.current}`); if (!res.ok) return; const data: UpbitTradeRaw[] = await res.json(); const list: RecentTrade[] = data.map(d => ({ price: d.trade_price, volume: d.trade_volume, side: d.ask_bid === 'ASK' ? 'ask' : 'bid', time: d.timestamp, })); setTrades(list); const buyVol = list.filter(x => x.side === 'bid').reduce((s, x) => s + x.volume, 0); const sellVol = list.filter(x => x.side === 'ask').reduce((s, x) => s + x.volume, 0); if (sellVol > 0) setStrength((buyVol / sellVol) * 100); } catch { /* ignore */ } }, []); useEffect(() => { mountedRef.current = true; setTrades([]); setStrength(null); if (!enabled) { return () => { mountedRef.current = false; }; } fetchSnapshot(); const unsub = subscribeUpbitWs('trade', [market], raw => { if (!mountedRef.current) return; const msg = raw as unknown as UpbitTradeWs; if (msg.type === 'trade' && msg.code === marketRef.current) { pushTrade({ price: msg.trade_price, volume: msg.trade_volume, side: msg.ask_bid === 'ASK' ? 'ask' : 'bid', time: msg.timestamp, }); } }); return () => { mountedRef.current = false; unsub(); }; }, [market, enabled, fetchSnapshot, pushTrade]); return { trades, strength }; }