종목변경 시 캔들차트 사라지는 현상 수정

This commit is contained in:
Macbook
2026-05-24 21:51:27 +09:00
parent af230a4233
commit 3c66c562d8
14 changed files with 288 additions and 219 deletions
+90 -16
View File
@@ -15,6 +15,8 @@ import { DEFAULT_DISPLAY_TIMEZONE } from '../utils/timezone';
interface TradingChartProps {
bars: OHLCVBar[];
/** bars 가 현재 market 과 일치할 때만 전달 (없으면 bars.length>0 이면 준비된 것으로 간주) */
barsMarket?: string | null;
/** BB 등 다른 심볼 계산용 */
market?: string;
timeframe?: Timeframe;
@@ -78,7 +80,7 @@ interface TradingChartProps {
}
const TradingChart: React.FC<TradingChartProps> = ({
bars, market = '', timeframe = '1D', chartType, theme, mode, indicators, drawingTool, drawings,
bars, barsMarket, market = '', timeframe = '1D', chartType, theme, mode, indicators, drawingTool, drawings,
logScale, drawingsLocked = false, drawingsVisible = true,
onCrosshair, onManagerReady, onAddDrawing,
onSeriesClick, onSeriesDoubleClick,
@@ -255,6 +257,8 @@ const TradingChart: React.FC<TradingChartProps> = ({
// ── 전체 재로드 (데이터 + 인디케이터) ─────────────────────────────────────
const reloadSafetyTimers = useRef<ReturnType<typeof setTimeout>[]>([]);
const reloadGenRef = useRef(0);
const reloadRetryRef = useRef(0);
const reloadAll = useCallback(async (
mgr: ChartManager,
@@ -266,10 +270,50 @@ const TradingChart: React.FC<TradingChartProps> = ({
) => {
if (newBars.length === 0) return;
const gen = ++reloadGenRef.current;
reloadSafetyTimers.current.forEach(clearTimeout);
reloadSafetyTimers.current = [];
barsRef.current = newBars;
barsRef.current = newBars;
mgr.setData(newBars, ct, th);
if (gen !== reloadGenRef.current) {
prevBarsKey.current = '';
return;
}
mgr.setTheme(th);
mgr.setLogScale(ls);
mgr.setDisplayTimezone(displayTimezone, timeframe);
for (const ind of inds) {
if (gen !== reloadGenRef.current) {
prevBarsKey.current = '';
return;
}
await mgr.addIndicator(ind);
}
if (gen !== reloadGenRef.current) {
prevBarsKey.current = '';
return;
}
const expectIndicators = inds.some(i => i.hidden !== true);
if (expectIndicators && mgr.getIndicatorCount() === 0) {
prevBarsKey.current = '';
if (reloadRetryRef.current < 3) {
reloadRetryRef.current += 1;
requestAnimationFrame(() => {
if (managerRef.current) {
void reloadAll(managerRef.current, newBars, ct, th, ls, inds);
}
});
}
return;
}
reloadRetryRef.current = 0;
prevChartType.current = ct;
prevTheme.current = th;
prevLogScale.current = ls;
@@ -277,15 +321,6 @@ const TradingChart: React.FC<TradingChartProps> = ({
prevIndKey.current = indKey(inds);
prevSortedPKRef.current = sortedParamKey(inds);
mgr.setData(newBars, ct, th);
mgr.setTheme(th);
mgr.setLogScale(ls);
mgr.setDisplayTimezone(displayTimezone, timeframe);
for (const ind of inds) {
await mgr.addIndicator(ind);
}
await new Promise<void>(resolve => requestAnimationFrame(() => requestAnimationFrame(() => {
applyPaneLayout(mgr);
requestAnimationFrame(() => {
@@ -295,6 +330,11 @@ const TradingChart: React.FC<TradingChartProps> = ({
});
})));
if (gen !== reloadGenRef.current) {
prevBarsKey.current = '';
return;
}
onDataLoaded?.();
reloadSafetyTimers.current = [300, 700, 1400].map(delay =>
@@ -611,6 +651,12 @@ const TradingChart: React.FC<TradingChartProps> = ({
prevMarket.current = market;
prevMarketTf.current = timeframe;
prevBarsKey.current = '';
prevIndKey.current = '';
reloadRetryRef.current = 0;
reloadGenRef.current += 1;
reloadSafetyTimers.current.forEach(clearTimeout);
reloadSafetyTimers.current = [];
managerRef.current?.cancelPendingIndicatorUpdates();
}, [market, timeframe]);
// ── 데이터/인디케이터 동기화 ─────────────────────────────────────────────
@@ -618,6 +664,11 @@ const TradingChart: React.FC<TradingChartProps> = ({
const mgr = managerRef.current;
if (!mgr || !chartMgr || bars.length === 0) return;
const barsReady = barsMarket === undefined
? true
: barsMarket === market;
if (!barsReady) return;
// manager 준비 전 bars 가 먼저 도착한 경우(로그인 직후 등) 메인 시리즈 누락 방지
if (!mgr.hasMainSeries()) {
void reloadAll(mgr, bars, chartType, theme, logScale, indicators);
@@ -631,12 +682,16 @@ const TradingChart: React.FC<TradingChartProps> = ({
const ctChanged = chartType !== prevChartType.current;
const thChanged = theme !== prevTheme.current;
const lsChanged = logScale !== prevLogScale.current;
const expectIndicators = indicators.some(i => i.hidden !== true);
const indicatorsIncomplete = expectIndicators
&& mgr.hasMainSeries()
&& mgr.getIndicatorCount() === 0;
if (!barsChanged && !indChanged && !ctChanged && !thChanged && !lsChanged) return;
if (!barsChanged && !indChanged && !ctChanged && !thChanged && !lsChanged && !indicatorsIncomplete) return;
if (barsChanged || ctChanged || thChanged) {
// 데이터 또는 차트 형식 변경 → 전체 재로드
reloadAll(mgr, bars, chartType, theme, logScale, indicators);
if (barsChanged || ctChanged || thChanged || indicatorsIncomplete) {
// 데이터·차트 형식 변경 또는 지표 누락 → 전체 재로드
void reloadAll(mgr, bars, chartType, theme, logScale, indicators);
} else if (lsChanged) {
prevLogScale.current = logScale;
mgr.setLogScale(logScale);
@@ -715,7 +770,26 @@ const TradingChart: React.FC<TradingChartProps> = ({
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [chartMgr, bars, chartType, theme, logScale, indicators]);
}, [chartMgr, bars, barsMarket, market, chartType, theme, logScale, indicators]);
// 종목 전환 직후 reloadAll 이 중단되어 캔들만 남은 경우 자동 복구
useEffect(() => {
const mgr = managerRef.current;
if (!mgr || !chartMgr || bars.length === 0) return;
if (barsMarket !== undefined && barsMarket !== market) return;
const expectIndicators = indicators.some(i => i.hidden !== true);
if (!expectIndicators) return;
const t = window.setTimeout(() => {
const m = managerRef.current;
if (!m || !m.hasMainSeries() || m.getIndicatorCount() > 0) return;
prevBarsKey.current = '';
void reloadAll(m, bars, chartType, theme, logScale, indicators);
}, 120);
return () => window.clearTimeout(t);
}, [chartMgr, bars, barsMarket, market, chartType, theme, logScale, indicators, reloadAll]);
// cursor 모드에서 드로잉 위에 있으면 pointer 커서로 변경
const handleWrapperMouseMove = useCallback((e: React.PointerEvent) => {