실시간 차트 오류수정
This commit is contained in:
+10
-2
@@ -983,6 +983,11 @@ function App() {
|
||||
const barsMarket = useUpbit ? upbitBarsMarket : symbol;
|
||||
barsMarketRef.current = barsMarket;
|
||||
|
||||
/** 종목·타임프레임 전환 시 이전 틱 버퍼 폐기 */
|
||||
useEffect(() => {
|
||||
pendingRealtimeBarRef.current = null;
|
||||
}, [symbol, timeframe]);
|
||||
|
||||
// Upbit 초기 로딩 완료 시 stats 업데이트
|
||||
useEffect(() => {
|
||||
if (useUpbit && upbitBars.length > 0) {
|
||||
@@ -1092,8 +1097,11 @@ function App() {
|
||||
noAutoSave: !dbLoaded,
|
||||
});
|
||||
|
||||
/** 로그인·로그아웃 시에만 차트 재마운트 (boot/ready·심볼 변경은 TradingChart effect로 처리) */
|
||||
const chartMountKey = useMemo(() => String(sessionKey), [sessionKey]);
|
||||
/** 로그인·로그아웃·종목·타임프레임 변경 시 차트 재마운트 (종목 전환 직후 stale 데이터·중단된 reload 방지) */
|
||||
const chartMountKey = useMemo(
|
||||
() => `${sessionKey}:${symbol}:${timeframe}`,
|
||||
[sessionKey, symbol, timeframe],
|
||||
);
|
||||
|
||||
// DB에 워크스페이스 없음(204) → appDefaults 레이아웃 적용 후 저장 허용
|
||||
useEffect(() => {
|
||||
|
||||
@@ -380,6 +380,10 @@ const ChartSlot = forwardRef<ChartSlotHandle, ChartSlotProps>(function ChartSlot
|
||||
// 데이터 로딩 완료 후 차트가 여전히 비어 있으면 1회만 재마운트 (data + blank guard)
|
||||
const [reloadTick, setReloadTick] = useState(0);
|
||||
const reloadTriggeredRef = useRef(false); // 재마운트는 최대 1회
|
||||
useEffect(() => {
|
||||
reloadTriggeredRef.current = false;
|
||||
pendingRealtimeBarRef.current = null;
|
||||
}, [symbol, timeframe]);
|
||||
useEffect(() => {
|
||||
const checks = [200, 500, 1000, 2000].map(delay =>
|
||||
setTimeout(() => {
|
||||
@@ -626,7 +630,7 @@ const ChartSlot = forwardRef<ChartSlotHandle, ChartSlotProps>(function ChartSlot
|
||||
)}
|
||||
|
||||
<TradingChart
|
||||
key={reloadTick}
|
||||
key={`${symbol}:${timeframe}:${reloadTick}`}
|
||||
bars={bars}
|
||||
barsMarket={barsMarket}
|
||||
market={symbol}
|
||||
|
||||
@@ -4,6 +4,9 @@ import type { OHLCVBar, ChartType, Theme, IndicatorConfig, LegendData, Drawing,
|
||||
import { ChartManager } from '../utils/ChartManager';
|
||||
import { setIndicatorChartContext } from '../utils/indicatorRegistry';
|
||||
import { DISPLAY_COUNT } from '../hooks/useUpbitData';
|
||||
|
||||
/** Upbit 실시간 — 초기 히스토리가 이 개수 미만이면 setData 하지 않음 (종목 전환 직후 1~2봉만 로드되는 깨짐 방지) */
|
||||
const MIN_BARS_FOR_FULL_RELOAD = Math.min(50, Math.floor(DISPLAY_COUNT / 4));
|
||||
import DrawingCanvas, { hitTestDrawing } from './DrawingCanvas';
|
||||
import PaneLegend, { type PaneLegendProps } from './PaneLegend';
|
||||
import ChartHoverToolbar from './ChartHoverToolbar';
|
||||
@@ -274,6 +277,25 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
inds: IndicatorConfig[],
|
||||
) => Promise<void>>(async () => {});
|
||||
|
||||
const queueFullReload = useCallback(() => {
|
||||
if (recoveryTimerRef.current) clearTimeout(recoveryTimerRef.current);
|
||||
recoveryTimerRef.current = window.setTimeout(() => {
|
||||
recoveryTimerRef.current = null;
|
||||
const m = managerRef.current;
|
||||
const barData = barsRef.current;
|
||||
const inds = indicatorsRef.current;
|
||||
if (!m || barData.length === 0) return;
|
||||
if (barsMarket !== undefined && barsMarket !== market) return;
|
||||
if (market.startsWith('KRW-') && barData.length < MIN_BARS_FOR_FULL_RELOAD) return;
|
||||
prevBarsKey.current = '';
|
||||
reloadAllRef.current?.(
|
||||
m, barData,
|
||||
prevChartType.current, prevTheme.current, prevLogScale.current,
|
||||
inds,
|
||||
);
|
||||
}, 80);
|
||||
}, [market, barsMarket]);
|
||||
|
||||
const queueIndicatorRecovery = useCallback(() => {
|
||||
if (recoveryTimerRef.current) clearTimeout(recoveryTimerRef.current);
|
||||
recoveryTimerRef.current = window.setTimeout(() => {
|
||||
@@ -329,7 +351,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
mgr.setData(newBars, ct, th);
|
||||
if (gen !== reloadGenRef.current) {
|
||||
prevBarsKey.current = '';
|
||||
queueIndicatorRecovery();
|
||||
queueFullReload();
|
||||
return;
|
||||
}
|
||||
mgr.setTheme(th);
|
||||
@@ -339,7 +361,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
for (const ind of inds) {
|
||||
if (gen !== reloadGenRef.current) {
|
||||
prevBarsKey.current = '';
|
||||
queueIndicatorRecovery();
|
||||
queueFullReload();
|
||||
return;
|
||||
}
|
||||
await mgr.addIndicator(ind);
|
||||
@@ -347,7 +369,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
|
||||
if (gen !== reloadGenRef.current) {
|
||||
prevBarsKey.current = '';
|
||||
queueIndicatorRecovery();
|
||||
queueFullReload();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -360,7 +382,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
reloadRetryRef.current += 1;
|
||||
await mgr.reloadIndicatorsOnly(inds);
|
||||
if (gen !== reloadGenRef.current) {
|
||||
queueIndicatorRecovery();
|
||||
queueFullReload();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -392,7 +414,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
|
||||
if (gen !== reloadGenRef.current) {
|
||||
prevBarsKey.current = '';
|
||||
queueIndicatorRecovery();
|
||||
queueFullReload();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -416,7 +438,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
if (delay === 1400) onDataLoaded?.();
|
||||
}, delay)
|
||||
);
|
||||
}, [applyPaneLayout, onDataLoaded, displayTimezone, timeframe, market, queueIndicatorRecovery]);
|
||||
}, [applyPaneLayout, onDataLoaded, displayTimezone, timeframe, market, queueFullReload]);
|
||||
|
||||
useEffect(() => {
|
||||
reloadAllRef.current = reloadAll;
|
||||
@@ -740,6 +762,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
? true
|
||||
: barsMarket === market;
|
||||
if (!barsReady) return;
|
||||
if (market.startsWith('KRW-') && bars.length < MIN_BARS_FOR_FULL_RELOAD) return;
|
||||
|
||||
// manager 준비 전 bars 가 먼저 도착한 경우(로그인 직후 등) 메인 시리즈 누락 방지
|
||||
if (!mgr.hasMainSeries()) {
|
||||
|
||||
@@ -157,6 +157,7 @@ export function useStompChartData(
|
||||
}, [market, candleType, opts.enabled]);
|
||||
|
||||
const applyCandle = useCallback((data: StompCandlePayload, displayTf: Timeframe) => {
|
||||
if (!historyReadyRef.current) return;
|
||||
const oneMin: OHLCVBar = {
|
||||
time: data.time,
|
||||
open: data.open,
|
||||
|
||||
Reference in New Issue
Block a user