백테스팅 차트 색상 오류 수정
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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],
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user