From 9fde6868e2aafdd4bd20ef4c5a900a8934d7b4fb Mon Sep 17 00:00:00 2001 From: Macbook Date: Mon, 8 Jun 2026 12:09:38 +0900 Subject: [PATCH] =?UTF-8?q?=EB=B0=B1=ED=85=8C=EC=8A=A4=ED=8C=85=20?= =?UTF-8?q?=EC=B0=A8=ED=8A=B8=20=EC=83=89=EC=83=81=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backtest/BacktestAnalysisChart.tsx | 62 +++++++------- .../trendSearch/TrendSearchCardChart.tsx | 16 +--- .../trendSearch/TrendSearchChartPanel.tsx | 16 +--- frontend/src/hooks/useIndicatorSettings.ts | 4 +- frontend/src/utils/indicatorPaneMerge.ts | 80 ++++++++++++------- 5 files changed, 89 insertions(+), 89 deletions(-) diff --git a/frontend/src/components/backtest/BacktestAnalysisChart.tsx b/frontend/src/components/backtest/BacktestAnalysisChart.tsx index daa1a4f..d7a464e 100644 --- a/frontend/src/components/backtest/BacktestAnalysisChart.tsx +++ b/frontend/src/components/backtest/BacktestAnalysisChart.tsx @@ -17,12 +17,18 @@ import { import { getKoreanName } from '../../utils/marketNameCache'; import { useIndicatorSettings } from '../../hooks/useIndicatorSettings'; import { useAppSettings } from '../../hooks/useAppSettings'; -import { enrichIndicatorConfig, getIndicatorDef } from '../../utils/indicatorRegistry'; +import { getIndicatorDef } from '../../utils/indicatorRegistry'; import { chartPaneFlexRatio, countNonOverlayIndicatorPanes, } from '../../utils/strategyOscillatorSeries'; -import { normalizeSmaConfig } from '../../utils/smaConfig'; +import { + createDefaultSmaPlotVisibility, + normalizeSmaConfig, + smaPeriodKey, + smaPlotId, +} from '../../utils/smaConfig'; +import { buildChartIndicatorConfig } from '../../utils/indicatorPaneMerge'; import { applyPaperOverlayVisibility, listPaperOverlayToggleItems, @@ -76,44 +82,32 @@ const ALL_TF_VISIBLE = { '1m': true, '3m': true, '5m': true, '10m': true, '15m': function defaultOverlayIndicators( getParams: (t: string, d?: Record) => Record, + getVisualConfig: ReturnType['getVisualConfig'], ): IndicatorConfig[] { - const def = getIndicatorDef('SMA'); - if (!def) return []; - const params: Record = { ...def.defaultParams, ...getParams('SMA', def.defaultParams) }; - for (let i = 1; i <= 11; i++) { - params[`length${i}`] = i === 1 ? 14 : 0; - params[`visible${i}`] = i === 1; - } - const cfg = normalizeSmaConfig(enrichIndicatorConfig({ - id: newIndId(), - type: 'SMA', - params, - plots: def.plots, - hlines: def.hlines ?? [], + const cfg = buildChartIndicatorConfig('SMA', newIndId(), getParams, getVisualConfig, { timeframeVisibility: ALL_TF_VISIBLE, - })); - return [cfg]; + }); + if (!cfg) return []; + // 전략 미선택 시 MA1(14)만 표시 + const p = { ...cfg.params }; + p[smaPeriodKey(1)] = 14; + const plotVisibility = createDefaultSmaPlotVisibility(); + for (let i = 0; i < 11; i++) { + plotVisibility[smaPlotId(i)] = i === 0; + } + return [normalizeSmaConfig({ ...cfg, params: p, plotVisibility })]; } /** 전략 미선택 시 기본 오실레이터(RSI, MACD)를 TradingChart sub-pane 으로 추가 */ function defaultOscillatorIndicators( getParams: (t: string, d?: Record) => Record, + getVisualConfig: ReturnType['getVisualConfig'], ): IndicatorConfig[] { - const result: IndicatorConfig[] = []; - for (const type of ['RSI', 'MACD']) { - const def = getIndicatorDef(type); - if (!def) continue; - const params: Record = { ...def.defaultParams, ...getParams(type, def.defaultParams) }; - result.push(enrichIndicatorConfig({ - id: newIndId(), - type, - params, - plots: def.plots, - hlines: def.hlines ?? [], + return ['RSI', 'MACD'] + .map(type => buildChartIndicatorConfig(type, newIndId(), getParams, getVisualConfig, { timeframeVisibility: ALL_TF_VISIBLE, - })); - } - return result; + })) + .filter((c): c is IndicatorConfig => c != null); } const isOverlayType = (type: string) => getIndicatorDef(type)?.overlay === true; @@ -194,13 +188,13 @@ const BacktestAnalysisChart: React.FC = ({ // 전략 지표가 모두 캔들 오버레이(SMA/EMA/일목 등)인 경우 기본 오실레이터(RSI·MACD)를 추가. // 기존 SVG 기반 StrategyOscillatorPanes 의 fallback 동작과 동일. if (showOscillatorPanel && inds.every(i => isOverlayType(i.type))) { - inds = [...inds, ...defaultOscillatorIndicators(getParams)]; + inds = [...inds, ...defaultOscillatorIndicators(getParams, getVisualConfig)]; } } else { - inds = defaultOverlayIndicators(getParams); + inds = defaultOverlayIndicators(getParams, getVisualConfig); // 전략 미선택 시 기본 오실레이터(RSI, MACD)도 TradingChart sub-pane 으로 추가 if (showOscillatorPanel) { - inds = [...inds, ...defaultOscillatorIndicators(getParams)]; + inds = [...inds, ...defaultOscillatorIndicators(getParams, getVisualConfig)]; } } if (overlayVisibility) { diff --git a/frontend/src/components/trendSearch/TrendSearchCardChart.tsx b/frontend/src/components/trendSearch/TrendSearchCardChart.tsx index b2ae1f4..37ff285 100644 --- a/frontend/src/components/trendSearch/TrendSearchCardChart.tsx +++ b/frontend/src/components/trendSearch/TrendSearchCardChart.tsx @@ -15,7 +15,7 @@ import { useHistoryLoader } from '../../hooks/useHistoryLoader'; import { useIndicatorSettings } from '../../hooks/useIndicatorSettings'; import type { ChartManager } from '../../utils/ChartManager'; import { timeframeToCandleType } from '../../utils/chartCandleType'; -import { enrichIndicatorConfig } from '../../utils/indicatorRegistry'; +import { buildChartIndicatorConfig } from '../../utils/indicatorPaneMerge'; import { DEFAULT_DISPLAY_TIMEZONE } from '../../utils/timezone'; interface Props { @@ -51,17 +51,9 @@ const TrendSearchCardChart: React.FC = ({ const { getParams, getVisualConfig } = useIndicatorSettings(); const indicators = useMemo( - () => CHART_INDICATOR_TYPES.map(type => { - const base = enrichIndicatorConfig({ - id: `${type}-tsd-card`, - type, - params: { ...getParams(type) }, - }); - return { - ...base, - plots: getVisualConfig(type)?.plots ?? base.plots, - }; - }), + () => CHART_INDICATOR_TYPES.map(type => + buildChartIndicatorConfig(type, `${type}-tsd-card`, getParams, getVisualConfig), + ).filter((c): c is NonNullable => c != null), [getParams, getVisualConfig], ); diff --git a/frontend/src/components/trendSearch/TrendSearchChartPanel.tsx b/frontend/src/components/trendSearch/TrendSearchChartPanel.tsx index c1258f1..c0b79fc 100644 --- a/frontend/src/components/trendSearch/TrendSearchChartPanel.tsx +++ b/frontend/src/components/trendSearch/TrendSearchChartPanel.tsx @@ -11,7 +11,7 @@ import { useHistoryLoader } from '../../hooks/useHistoryLoader'; import { useIndicatorSettings } from '../../hooks/useIndicatorSettings'; import type { ChartManager } from '../../utils/ChartManager'; import { timeframeToCandleType } from '../../utils/chartCandleType'; -import { enrichIndicatorConfig } from '../../utils/indicatorRegistry'; +import { buildChartIndicatorConfig } from '../../utils/indicatorPaneMerge'; import { DEFAULT_DISPLAY_TIMEZONE } from '../../utils/timezone'; interface Props { @@ -48,17 +48,9 @@ const TrendSearchChartPanel: React.FC = ({ const { getParams, getVisualConfig } = useIndicatorSettings(); const indicators = useMemo((): IndicatorConfig[] => - CHART_INDICATOR_TYPES.map(type => { - const base = enrichIndicatorConfig({ - id: `${type}-tsd`, - type, - params: { ...getParams(type) }, - }); - return { - ...base, - plots: getVisualConfig(type)?.plots ?? base.plots, - }; - }), + CHART_INDICATOR_TYPES.map(type => + buildChartIndicatorConfig(type, `${type}-tsd`, getParams, getVisualConfig), + ).filter((c): c is IndicatorConfig => c != null), [getParams, getVisualConfig]); const managerRef = useRef(null); diff --git a/frontend/src/hooks/useIndicatorSettings.ts b/frontend/src/hooks/useIndicatorSettings.ts index 53edc80..5734dc8 100644 --- a/frontend/src/hooks/useIndicatorSettings.ts +++ b/frontend/src/hooks/useIndicatorSettings.ts @@ -179,7 +179,7 @@ export function useIndicatorSettings(sessionKey = 0) { } return merged; }, - [] + [settingsRevision], ); /** 특정 지표 파라미터를 DB에 저장하고 캐시를 업데이트 */ @@ -309,7 +309,7 @@ export function useIndicatorSettings(sessionKey = 0) { bandBackground: bandBackground ? { ...bandBackground } : undefined, }; }, - [] + [settingsRevision], ); /** diff --git a/frontend/src/utils/indicatorPaneMerge.ts b/frontend/src/utils/indicatorPaneMerge.ts index 4bfe4c8..c154aba 100644 --- a/frontend/src/utils/indicatorPaneMerge.ts +++ b/frontend/src/utils/indicatorPaneMerge.ts @@ -142,16 +142,18 @@ export function layoutKey(inds: IndicatorConfig[]): string { return inds.map(i => `${i.id}>${i.mergedWith ?? ''}`).join(';'); } -type GetParams = ( - type: string, - defaults?: Record, -) => Record; - +type ParamRecord = Record; +type GetParams = (type: string, defaults?: ParamRecord) => ParamRecord; type GetVisual = ( type: string, defaultPlots?: PlotDef[], defaultHlines?: HLineDef[], -) => { plots: PlotDef[]; hlines: HLineDef[]; cloudColors?: IchimokuCloudColors; bandBackground?: import('../types').HlinesBackground }; +) => { + plots: PlotDef[]; + hlines: HLineDef[]; + cloudColors?: IchimokuCloudColors; + bandBackground?: import('../types').HlinesBackground; +}; /** 탭 전체 추가 — 기존 차트 지표를 제거하고 types 목록만 새로 구성 */ export function replaceIndicatorConfigsFromTypes( @@ -163,6 +165,47 @@ export function replaceIndicatorConfigsFromTypes( return buildIndicatorConfigsFromTypes(types, [], getParams, getVisualConfig, newId); } +/** DB 보조지표 설정(파라미터·색상·선굵기·유형)을 반영한 단일 IndicatorConfig */ +export function buildChartIndicatorConfig( + type: string, + id: string, + getParams: GetParams, + getVisualConfig: GetVisual, + extras: Partial = {}, +): IndicatorConfig | null { + const def = getIndicatorDef(type); + if (!def) return null; + + const params = getParams(type, def.defaultParams as ParamRecord); + const { plots, hlines, cloudColors, bandBackground } = getVisualConfig( + type, + def.plots, + def.hlines ?? [], + ); + + let cfg: IndicatorConfig = { + id, + type: def.type, + params, + plots, + hlines, + ...(cloudColors ? { cloudColors } : {}), + ...(bandBackground ? { bandBackground } : {}), + ...extras, + }; + + if (type === 'SMA') { + cfg = normalizeSmaConfig({ + ...cfg, + plotVisibility: cfg.plotVisibility ?? createDefaultSmaPlotVisibility(), + }); + } else if (type === 'IchimokuCloud') { + cfg = normalizeIchimokuConfig(cfg); + } + + return enrichIndicatorConfig(cfg); +} + /** 지표 타입 목록 → 차트에 추가할 IndicatorConfig 배열 (이미 활성인 type 제외) */ export function buildIndicatorConfigsFromTypes( types: string[], @@ -176,30 +219,9 @@ export function buildIndicatorConfigsFromTypes( for (const type of types) { if (seen.has(type)) continue; - const def = getIndicatorDef(type); - if (!def) continue; seen.add(type); - - const params = getParams(type, def.defaultParams); - const { plots, hlines, cloudColors, bandBackground } = getVisualConfig(type, def.plots, def.hlines); - let cfg: IndicatorConfig = { - id: newId(), - type: def.type, - params, - plots, - hlines, - ...(cloudColors ? { cloudColors } : {}), - ...(bandBackground ? { bandBackground } : {}), - }; - if (type === 'SMA') { - cfg = normalizeSmaConfig({ - ...cfg, - plotVisibility: cfg.plotVisibility ?? createDefaultSmaPlotVisibility(), - }); - } else if (type === 'IchimokuCloud') { - cfg = normalizeIchimokuConfig(cfg); - } - out.push(cfg); + const cfg = buildChartIndicatorConfig(type, newId(), getParams, getVisualConfig); + if (cfg) out.push(cfg); } return out; }