Files
goldenChart/frontend/src/hooks/useUpbitRecentTrades.ts
T
2026-06-14 01:03:51 +09:00

105 lines
3.0 KiB
TypeScript

/**
* 업비트 최근 체결 (호가창 좌측 체결 목록용)
*/
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<RecentTrade[]>([]);
const [strength, setStrength] = useState<number | null>(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 };
}