goldenChat base source add
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 업비트 최근 체결 (호가창 좌측 체결 목록용)
|
||||
*/
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
|
||||
export interface RecentTrade {
|
||||
price: number;
|
||||
volume: number;
|
||||
side: 'ask' | 'bid'; // ask=매도체결, bid=매수체결
|
||||
time: number;
|
||||
}
|
||||
|
||||
const MAX_TRADES = 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) {
|
||||
const [trades, setTrades] = useState<RecentTrade[]>([]);
|
||||
const [strength, setStrength] = useState<number | null>(null);
|
||||
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const mountedRef = useRef(true);
|
||||
|
||||
const pushTrade = useCallback((t: RecentTrade) => {
|
||||
setTrades(prev => {
|
||||
const next = [t, ...prev].slice(0, MAX_TRADES);
|
||||
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=${market}&count=${MAX_TRADES}`);
|
||||
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 */ }
|
||||
}, [market]);
|
||||
|
||||
useEffect(() => {
|
||||
mountedRef.current = true;
|
||||
setTrades([]);
|
||||
setStrength(null);
|
||||
fetchSnapshot();
|
||||
|
||||
const proto = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
const ws = new WebSocket(`${proto}://${window.location.host}/upbit-ws`);
|
||||
ws.binaryType = 'arraybuffer';
|
||||
wsRef.current = ws;
|
||||
|
||||
ws.onopen = () => {
|
||||
ws.send(JSON.stringify([
|
||||
{ ticket: `tr-${market}-${Date.now()}` },
|
||||
{ type: 'trade', codes: [market] },
|
||||
]));
|
||||
};
|
||||
|
||||
ws.onmessage = async (e: MessageEvent) => {
|
||||
if (!mountedRef.current) return;
|
||||
try {
|
||||
let text: string;
|
||||
if (typeof e.data === 'string') text = e.data;
|
||||
else if (e.data instanceof ArrayBuffer) text = new TextDecoder().decode(e.data);
|
||||
else if (e.data instanceof Blob) text = await e.data.text();
|
||||
else return;
|
||||
const msg: UpbitTradeWs = JSON.parse(text);
|
||||
if (msg.type === 'trade' && msg.code === market) {
|
||||
pushTrade({
|
||||
price: msg.trade_price,
|
||||
volume: msg.trade_volume,
|
||||
side: msg.ask_bid === 'ASK' ? 'ask' : 'bid',
|
||||
time: msg.timestamp,
|
||||
});
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
};
|
||||
|
||||
return () => {
|
||||
mountedRef.current = false;
|
||||
if (wsRef.current) {
|
||||
wsRef.current.onopen = null;
|
||||
wsRef.current.onmessage = null;
|
||||
wsRef.current.onclose = null;
|
||||
wsRef.current.close();
|
||||
wsRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [market, fetchSnapshot, pushTrade]);
|
||||
|
||||
return { trades, strength };
|
||||
}
|
||||
Reference in New Issue
Block a user