매매 시그널 알림 화면 수정

This commit is contained in:
Macbook
2026-05-29 00:46:20 +09:00
parent cbad62a5b0
commit e43b5cbd5a
45 changed files with 2186 additions and 415 deletions
+61 -4
View File
@@ -2,6 +2,7 @@ import React, { useRef, useEffect, useLayoutEffect, useState, useCallback } from
import type { MouseEventParams, Time } from 'lightweight-charts';
import type { OHLCVBar, ChartType, Theme, IndicatorConfig, LegendData, Drawing, ChartMode, Timeframe } from '../types';
import { ChartManager } from '../utils/ChartManager';
import { useChartTimeFormat } from '../utils/chartTimeFormat';
import { setIndicatorChartContext } from '../utils/indicatorRegistry';
import { DISPLAY_COUNT } from '../hooks/useUpbitData';
@@ -45,6 +46,7 @@ function canApplyChartBars(
}
import DrawingCanvas, { hitTestDrawing } from './DrawingCanvas';
import PaneLegend, { type PaneLegendProps } from './PaneLegend';
import CandlePaneTimeAxis from './CandlePaneTimeAxis';
import ChartHoverToolbar from './ChartHoverToolbar';
import ChartMagnifier from './ChartMagnifier';
import ChartContextMenu from './ChartContextMenu';
@@ -216,6 +218,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
const prevSortedPKRef = useRef<string>(''); // 순서 무관 paramKey (reorder 감지용)
const prevIndicatorsListRef = useRef<IndicatorConfig[]>(indicators);
const indicatorSyncInFlightRef = useRef(false);
const pendingIndicatorResyncRef = useRef(false);
const indicatorReloadGenRef = useRef(0);
const prevMarket = useRef<string>(market);
const prevMarketTf = useRef<Timeframe>(timeframe);
@@ -228,6 +231,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
const resizeDebounceRef = useRef<number | null>(null);
const chartVisibleRef = useRef(chartVisible);
const chartTimeFormat = useChartTimeFormat();
const [chartMgr, setChartMgr] = useState<ChartManager | null>(null);
/** 캔들 pane 전체보기 (오버레이 지표 유지, 하단 보조지표 pane 숨김) */
const [candleOnlyMode, setCandleOnlyMode] = useState(false);
@@ -456,6 +460,27 @@ const TradingChart: React.FC<TradingChartProps> = ({
});
}, [afterIndicatorPaneMutation, restoreLogicalRange]);
/** 설정 저장 등으로 연속 갱신이 겹칠 때 마지막 상태로 한 번 더 동기화 */
const flushPendingIndicatorResync = useCallback((
mgr: ChartManager,
completedGen: number,
) => {
if (!pendingIndicatorResyncRef.current) return;
if (completedGen !== indicatorReloadGenRef.current) return;
pendingIndicatorResyncRef.current = false;
const latest = indicatorsRef.current;
indicatorSyncInFlightRef.current = true;
const flushGen = ++indicatorReloadGenRef.current;
reloadIndicatorsWithCover(mgr, latest, () => {
if (flushGen !== indicatorReloadGenRef.current) {
indicatorSyncInFlightRef.current = false;
return;
}
syncIndicatorTrackingRefs(latest);
indicatorSyncInFlightRef.current = false;
});
}, [reloadIndicatorsWithCover, syncIndicatorTrackingRefs]);
const queueFullReload = useCallback(() => {
if (!chartVisibleRef.current) return;
if (recoveryTimerRef.current) clearTimeout(recoveryTimerRef.current);
@@ -573,7 +598,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
}
mgr.setTheme(th);
mgr.setLogScale(ls);
mgr.setDisplayTimezone(displayTimezone, timeframe);
mgr.setDisplayTimezone(displayTimezone, timeframe, chartTimeFormat);
commitCandleLayout(mgr);
onCandlesReady?.();
lastAppliedMarketRef.current = market;
@@ -996,8 +1021,8 @@ const TradingChart: React.FC<TradingChartProps> = ({
}, []);
useEffect(() => {
managerRef.current?.setDisplayTimezone(displayTimezone, timeframe);
}, [displayTimezone, timeframe]);
managerRef.current?.setDisplayTimezone(displayTimezone, timeframe, chartTimeFormat);
}, [displayTimezone, timeframe, chartTimeFormat]);
// 종목·타임프레임 변경 시 barsKey 캐시만 초기화 (reloadAll 은 bars effect 에서 1회만)
useEffect(() => {
@@ -1083,6 +1108,10 @@ const TradingChart: React.FC<TradingChartProps> = ({
prevLogScale.current = logScale;
mgr.setLogScale(logScale);
} else if (indChanged) {
if (indicatorSyncInFlightRef.current) {
pendingIndicatorResyncRef.current = true;
return;
}
const prevList = prevIndicatorsListRef.current;
const prevPK = prevIndKey.current.split('@@')[0] ?? '';
const currPK = paramKey(indicators);
@@ -1142,12 +1171,18 @@ const TradingChart: React.FC<TradingChartProps> = ({
&& addedConfigs.length === 0
) {
const savedLR = mgr.getVisibleLogicalRange();
const syncGen = ++indicatorReloadGenRef.current;
indicatorSyncInFlightRef.current = true;
void mgr.refreshIndicators(paramsChangedConfigs).then(() => {
if (syncGen !== indicatorReloadGenRef.current) {
indicatorSyncInFlightRef.current = false;
return;
}
afterIndicatorPaneMutation(mgr);
restoreLogicalRange(mgr, savedLR);
syncIndicatorTrackingRefs(indicators);
indicatorSyncInFlightRef.current = false;
flushPendingIndicatorResync(mgr, syncGen);
});
} else if (isReorderOnly) {
reloadIndicatorsWithCover(mgr, indicators, () => {
@@ -1165,7 +1200,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [chartMgr, bars, barsMarket, market, chartType, theme, logScale, indicators, chartVisible, reloadIndicatorsWithCover, afterIndicatorPaneMutation, restoreLogicalRange, syncIndicatorTrackingRefs, repairMissingIndicators]);
}, [chartMgr, bars, barsMarket, market, chartType, theme, logScale, indicators, chartVisible, reloadIndicatorsWithCover, afterIndicatorPaneMutation, restoreLogicalRange, syncIndicatorTrackingRefs, repairMissingIndicators, flushPendingIndicatorResync]);
// 데이터는 준비됐으나 메인 시리즈가 없는 빈 차트 자동 복구 (종목 전환·레이아웃 전환 직후)
useEffect(() => {
@@ -1325,6 +1360,12 @@ const TradingChart: React.FC<TradingChartProps> = ({
onClose={() => setCtxMenu(null)}
/>
)}
{chartMgr && (
<CandlePaneTimeAxisPortal
manager={chartMgr}
getContainer={() => containerRef.current}
/>
)}
{chartMgr && paneSeparatorOptions && (
<PaneSeparatorOverlay
manager={chartMgr}
@@ -1397,6 +1438,22 @@ const TradingChart: React.FC<TradingChartProps> = ({
);
};
const CandlePaneTimeAxisPortal: React.FC<{
manager: ChartManager;
getContainer: () => HTMLElement | null;
}> = ({ manager, getContainer }) => {
const [el, setEl] = useState<HTMLElement | null>(null);
useEffect(() => {
const c = getContainer();
if (c) { setEl(c); return; }
const tid = setTimeout(() => setEl(getContainer()), 100);
return () => clearTimeout(tid);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!el) return null;
return <CandlePaneTimeAxis manager={manager} containerEl={el} />;
};
const CandlePaneControlsPortal: React.FC<{
manager: ChartManager;
getContainer: () => HTMLElement | null;