diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6d0c9ad..690b748 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -650,6 +650,9 @@ function App() { signals: BacktestSignal[]; showPrice: boolean; } | null>(null); + /** applyBacktestMarkersWhenReady rAF 재시도 시 최신 차트 종목·타임프레임 검증용 */ + const chartContextRef = useRef({ symbol, timeframe }); + chartContextRef.current = { symbol, timeframe }; const syncBacktestMarkersRef = useRef<() => void>(() => {}); @@ -658,17 +661,23 @@ function App() { payload: NonNullable, attempt = 0, ) => { + btMarkerPayloadRef.current = payload; + const { symbol: curSym, timeframe: curTf } = chartContextRef.current; + if (payload.symbol !== curSym || payload.timeframe !== curTf) return; + const mgr = managerRef.current; if (!mgr || !mgr.hasMainSeries()) { - if (attempt < 30) { + if (attempt < 120) { requestAnimationFrame(() => applyBacktestMarkersWhenReady(payload, attempt + 1)); } return; } - btMarkerPayloadRef.current = payload; + if (payload.symbol !== chartContextRef.current.symbol + || payload.timeframe !== chartContextRef.current.timeframe) return; mgr.setBacktestMarkers(payload.signals, payload.showPrice); }, []); + /** reloadAll 직후 등 — payload 가 있을 때만 재적용 (없을 때 clear 하지 않음, 경쟁 방지) */ const syncBacktestMarkers = useCallback(() => { const payload = btMarkerPayloadRef.current; if ( @@ -678,9 +687,7 @@ function App() { payload.signals.length > 0 ) { applyBacktestMarkersWhenReady(payload); - return; } - managerRef.current?.clearBacktestMarkers(); }, [symbol, timeframe, applyBacktestMarkersWhenReady]); syncBacktestMarkersRef.current = syncBacktestMarkers; @@ -897,16 +904,18 @@ function App() { // 백테스팅 시그널 → ref 갱신 후 ChartManager 마커 동기화 useEffect(() => { if (btMatchesChart && btSignals.length > 0) { - btMarkerPayloadRef.current = { + const payload = { symbol, timeframe, signals: btSignals, showPrice: appDefaults.btShowPrice, }; + btMarkerPayloadRef.current = payload; + applyBacktestMarkersWhenReady(payload); } else { btMarkerPayloadRef.current = null; + managerRef.current?.clearBacktestMarkers(); } - syncBacktestMarkers(); let innerRaf = 0; const outerRaf = requestAnimationFrame(() => { innerRaf = requestAnimationFrame(syncBacktestMarkers); @@ -915,7 +924,7 @@ function App() { cancelAnimationFrame(outerRaf); if (innerRaf) cancelAnimationFrame(innerRaf); }; - }, [btMatchesChart, btSignals, btRunSeq, symbol, timeframe, appDefaults.btShowPrice, syncBacktestMarkers]); + }, [btMatchesChart, btSignals, btRunSeq, symbol, timeframe, appDefaults.btShowPrice, applyBacktestMarkersWhenReady, syncBacktestMarkers]); // ── 시뮬레이션 데이터 (Upbit 아닐 때) ────────────────────────────────── const [simBars, setSimBars] = useState(() => @@ -1716,12 +1725,14 @@ function App() { r.signals.length > 0 ) { const showPrice = appDefaults.btShowPrice; - applyBacktestMarkersWhenReady({ + const payload = { symbol: runSymbol, timeframe: runTf, signals: r.signals, showPrice, - }); + }; + btMarkerPayloadRef.current = payload; + applyBacktestMarkersWhenReady(payload); } if (appDefaults.btAutoPopup) setShowBtResult(true); } @@ -1729,7 +1740,11 @@ function App() { onOpenBtSettings={() => setShowBtSettings(true)} onOpenBtResults={() => setShowBtResult(true)} hasBtResult={btMatchesChart} - onClearBtMarkers={() => { btClear(); managerRef.current?.clearBacktestMarkers(); }} + onClearBtMarkers={() => { + btMarkerPayloadRef.current = null; + btClear(); + managerRef.current?.clearBacktestMarkers(); + }} onToggleLiveStrategy={() => setShowLivePanel(v => !v)} liveStrategyActive={showLivePanel || monitoredMarkets.length > 0} /> @@ -2003,7 +2018,11 @@ function App() { { btClear(); managerRef.current?.clearBacktestMarkers(); }} + onClear={() => { + btMarkerPayloadRef.current = null; + btClear(); + managerRef.current?.clearBacktestMarkers(); + }} /> )} diff --git a/frontend/src/components/TradingChart.tsx b/frontend/src/components/TradingChart.tsx index a6b266d..713207b 100644 --- a/frontend/src/components/TradingChart.tsx +++ b/frontend/src/components/TradingChart.tsx @@ -306,6 +306,7 @@ const TradingChart: React.FC = ({ m.resetPaneHeights(w.clientHeight); const fb = m.hasIchimoku() ? 28 : 0; m.setInitialVisibleRange(DISPLAY_COUNT, fb); + if (delay === 1400) onDataLoaded?.(); }, delay) ); }, [applyPaneLayout, onDataLoaded, displayTimezone, timeframe, market]);