백테스팅 차트 색상 오류 수정

This commit is contained in:
Macbook
2026-06-08 12:09:38 +09:00
parent c20c806c19
commit 9fde6868e2
5 changed files with 89 additions and 89 deletions
@@ -17,12 +17,18 @@ import {
import { getKoreanName } from '../../utils/marketNameCache'; import { getKoreanName } from '../../utils/marketNameCache';
import { useIndicatorSettings } from '../../hooks/useIndicatorSettings'; import { useIndicatorSettings } from '../../hooks/useIndicatorSettings';
import { useAppSettings } from '../../hooks/useAppSettings'; import { useAppSettings } from '../../hooks/useAppSettings';
import { enrichIndicatorConfig, getIndicatorDef } from '../../utils/indicatorRegistry'; import { getIndicatorDef } from '../../utils/indicatorRegistry';
import { import {
chartPaneFlexRatio, chartPaneFlexRatio,
countNonOverlayIndicatorPanes, countNonOverlayIndicatorPanes,
} from '../../utils/strategyOscillatorSeries'; } from '../../utils/strategyOscillatorSeries';
import { normalizeSmaConfig } from '../../utils/smaConfig'; import {
createDefaultSmaPlotVisibility,
normalizeSmaConfig,
smaPeriodKey,
smaPlotId,
} from '../../utils/smaConfig';
import { buildChartIndicatorConfig } from '../../utils/indicatorPaneMerge';
import { import {
applyPaperOverlayVisibility, applyPaperOverlayVisibility,
listPaperOverlayToggleItems, listPaperOverlayToggleItems,
@@ -76,44 +82,32 @@ const ALL_TF_VISIBLE = { '1m': true, '3m': true, '5m': true, '10m': true, '15m':
function defaultOverlayIndicators( function defaultOverlayIndicators(
getParams: (t: string, d?: Record<string, number | string | boolean>) => Record<string, number | string | boolean>, getParams: (t: string, d?: Record<string, number | string | boolean>) => Record<string, number | string | boolean>,
getVisualConfig: ReturnType<typeof useIndicatorSettings>['getVisualConfig'],
): IndicatorConfig[] { ): IndicatorConfig[] {
const def = getIndicatorDef('SMA'); const cfg = buildChartIndicatorConfig('SMA', newIndId(), getParams, getVisualConfig, {
if (!def) return [];
const params: Record<string, number | string | boolean> = { ...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 ?? [],
timeframeVisibility: ALL_TF_VISIBLE, 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 으로 추가 */ /** 전략 미선택 시 기본 오실레이터(RSI, MACD)를 TradingChart sub-pane 으로 추가 */
function defaultOscillatorIndicators( function defaultOscillatorIndicators(
getParams: (t: string, d?: Record<string, number | string | boolean>) => Record<string, number | string | boolean>, getParams: (t: string, d?: Record<string, number | string | boolean>) => Record<string, number | string | boolean>,
getVisualConfig: ReturnType<typeof useIndicatorSettings>['getVisualConfig'],
): IndicatorConfig[] { ): IndicatorConfig[] {
const result: IndicatorConfig[] = []; return ['RSI', 'MACD']
for (const type of ['RSI', 'MACD']) { .map(type => buildChartIndicatorConfig(type, newIndId(), getParams, getVisualConfig, {
const def = getIndicatorDef(type);
if (!def) continue;
const params: Record<string, number | string | boolean> = { ...def.defaultParams, ...getParams(type, def.defaultParams) };
result.push(enrichIndicatorConfig({
id: newIndId(),
type,
params,
plots: def.plots,
hlines: def.hlines ?? [],
timeframeVisibility: ALL_TF_VISIBLE, timeframeVisibility: ALL_TF_VISIBLE,
})); }))
} .filter((c): c is IndicatorConfig => c != null);
return result;
} }
const isOverlayType = (type: string) => getIndicatorDef(type)?.overlay === true; const isOverlayType = (type: string) => getIndicatorDef(type)?.overlay === true;
@@ -194,13 +188,13 @@ const BacktestAnalysisChart: React.FC<Props> = ({
// 전략 지표가 모두 캔들 오버레이(SMA/EMA/일목 등)인 경우 기본 오실레이터(RSI·MACD)를 추가. // 전략 지표가 모두 캔들 오버레이(SMA/EMA/일목 등)인 경우 기본 오실레이터(RSI·MACD)를 추가.
// 기존 SVG 기반 StrategyOscillatorPanes 의 fallback 동작과 동일. // 기존 SVG 기반 StrategyOscillatorPanes 의 fallback 동작과 동일.
if (showOscillatorPanel && inds.every(i => isOverlayType(i.type))) { if (showOscillatorPanel && inds.every(i => isOverlayType(i.type))) {
inds = [...inds, ...defaultOscillatorIndicators(getParams)]; inds = [...inds, ...defaultOscillatorIndicators(getParams, getVisualConfig)];
} }
} else { } else {
inds = defaultOverlayIndicators(getParams); inds = defaultOverlayIndicators(getParams, getVisualConfig);
// 전략 미선택 시 기본 오실레이터(RSI, MACD)도 TradingChart sub-pane 으로 추가 // 전략 미선택 시 기본 오실레이터(RSI, MACD)도 TradingChart sub-pane 으로 추가
if (showOscillatorPanel) { if (showOscillatorPanel) {
inds = [...inds, ...defaultOscillatorIndicators(getParams)]; inds = [...inds, ...defaultOscillatorIndicators(getParams, getVisualConfig)];
} }
} }
if (overlayVisibility) { if (overlayVisibility) {
@@ -15,7 +15,7 @@ import { useHistoryLoader } from '../../hooks/useHistoryLoader';
import { useIndicatorSettings } from '../../hooks/useIndicatorSettings'; import { useIndicatorSettings } from '../../hooks/useIndicatorSettings';
import type { ChartManager } from '../../utils/ChartManager'; import type { ChartManager } from '../../utils/ChartManager';
import { timeframeToCandleType } from '../../utils/chartCandleType'; import { timeframeToCandleType } from '../../utils/chartCandleType';
import { enrichIndicatorConfig } from '../../utils/indicatorRegistry'; import { buildChartIndicatorConfig } from '../../utils/indicatorPaneMerge';
import { DEFAULT_DISPLAY_TIMEZONE } from '../../utils/timezone'; import { DEFAULT_DISPLAY_TIMEZONE } from '../../utils/timezone';
interface Props { interface Props {
@@ -51,17 +51,9 @@ const TrendSearchCardChart: React.FC<Props> = ({
const { getParams, getVisualConfig } = useIndicatorSettings(); const { getParams, getVisualConfig } = useIndicatorSettings();
const indicators = useMemo( const indicators = useMemo(
() => CHART_INDICATOR_TYPES.map(type => { () => CHART_INDICATOR_TYPES.map(type =>
const base = enrichIndicatorConfig({ buildChartIndicatorConfig(type, `${type}-tsd-card`, getParams, getVisualConfig),
id: `${type}-tsd-card`, ).filter((c): c is NonNullable<typeof c> => c != null),
type,
params: { ...getParams(type) },
});
return {
...base,
plots: getVisualConfig(type)?.plots ?? base.plots,
};
}),
[getParams, getVisualConfig], [getParams, getVisualConfig],
); );
@@ -11,7 +11,7 @@ import { useHistoryLoader } from '../../hooks/useHistoryLoader';
import { useIndicatorSettings } from '../../hooks/useIndicatorSettings'; import { useIndicatorSettings } from '../../hooks/useIndicatorSettings';
import type { ChartManager } from '../../utils/ChartManager'; import type { ChartManager } from '../../utils/ChartManager';
import { timeframeToCandleType } from '../../utils/chartCandleType'; import { timeframeToCandleType } from '../../utils/chartCandleType';
import { enrichIndicatorConfig } from '../../utils/indicatorRegistry'; import { buildChartIndicatorConfig } from '../../utils/indicatorPaneMerge';
import { DEFAULT_DISPLAY_TIMEZONE } from '../../utils/timezone'; import { DEFAULT_DISPLAY_TIMEZONE } from '../../utils/timezone';
interface Props { interface Props {
@@ -48,17 +48,9 @@ const TrendSearchChartPanel: React.FC<Props> = ({
const { getParams, getVisualConfig } = useIndicatorSettings(); const { getParams, getVisualConfig } = useIndicatorSettings();
const indicators = useMemo((): IndicatorConfig[] => const indicators = useMemo((): IndicatorConfig[] =>
CHART_INDICATOR_TYPES.map(type => { CHART_INDICATOR_TYPES.map(type =>
const base = enrichIndicatorConfig({ buildChartIndicatorConfig(type, `${type}-tsd`, getParams, getVisualConfig),
id: `${type}-tsd`, ).filter((c): c is IndicatorConfig => c != null),
type,
params: { ...getParams(type) },
});
return {
...base,
plots: getVisualConfig(type)?.plots ?? base.plots,
};
}),
[getParams, getVisualConfig]); [getParams, getVisualConfig]);
const managerRef = useRef<ChartManager | null>(null); const managerRef = useRef<ChartManager | null>(null);
+2 -2
View File
@@ -179,7 +179,7 @@ export function useIndicatorSettings(sessionKey = 0) {
} }
return merged; return merged;
}, },
[] [settingsRevision],
); );
/** 특정 지표 파라미터를 DB에 저장하고 캐시를 업데이트 */ /** 특정 지표 파라미터를 DB에 저장하고 캐시를 업데이트 */
@@ -309,7 +309,7 @@ export function useIndicatorSettings(sessionKey = 0) {
bandBackground: bandBackground ? { ...bandBackground } : undefined, bandBackground: bandBackground ? { ...bandBackground } : undefined,
}; };
}, },
[] [settingsRevision],
); );
/** /**
+51 -29
View File
@@ -142,16 +142,18 @@ export function layoutKey(inds: IndicatorConfig[]): string {
return inds.map(i => `${i.id}>${i.mergedWith ?? ''}`).join(';'); return inds.map(i => `${i.id}>${i.mergedWith ?? ''}`).join(';');
} }
type GetParams = ( type ParamRecord = Record<string, number | string | boolean>;
type: string, type GetParams = (type: string, defaults?: ParamRecord) => ParamRecord;
defaults?: Record<string, number | string | boolean>,
) => Record<string, number | string | boolean>;
type GetVisual = ( type GetVisual = (
type: string, type: string,
defaultPlots?: PlotDef[], defaultPlots?: PlotDef[],
defaultHlines?: HLineDef[], defaultHlines?: HLineDef[],
) => { plots: PlotDef[]; hlines: HLineDef[]; cloudColors?: IchimokuCloudColors; bandBackground?: import('../types').HlinesBackground }; ) => {
plots: PlotDef[];
hlines: HLineDef[];
cloudColors?: IchimokuCloudColors;
bandBackground?: import('../types').HlinesBackground;
};
/** 탭 전체 추가 — 기존 차트 지표를 제거하고 types 목록만 새로 구성 */ /** 탭 전체 추가 — 기존 차트 지표를 제거하고 types 목록만 새로 구성 */
export function replaceIndicatorConfigsFromTypes( export function replaceIndicatorConfigsFromTypes(
@@ -163,6 +165,47 @@ export function replaceIndicatorConfigsFromTypes(
return buildIndicatorConfigsFromTypes(types, [], getParams, getVisualConfig, newId); return buildIndicatorConfigsFromTypes(types, [], getParams, getVisualConfig, newId);
} }
/** DB 보조지표 설정(파라미터·색상·선굵기·유형)을 반영한 단일 IndicatorConfig */
export function buildChartIndicatorConfig(
type: string,
id: string,
getParams: GetParams,
getVisualConfig: GetVisual,
extras: Partial<IndicatorConfig> = {},
): 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 제외) */ /** 지표 타입 목록 → 차트에 추가할 IndicatorConfig 배열 (이미 활성인 type 제외) */
export function buildIndicatorConfigsFromTypes( export function buildIndicatorConfigsFromTypes(
types: string[], types: string[],
@@ -176,30 +219,9 @@ export function buildIndicatorConfigsFromTypes(
for (const type of types) { for (const type of types) {
if (seen.has(type)) continue; if (seen.has(type)) continue;
const def = getIndicatorDef(type);
if (!def) continue;
seen.add(type); seen.add(type);
const cfg = buildChartIndicatorConfig(type, newId(), getParams, getVisualConfig);
const params = getParams(type, def.defaultParams); if (cfg) out.push(cfg);
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);
} }
return out; return out;
} }