전략편집기 시간봉 선택오류 수정

This commit is contained in:
Macbook
2026-06-12 21:32:19 +09:00
parent 046bcc6379
commit 6118230471
8 changed files with 164 additions and 97 deletions
+69 -11
View File
@@ -573,6 +573,13 @@ export function useChartWorkspace({
const [favoritesList, setFavoritesList] = useState<string[]>(getFavorites);
// DB 로드 완료 여부 (false → DB 로드 전, localStorage 기반 동작)
const [dbLoaded, setDbLoaded] = useState(false);
/** DB·앱설정·지표 기본값 반영 완료 — 이후 STOMP 초기 로딩 시작 (이중 페인트 방지) */
const [chartBootstrapComplete, setChartBootstrapComplete] = useState(false);
/** 싱글 레이아웃 차트 paint 완료 */
const [singleChartPaintReady, setSingleChartPaintReady] = useState(false);
/** 멀티 슬롯별 paint 완료 */
const slotPaintReadyRef = useRef<Record<number, boolean>>({});
const [slotPaintEpoch, setSlotPaintEpoch] = useState(0);
const priorityMarkets = useMemo(
() => [symbol, ...watchlist],
@@ -1111,6 +1118,7 @@ export function useChartWorkspace({
const handleCandlesReady = useCallback(() => {
chartLiveReadyRef.current = true;
setSingleChartPaintReady(true);
const pending = pendingRealtimeBarRef.current;
const mgr = managerRef.current;
if (!pending || !mgr || barsMarketRef.current !== chartContextRef.current.symbol) return;
@@ -1118,16 +1126,26 @@ export function useChartWorkspace({
mgr.updateBar(pending);
}, []);
const handleSingleChartPaintReadyChange = useCallback((ready: boolean) => {
setSingleChartPaintReady(ready);
}, []);
const handleSlotPaintReadyChange = useCallback((slotIndex: number, ready: boolean) => {
slotPaintReadyRef.current[slotIndex] = ready;
setSlotPaintEpoch(v => v + 1);
}, []);
const chartRealtimeSource = appDefaults.chartRealtimeSource ?? 'BACKEND_STOMP';
const chartConfigReady = appSettingsLoaded && dbLoaded && chartBootstrapComplete;
const { bars: upbitBars, barsMarket: upbitBarsMarket, latestBar, wsStatus, isLoading, error } = useChartRealtimeData(
symbol,
timeframe,
useMemo(() => ({
onTickUpdate: handleTickUpdate,
onNewCandle: handleNewCandle,
enabled: chartScreenActive && useUpbit,
enabled: chartScreenActive && useUpbit && chartConfigReady,
source: chartRealtimeSource as 'BACKEND_STOMP' | 'UPBIT_DIRECT',
}), [handleTickUpdate, handleNewCandle, chartScreenActive, useUpbit, chartRealtimeSource]),
}), [handleTickUpdate, handleNewCandle, chartScreenActive, useUpbit, chartConfigReady, chartRealtimeSource]),
);
// 실제로 차트에 넘길 bars (초기 200개, 실시간 수신 후에도 변경 없음)
@@ -1167,8 +1185,15 @@ export function useChartWorkspace({
useLayoutEffect(() => {
chartLiveReadyRef.current = false;
pendingRealtimeBarRef.current = null;
setSingleChartPaintReady(false);
}, [symbol, timeframe]);
useEffect(() => {
slotPaintReadyRef.current = {};
setSlotPaintEpoch(v => v + 1);
setSingleChartPaintReady(false);
}, [layoutDef.count, sessionKey]);
// Upbit 초기 로딩 완료 시 stats 업데이트
useEffect(() => {
if (useUpbit && upbitBars.length > 0) {
@@ -1379,6 +1404,10 @@ export function useChartWorkspace({
});
}, [focusedIndicatorId, indicators, layoutDef.count]);
useEffect(() => {
setSingleChartPaintReady(false);
}, [effectiveIndicators]);
const handleExpandIndicator = useCallback((id: string) => {
setFocusedIndicatorId(id);
}, []);
@@ -1701,7 +1730,7 @@ export function useChartWorkspace({
const bulkSettingsIndicators = layoutDef.count > 1 ? activeSlotIndicators : indicators;
/** 설정 화면에서 DB 기본값 저장 후 차트 복귀 시 — 전역 기본값을 차트 인스턴스에 일괄 반영 */
const lastSyncedSettingsRevisionRef = useRef(indicatorSettingsRevision);
const lastSyncedSettingsRevisionRef = useRef(-1);
const syncChartsFromSavedDefaults = useCallback(() => {
const merge = (inds: IndicatorConfig[]) =>
mergeGlobalDefaultsOntoChartIndicators(inds, getParams, getVisualConfig);
@@ -1729,15 +1758,25 @@ export function useChartWorkspace({
}, [layoutDef.count, activeSlot, activeSlotIndicators, getParams, getVisualConfig]);
useLayoutEffect(() => {
if (!chartVisible) return;
if (indicatorSettingsRevision <= lastSyncedSettingsRevisionRef.current) return;
if (consumeSkipNextChartDefaultsSync()) {
lastSyncedSettingsRevisionRef.current = indicatorSettingsRevision;
if (!appSettingsLoaded || !dbLoaded) {
setChartBootstrapComplete(false);
return;
}
syncChartsFromSavedDefaults();
lastSyncedSettingsRevisionRef.current = indicatorSettingsRevision;
}, [chartVisible, indicatorSettingsRevision, syncChartsFromSavedDefaults]);
if (chartVisible && indicatorSettingsRevision > lastSyncedSettingsRevisionRef.current) {
if (consumeSkipNextChartDefaultsSync()) {
lastSyncedSettingsRevisionRef.current = indicatorSettingsRevision;
} else {
syncChartsFromSavedDefaults();
lastSyncedSettingsRevisionRef.current = indicatorSettingsRevision;
}
}
setChartBootstrapComplete(true);
}, [appSettingsLoaded, dbLoaded, chartVisible, indicatorSettingsRevision, syncChartsFromSavedDefaults]);
useEffect(() => {
setChartBootstrapComplete(false);
lastSyncedSettingsRevisionRef.current = -1;
}, [sessionKey]);
/** 보조지표 일괄 설정 적용 — 전체 지표 DB + 차트 인스턴스 병합 */
const handleBulkIndicatorApply = useCallback((result: {
@@ -1911,6 +1950,23 @@ export function useChartWorkspace({
settingsPageProps,
]);
const allSlotsPaintReady = useMemo(() => {
void slotPaintEpoch;
for (let i = 0; i < layoutDef.count; i++) {
if (!slotPaintReadyRef.current[i]) return false;
}
return layoutDef.count > 0;
}, [layoutDef.count, slotPaintEpoch]);
const chartDataFetched = !useUpbit || (!isLoading && bars.length >= 2 && barsMarket === symbol);
const chartPaintComplete = layoutDef.count > 1 ? allSlotsPaintReady : singleChartPaintReady;
const chartDataLoading = useUpbit && chartVisible && (!chartConfigReady || !chartDataFetched);
const chartAreaLoading = useUpbit && chartVisible && (
!chartConfigReady
|| !chartPaintComplete
|| (layoutDef.count === 1 && !chartDataFetched)
);
const viewProps: ChartWorkspaceViewProps = {
onOpenNotificationsPage,
onOpenAppDownload,
@@ -1946,6 +2002,7 @@ export function useChartWorkspace({
paperCashBalance, paperCoinQty, paperTradingEnabled, paperAutoTradeEnabled,
appDefaults, stats, priceMap, alerts, hoverBar, quoteBar, liveQuote,
isLoading, error: error ?? null, bulkSettingsIndicators, chartIndicatorsForSettings,
chartConfigReady, chartAreaLoading, chartDataLoading,
getParams, getVisualConfig, activeSlotIndicators,
setSymbol, setLegend, setChartType, setMode, setLogScale, setShowGrid,
setDrawingTool, setDrawings, setDrawingsLocked, setDrawingsVisible,
@@ -1974,7 +2031,8 @@ export function useChartWorkspace({
handleTogglePriceExtremeLabels,
handleCustomOverlayActiveChange, handleCustom1OverlayActiveChange,
handleSaveCustomOverlaySelection, syncCustomOverlaysToManager,
handleCandlesReady, applyBacktestMarkersWhenReady, btRun, refreshPaperAccount,
handleCandlesReady, handleSingleChartPaintReadyChange, handleSlotPaintReadyChange,
applyBacktestMarkersWhenReady, btRun, refreshPaperAccount,
handlePaperOrderFilled, handleLiveSettingsChange, clearLiveMarkers,
goToMarketChart, handleTimezoneChange,
};