From 737750789466e70a3fceb720e5408c9f585f2f05 Mon Sep 17 00:00:00 2001 From: Macbook Date: Wed, 17 Jun 2026 02:01:05 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BB=A4=EC=8A=A4=ED=85=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/ChartRightToolbar.tsx | 4 +- .../StrategyEvaluationChart.tsx | 103 +++++++++++++++++- 2 files changed, 104 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/ChartRightToolbar.tsx b/frontend/src/components/ChartRightToolbar.tsx index cffcffd..0faf7bb 100644 --- a/frontend/src/components/ChartRightToolbar.tsx +++ b/frontend/src/components/ChartRightToolbar.tsx @@ -449,8 +449,8 @@ const ChartRightToolbar: React.FC = ({ ? paneItems.find(p => p.id === openMenuId) : null; - const showCustomBtn = !!(showOverlayIcons && !overlayControlsOnly && onCustomOverlayActiveChange && onOpenCustomOverlaySettings); - const showCustom1Btn = !!(showOverlayIcons && !overlayControlsOnly && onCustom1OverlayActiveChange && onOpenCustom1OverlaySettings); + const showCustomBtn = !!(showOverlayIcons && onCustomOverlayActiveChange && onOpenCustomOverlaySettings); + const showCustom1Btn = !!(showOverlayIcons && onCustom1OverlayActiveChange && onOpenCustomOverlaySettings); const customMenuOpen = openMenuId === CUSTOM_MENU_ID; const custom1MenuOpen = openMenuId === CUSTOM1_MENU_ID; diff --git a/frontend/src/components/strategyEvaluation/StrategyEvaluationChart.tsx b/frontend/src/components/strategyEvaluation/StrategyEvaluationChart.tsx index 356136b..3bb9e17 100644 --- a/frontend/src/components/strategyEvaluation/StrategyEvaluationChart.tsx +++ b/frontend/src/components/strategyEvaluation/StrategyEvaluationChart.tsx @@ -1,6 +1,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import ReactDOM from 'react-dom'; import TradingChart from '../TradingChart'; +import ChartCustomOverlaySettingsModal from '../ChartCustomOverlaySettingsModal'; import { MarketSearchPanel } from '../MarketSearchPanel'; import type { ChartType, IndicatorConfig, OHLCVBar, Theme, Timeframe } from '../../types'; import type { ChartManager } from '../../utils/ChartManager'; @@ -22,6 +23,14 @@ import { } from '../../utils/backtestWarmup'; import type { EvalIndicatorParams } from '../../utils/strategyEvaluationParams'; import { DISPLAY_COUNT } from '../../hooks/useUpbitData'; +import { + createDefaultChartCustomOverlaySelection, + isCustomOverlaySlotActive, + resolveActiveCustomOverlaySlot, + syncChartManagerCustomOverlaysByActiveSlot, + type ChartCustomOverlaySelection, + type ChartCustomOverlaySlotId, +} from '../../utils/chartCustomOverlay'; import { chartOverlayKeyForType, DEFAULT_CHART_OVERLAY_VISIBILITY, @@ -202,6 +211,73 @@ const StrategyEvaluationChart: React.FC = ({ }); }, []); + const [customOverlaySelection, setCustomOverlaySelection] = useState( + () => createDefaultChartCustomOverlaySelection(), + ); + const [custom1OverlaySelection, setCustom1OverlaySelection] = useState( + () => createDefaultChartCustomOverlaySelection(), + ); + const [activeCustomOverlaySlot, setActiveCustomOverlaySlot] = useState(null); + const [customOverlaySettingsSlot, setCustomOverlaySettingsSlot] = useState(null); + const customOverlayActive = isCustomOverlaySlotActive(activeCustomOverlaySlot, 'custom'); + const custom1OverlayActive = isCustomOverlaySlotActive(activeCustomOverlaySlot, 'custom1'); + + const syncCustomOverlaysToManager = useCallback((mgr: ChartManager) => { + syncChartManagerCustomOverlaysByActiveSlot( + mgr, + activeCustomOverlaySlot, + customOverlaySelection, + custom1OverlaySelection, + ); + }, [activeCustomOverlaySlot, customOverlaySelection, custom1OverlaySelection]); + + const handleCustomOverlayActiveChange = useCallback((active: boolean) => { + const nextSlot = resolveActiveCustomOverlaySlot('custom', active, activeCustomOverlaySlot); + setActiveCustomOverlaySlot(nextSlot); + const mgr = managerRef.current; + if (mgr) { + syncChartManagerCustomOverlaysByActiveSlot( + mgr, + nextSlot, + customOverlaySelection, + custom1OverlaySelection, + ); + } + }, [activeCustomOverlaySlot, customOverlaySelection, custom1OverlaySelection]); + + const handleCustom1OverlayActiveChange = useCallback((active: boolean) => { + const nextSlot = resolveActiveCustomOverlaySlot('custom1', active, activeCustomOverlaySlot); + setActiveCustomOverlaySlot(nextSlot); + const mgr = managerRef.current; + if (mgr) { + syncChartManagerCustomOverlaysByActiveSlot( + mgr, + nextSlot, + customOverlaySelection, + custom1OverlaySelection, + ); + } + }, [activeCustomOverlaySlot, customOverlaySelection, custom1OverlaySelection]); + + const handleSaveCustomOverlaySelection = useCallback(( + slot: ChartCustomOverlaySlotId, + selection: ChartCustomOverlaySelection, + ) => { + const nextCustomSel = slot === 'custom' ? selection : customOverlaySelection; + const nextCustom1Sel = slot === 'custom1' ? selection : custom1OverlaySelection; + if (slot === 'custom') setCustomOverlaySelection(selection); + else setCustom1OverlaySelection(selection); + setCustomOverlaySettingsSlot(null); + const mgr = managerRef.current; + if (!mgr) return; + syncChartManagerCustomOverlaysByActiveSlot( + mgr, + activeCustomOverlaySlot, + nextCustomSel, + nextCustom1Sel, + ); + }, [activeCustomOverlaySlot, customOverlaySelection, custom1OverlaySelection]); + const auxPaneCount = useMemo( () => countNonOverlayIndicatorPanes(indicators, isOverlayType), [indicators], @@ -367,12 +443,13 @@ const StrategyEvaluationChart: React.FC = ({ managerRef.current = mgr; setChartMgr(mgr); mgr.setChartOverlayVisibility(overlayVisibilityRef.current); + syncCustomOverlaysToManager(mgr); if (paneAreaRatio && paneAreaRatio.aux > 0) { mgr.setPaneAreaRatio(paneAreaRatio.candle, paneAreaRatio.aux); } applyMarkers(); fitChartViewport(); - }, [paneAreaRatio, applyMarkers, fitChartViewport]); + }, [paneAreaRatio, applyMarkers, fitChartViewport, syncCustomOverlaysToManager]); const onCandlesReady = useCallback(() => { const mgr = managerRef.current; @@ -625,6 +702,12 @@ const StrategyEvaluationChart: React.FC = ({ showCandleOverlayControls candleOverlayToggles={candleOverlayToggles} onToggleCandleOverlay={handleToggleCandleOverlay} + customOverlayActive={customOverlayActive} + onCustomOverlayActiveChange={handleCustomOverlayActiveChange} + onOpenCustomOverlaySettings={() => setCustomOverlaySettingsSlot('custom')} + custom1OverlayActive={custom1OverlayActive} + onCustom1OverlayActiveChange={handleCustom1OverlayActiveChange} + onOpenCustom1OverlaySettings={() => setCustomOverlaySettingsSlot('custom1')} magnifierEnabled={magnifierActive} onMagnifierClose={() => setMagnifierActive(false)} resolveInitialDisplayCount={resolveInitialDisplayCount} @@ -637,6 +720,24 @@ const StrategyEvaluationChart: React.FC = ({ onSelectedBarIndexChange={onSelectedBarIndexChange} /> )} + {customOverlaySettingsSlot === 'custom' && ( + handleSaveCustomOverlaySelection('custom', sel)} + onCancel={() => setCustomOverlaySettingsSlot(null)} + smaParams={baseIndicators.find(i => i.type === 'SMA')?.params} + /> + )} + {customOverlaySettingsSlot === 'custom1' && ( + handleSaveCustomOverlaySelection('custom1', sel)} + onCancel={() => setCustomOverlaySettingsSlot(null)} + smaParams={baseIndicators.find(i => i.type === 'SMA')?.params} + /> + )} )} {!loading && !error && bars.length === 0 && (