실시간 차트 오류 수정
This commit is contained in:
@@ -10,6 +10,10 @@ import { getCandleStart, TF_SECONDS } from '../utils/upbitApi';
|
||||
import { DISPLAY_COUNT, WARMUP_COUNT } from './useUpbitData';
|
||||
import type { WsStatus } from './useUpbitData';
|
||||
import { API_BASE } from '../utils/backendApi';
|
||||
import { fetchUpbitCandlesCached } from '../utils/requestCache';
|
||||
|
||||
/** 초기 렌더 최소 봉 수 — 미만이면 업비트 REST 폴백 */
|
||||
const MIN_INITIAL_BARS = Math.min(50, Math.floor(DISPLAY_COUNT / 4));
|
||||
|
||||
/** 백엔드 BarBuilder 는 틱마다 1m 만 STOMP 발행 — 차트 분봉은 클라이언트에서 집계 */
|
||||
const STOMP_TICK_CANDLE_TYPE = '1m';
|
||||
@@ -100,6 +104,36 @@ async function fetchInitialBarsWithRetry(
|
||||
throw lastErr ?? new Error('history fetch failed');
|
||||
}
|
||||
|
||||
/** 백엔드 history → 부족·실패 시 업비트 REST 폴백 (종목별 Ta4j 미적재 대응) */
|
||||
async function loadInitialBars(
|
||||
market: string,
|
||||
timeframe: Timeframe,
|
||||
count: number,
|
||||
marketChanged: boolean,
|
||||
): Promise<OHLCVBar[]> {
|
||||
const candleType = timeframeToCandleType(timeframe);
|
||||
let bars: OHLCVBar[] = [];
|
||||
try {
|
||||
bars = await fetchInitialBarsWithRetry(market, candleType, count);
|
||||
} catch {
|
||||
bars = [];
|
||||
}
|
||||
if (bars.length < MIN_INITIAL_BARS) {
|
||||
try {
|
||||
const fallback = await fetchUpbitCandlesCached(
|
||||
market,
|
||||
timeframe,
|
||||
Math.min(count, 200),
|
||||
marketChanged,
|
||||
);
|
||||
if (fallback.length > bars.length) bars = fallback;
|
||||
} catch {
|
||||
/* 폴백 실패 시 backend 결과 유지 */
|
||||
}
|
||||
}
|
||||
return bars;
|
||||
}
|
||||
|
||||
export function useStompChartData(
|
||||
market: string,
|
||||
timeframe: Timeframe,
|
||||
@@ -122,11 +156,15 @@ export function useStompChartData(
|
||||
const last1mTimeRef = useRef<number>(0);
|
||||
/** 초기 히스토리 로드 완료 전에는 차트 직접 갱신 콜백을 호출하지 않음 */
|
||||
const historyReadyRef = useRef(false);
|
||||
/** 백엔드 history 비어 있을 때 STOMP 누적 → React bars 동기화 */
|
||||
const bootstrapFromStompRef = useRef(false);
|
||||
const prevMarketRef = useRef('');
|
||||
|
||||
useEffect(() => {
|
||||
if (opts.enabled === false) return;
|
||||
let cancelled = false;
|
||||
historyReadyRef.current = false;
|
||||
bootstrapFromStompRef.current = false;
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
setBars([]);
|
||||
@@ -136,16 +174,26 @@ export function useStompChartData(
|
||||
last1mTimeRef.current = 0;
|
||||
lastTimeRef.current = 0;
|
||||
|
||||
const marketChanged = prevMarketRef.current !== '' && prevMarketRef.current !== market;
|
||||
prevMarketRef.current = market;
|
||||
|
||||
const count = DISPLAY_COUNT + WARMUP_COUNT;
|
||||
fetchInitialBarsWithRetry(market, candleType, count)
|
||||
loadInitialBars(market, timeframe, count, marketChanged)
|
||||
.then(newBars => {
|
||||
if (cancelled) return;
|
||||
barsRef.current = newBars;
|
||||
historyReadyRef.current = true;
|
||||
setBarsMarket(market);
|
||||
setBars(newBars);
|
||||
setLatestBar(newBars[newBars.length - 1] ?? null);
|
||||
lastTimeRef.current = newBars[newBars.length - 1]?.time ?? 0;
|
||||
if (newBars.length >= 2) {
|
||||
bootstrapFromStompRef.current = false;
|
||||
setBars(newBars);
|
||||
setLatestBar(newBars[newBars.length - 1] ?? null);
|
||||
lastTimeRef.current = newBars[newBars.length - 1]?.time ?? 0;
|
||||
} else {
|
||||
bootstrapFromStompRef.current = true;
|
||||
setBars([]);
|
||||
setLatestBar(null);
|
||||
}
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch(err => {
|
||||
@@ -154,7 +202,15 @@ export function useStompChartData(
|
||||
setIsLoading(false);
|
||||
});
|
||||
return () => { cancelled = true; };
|
||||
}, [market, candleType, opts.enabled]);
|
||||
}, [market, candleType, timeframe, opts.enabled]);
|
||||
|
||||
const syncBarsToReactIfNeeded = useCallback((marketId: string) => {
|
||||
if (!bootstrapFromStompRef.current) return;
|
||||
if (barsRef.current.length < 2) return;
|
||||
bootstrapFromStompRef.current = false;
|
||||
setBarsMarket(marketId);
|
||||
setBars([...barsRef.current]);
|
||||
}, []);
|
||||
|
||||
const applyCandle = useCallback((data: StompCandlePayload, displayTf: Timeframe) => {
|
||||
if (!historyReadyRef.current) return;
|
||||
@@ -193,6 +249,7 @@ export function useStompChartData(
|
||||
if (notifyChart) optsRef.current.onTickUpdate(patched);
|
||||
}
|
||||
lastTimeRef.current = bar.time;
|
||||
syncBarsToReactIfNeeded(market);
|
||||
};
|
||||
|
||||
const lastChart = barsRef.current[barsRef.current.length - 1];
|
||||
@@ -256,7 +313,7 @@ export function useStompChartData(
|
||||
last1mTimeRef.current = oneMin.time;
|
||||
last1mVolRef.current = oneMin.volume;
|
||||
}
|
||||
}, []);
|
||||
}, [market, syncBarsToReactIfNeeded]);
|
||||
|
||||
useEffect(() => {
|
||||
if (opts.enabled === false) {
|
||||
|
||||
Reference in New Issue
Block a user