goldenChat base source add
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* useLiveStrategyMarkers
|
||||
*
|
||||
* STOMP WebSocket 으로 /sub/charts/{market}/{candleType} 구독:
|
||||
* - signal 필드(BUY/SELL) 수신 시 마커 누적 (종목별)
|
||||
* - 동일 종목·타임스탬프·시그널 중복 방지
|
||||
* - activeMarket 차트에만 마커 렌더링, 알림 팝업은 모든 모니터링 종목에 대해 발생
|
||||
*/
|
||||
import { useEffect, useRef, useCallback, useState } from 'react';
|
||||
import { parseStompJson } from '../utils/stompMessage';
|
||||
import { subscribeStompTopic } from '../utils/stompChartBroker';
|
||||
|
||||
export interface LiveMarker {
|
||||
time: number;
|
||||
signal: 'BUY' | 'SELL';
|
||||
price: number;
|
||||
market: string;
|
||||
}
|
||||
|
||||
export interface MarketCandleSub {
|
||||
market: string;
|
||||
candleType: string;
|
||||
}
|
||||
|
||||
interface UseLiveStrategyMarkersOptions {
|
||||
/** 실시간 체크 ON 인 종목 목록 */
|
||||
markets: string[];
|
||||
/** 종목별 STOMP 분봉 (없으면 candleType 기본값 사용) */
|
||||
subscriptions?: MarketCandleSub[];
|
||||
activeMarket: string;
|
||||
candleType?: string;
|
||||
enabled: boolean;
|
||||
onMarkersChange: (markers: LiveMarker[]) => void;
|
||||
onSignal?: (marker: LiveMarker) => void;
|
||||
}
|
||||
|
||||
export function useLiveStrategyMarkers({
|
||||
markets,
|
||||
subscriptions,
|
||||
activeMarket,
|
||||
candleType = '1m',
|
||||
enabled,
|
||||
onMarkersChange,
|
||||
onSignal,
|
||||
}: UseLiveStrategyMarkersOptions) {
|
||||
const [liveMarkers, setLiveMarkers] = useState<LiveMarker[]>([]);
|
||||
|
||||
const markersByMarketRef = useRef<Record<string, LiveMarker[]>>({});
|
||||
const seenRef = useRef<Set<string>>(new Set());
|
||||
|
||||
const onMarkersChangeRef = useRef(onMarkersChange);
|
||||
onMarkersChangeRef.current = onMarkersChange;
|
||||
|
||||
const onSignalRef = useRef(onSignal);
|
||||
onSignalRef.current = onSignal;
|
||||
|
||||
const pushMarker = useCallback((market: string, data: {
|
||||
time: number; close: number; signal: string;
|
||||
}) => {
|
||||
if (data.signal !== 'BUY' && data.signal !== 'SELL') return;
|
||||
|
||||
const key = `${market}:${data.time}:${data.signal}`;
|
||||
if (seenRef.current.has(key)) return;
|
||||
seenRef.current.add(key);
|
||||
|
||||
const newMarker: LiveMarker = {
|
||||
time: data.time,
|
||||
signal: data.signal as 'BUY' | 'SELL',
|
||||
price: data.close,
|
||||
market,
|
||||
};
|
||||
|
||||
const prev = markersByMarketRef.current[market] ?? [];
|
||||
markersByMarketRef.current[market] = [...prev, newMarker];
|
||||
|
||||
if (market === activeMarket) {
|
||||
const active = markersByMarketRef.current[activeMarket] ?? [];
|
||||
setLiveMarkers(active);
|
||||
onMarkersChangeRef.current(active);
|
||||
}
|
||||
|
||||
onSignalRef.current?.(newMarker);
|
||||
}, [activeMarket]);
|
||||
|
||||
const clearMarkers = useCallback(() => {
|
||||
seenRef.current.clear();
|
||||
markersByMarketRef.current = {};
|
||||
setLiveMarkers([]);
|
||||
onMarkersChangeRef.current([]);
|
||||
}, []);
|
||||
|
||||
/** 활성 차트 종목 변경 시 해당 종목 마커만 차트에 반영 */
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
const active = markersByMarketRef.current[activeMarket] ?? [];
|
||||
setLiveMarkers(active);
|
||||
onMarkersChangeRef.current(active);
|
||||
}, [activeMarket, enabled]);
|
||||
|
||||
const marketsKey = markets.join(',');
|
||||
const subsKey = subscriptions?.map(s => `${s.market}:${s.candleType}`).join(',') ?? '';
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled || markets.length === 0) {
|
||||
clearMarkers();
|
||||
return;
|
||||
}
|
||||
|
||||
const subs: MarketCandleSub[] = subscriptions?.length
|
||||
? subscriptions
|
||||
: markets.map(m => ({ market: m, candleType }));
|
||||
|
||||
const unsubs = subs.map(({ market, candleType: ct }) => {
|
||||
const topic = `/sub/charts/${market}/${ct}`;
|
||||
return subscribeStompTopic(topic, msg => {
|
||||
const data = parseStompJson<{
|
||||
time: number;
|
||||
close: number;
|
||||
signal?: string;
|
||||
}>(msg);
|
||||
if (!data?.signal || data.signal === 'NONE') return;
|
||||
pushMarker(market, { time: data.time, close: data.close, signal: data.signal });
|
||||
});
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubs.forEach(u => u());
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [marketsKey, subsKey, candleType, enabled]);
|
||||
|
||||
return { liveMarkers, clearMarkers };
|
||||
}
|
||||
Reference in New Issue
Block a user