diff --git a/frontend/src/components/strategyEvaluation/StrategyEvaluationChart.tsx b/frontend/src/components/strategyEvaluation/StrategyEvaluationChart.tsx index cf16f7f..356136b 100644 --- a/frontend/src/components/strategyEvaluation/StrategyEvaluationChart.tsx +++ b/frontend/src/components/strategyEvaluation/StrategyEvaluationChart.tsx @@ -22,7 +22,18 @@ import { } from '../../utils/backtestWarmup'; import type { EvalIndicatorParams } from '../../utils/strategyEvaluationParams'; import { DISPLAY_COUNT } from '../../hooks/useUpbitData'; -import { buildVirtualTradingChartIndicators } from '../../utils/strategyToChartIndicators'; +import { + chartOverlayKeyForType, + DEFAULT_CHART_OVERLAY_VISIBILITY, + effectiveConfigForOverlay, + listChartOverlayToggleItems, + type ChartOverlayToggleKey, + type ChartOverlayVisibility, +} from '../../utils/chartOverlayVisibility'; +import { + buildVirtualTradingChartIndicators, + ensurePaperChartOverlays, +} from '../../utils/strategyToChartIndicators'; import { buildEvalParamsFromStrategy } from '../../utils/strategyEvaluationParams'; import { indicatorDefaultsFingerprint } from '../../utils/indicatorDefaultsFingerprint'; import { repairUtf8Mojibake } from '../../utils/textEncoding'; @@ -138,17 +149,59 @@ const StrategyEvaluationChart: React.FC = ({ const [magnifierActive, setMagnifierActive] = useState(false); const marketBtnRef = useRef(null); const managerRef = useRef(null); + const [overlayVisibility, setOverlayVisibility] = useState( + () => ({ ...DEFAULT_CHART_OVERLAY_VISIBILITY }), + ); + const overlayVisibilityRef = useRef(overlayVisibility); + useEffect(() => { + overlayVisibilityRef.current = overlayVisibility; + }, [overlayVisibility]); const backtestGenRef = useRef(0); const evaluationBarCountRef = useRef(BACKTEST_DISPLAY_BAR_COUNT); const signalsRef = useRef(signals); signalsRef.current = signals; const chartType: ChartType = 'candlestick'; - const indicators = useMemo(() => { + const baseIndicators = useMemo(() => { if (!strategy) return [] as IndicatorConfig[]; - return buildVirtualTradingChartIndicators(strategy, getParams, getVisualConfig); + let inds = buildVirtualTradingChartIndicators(strategy, getParams, getVisualConfig); + inds = ensurePaperChartOverlays(inds, getParams, getVisualConfig); + return inds; }, [strategy, getParams, getVisualConfig, settingsRevision]); + const indicators = useMemo( + () => baseIndicators.map(ind => effectiveConfigForOverlay(ind, overlayVisibility)), + [baseIndicators, overlayVisibility], + ); + + const indicatorsRef = useRef(indicators); + useEffect(() => { + indicatorsRef.current = indicators; + }, [indicators]); + + const candleOverlayToggles = useMemo( + () => listChartOverlayToggleItems(overlayVisibility), + [overlayVisibility], + ); + + const handleToggleCandleOverlay = useCallback((key: ChartOverlayToggleKey) => { + setOverlayVisibility(prev => { + const next = { ...prev, [key]: !prev[key] }; + const mgr = managerRef.current; + mgr?.setChartOverlayGroupVisible(key, next[key]); + if (mgr) { + for (const info of mgr.getIndicatorPaneInfo()) { + if (chartOverlayKeyForType(info.type) !== key) continue; + const reactInd = indicatorsRef.current.find(i => i.id === info.id); + mgr.applyIndicatorStyle(reactInd + ? { ...info.config, ...reactInd, id: info.id, type: info.type } + : info.config); + } + } + return next; + }); + }, []); + const auxPaneCount = useMemo( () => countNonOverlayIndicatorPanes(indicators, isOverlayType), [indicators], @@ -313,6 +366,7 @@ const StrategyEvaluationChart: React.FC = ({ const onManagerReady = useCallback((mgr: ChartManager) => { managerRef.current = mgr; setChartMgr(mgr); + mgr.setChartOverlayVisibility(overlayVisibilityRef.current); if (paneAreaRatio && paneAreaRatio.aux > 0) { mgr.setPaneAreaRatio(paneAreaRatio.candle, paneAreaRatio.aux); } @@ -568,6 +622,9 @@ const StrategyEvaluationChart: React.FC = ({ showHoverToolbar={false} showChartRightToolbar={false} showCandlePaneControls={false} + showCandleOverlayControls + candleOverlayToggles={candleOverlayToggles} + onToggleCandleOverlay={handleToggleCandleOverlay} magnifierEnabled={magnifierActive} onMagnifierClose={() => setMagnifierActive(false)} resolveInitialDisplayCount={resolveInitialDisplayCount}