보조지표 설정 저장 오류
This commit is contained in:
@@ -1589,13 +1589,14 @@ export function useChartWorkspace({
|
||||
cfg = { ...fromConfig, id: newIndId(), type: def.type };
|
||||
} else {
|
||||
const params = getParams(type, def.defaultParams);
|
||||
const { plots, hlines, cloudColors, bandBackground } = getVisualConfig(type, def.plots, def.hlines);
|
||||
const { plots, hlines, cloudColors, bandBackground, plotVisibility } = getVisualConfig(type, def.plots, def.hlines);
|
||||
cfg = {
|
||||
id: newIndId(),
|
||||
type: def.type,
|
||||
params,
|
||||
plots,
|
||||
hlines,
|
||||
...(plotVisibility ? { plotVisibility } : {}),
|
||||
...(cloudColors ? { cloudColors } : {}),
|
||||
...(bandBackground ? { bandBackground } : {}),
|
||||
};
|
||||
@@ -1695,6 +1696,7 @@ export function useChartWorkspace({
|
||||
updated.hlines,
|
||||
updated.cloudColors,
|
||||
updated.bandBackground,
|
||||
updated.plotVisibility,
|
||||
);
|
||||
|
||||
const applyUpdate = (prev: IndicatorConfig[]): IndicatorConfig[] => {
|
||||
@@ -1785,7 +1787,7 @@ export function useChartWorkspace({
|
||||
applyChartIndicators(sortIndicatorsByTypeOrder(chartIndicators));
|
||||
allConfigs.forEach(ind => {
|
||||
saveParams(ind.type, ind.params);
|
||||
saveVisual(ind.type, ind.plots, ind.hlines, ind.cloudColors, ind.bandBackground);
|
||||
saveVisual(ind.type, ind.plots, ind.hlines, ind.cloudColors, ind.bandBackground, ind.plotVisibility);
|
||||
});
|
||||
setShowBulkIndSettings(false);
|
||||
}, [applyChartIndicators, saveParams, saveVisual]);
|
||||
|
||||
@@ -650,6 +650,7 @@ const ChartSlot = forwardRef<ChartSlotHandle, ChartSlotProps>(function ChartSlot
|
||||
updated.hlines,
|
||||
updated.cloudColors,
|
||||
updated.bandBackground,
|
||||
updated.plotVisibility,
|
||||
);
|
||||
}, [saveParams, saveVisual]);
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import type { EvalIndicatorParams } from '../../utils/strategyEvaluationParams';
|
||||
import { DISPLAY_COUNT } from '../../hooks/useUpbitData';
|
||||
import { buildVirtualTradingChartIndicators } from '../../utils/strategyToChartIndicators';
|
||||
import { buildEvalParamsFromStrategy } from '../../utils/strategyEvaluationParams';
|
||||
import { indicatorDefaultsFingerprint } from '../../utils/indicatorDefaultsFingerprint';
|
||||
import { repairUtf8Mojibake } from '../../utils/textEncoding';
|
||||
import StrategyEvaluationBarSelector, {
|
||||
resolveBarIndexAtChartClick,
|
||||
@@ -102,7 +103,7 @@ const StrategyEvaluationChart: React.FC<Props> = ({
|
||||
evalLoading = false,
|
||||
onSignalsChange,
|
||||
}) => {
|
||||
const { getParams: baseGetParams, getVisualConfig: baseGetVisual } = useIndicatorSettings();
|
||||
const { getParams: baseGetParams, getVisualConfig: baseGetVisual, settingsRevision } = useIndicatorSettings();
|
||||
const getParams = getParamsOverride ?? baseGetParams;
|
||||
const getVisualConfig = getVisualConfigOverride ?? baseGetVisual;
|
||||
const { defaults: appDefaults } = useAppSettings();
|
||||
@@ -126,7 +127,7 @@ const StrategyEvaluationChart: React.FC<Props> = ({
|
||||
const indicators = useMemo(() => {
|
||||
if (!strategy) return [] as IndicatorConfig[];
|
||||
return buildVirtualTradingChartIndicators(strategy, getParams, getVisualConfig);
|
||||
}, [strategy, getParams, getVisualConfig]);
|
||||
}, [strategy, getParams, getVisualConfig, settingsRevision]);
|
||||
|
||||
const auxPaneCount = useMemo(
|
||||
() => countNonOverlayIndicatorPanes(indicators, isOverlayType),
|
||||
@@ -145,6 +146,11 @@ const StrategyEvaluationChart: React.FC<Props> = ({
|
||||
[strategy, getParams],
|
||||
);
|
||||
|
||||
const indicatorRenderKey = useMemo(
|
||||
() => indicators.map(i => indicatorDefaultsFingerprint(i)).join('|'),
|
||||
[indicators],
|
||||
);
|
||||
|
||||
const resolveInitialDisplayCount = useCallback(
|
||||
(plotWidth: number) => {
|
||||
const approx = Math.floor(plotWidth / 6);
|
||||
@@ -473,7 +479,7 @@ const StrategyEvaluationChart: React.FC<Props> = ({
|
||||
{!loading && !error && bars.length > 0 && (
|
||||
<>
|
||||
<TradingChart
|
||||
key={`${market}-${timeframe}-${strategy?.id ?? 0}-${paramsRevision}-${indicators.map(i => i.id).join(',')}`}
|
||||
key={`${market}-${timeframe}-${strategy?.id ?? 0}-${paramsRevision}-${settingsRevision}-${indicatorRenderKey}`}
|
||||
bars={bars}
|
||||
barsMarket={market}
|
||||
market={market}
|
||||
|
||||
@@ -47,6 +47,8 @@ type AllSettings = Record<string, ParamMap>;
|
||||
export interface IndicatorVisual {
|
||||
plots?: PlotDef[];
|
||||
hlines?: HLineDef[];
|
||||
/** 지표선 on/off — plot id → visible */
|
||||
plotVisibility?: Record<string, boolean>;
|
||||
cloudColors?: IchimokuCloudColors;
|
||||
/** 볼린저밴드 어퍼~로우어 배경 (업비트 백그라운드 그리기) */
|
||||
bandBackground?: HlinesBackground;
|
||||
@@ -56,6 +58,7 @@ export interface IndicatorVisual {
|
||||
export interface IndicatorVisualConfig {
|
||||
plots: PlotDef[];
|
||||
hlines: HLineDef[];
|
||||
plotVisibility?: Record<string, boolean>;
|
||||
cloudColors?: IchimokuCloudColors;
|
||||
bandBackground?: HlinesBackground;
|
||||
}
|
||||
@@ -243,6 +246,7 @@ export function useIndicatorSettings(sessionKey = 0, enabled = true) {
|
||||
plots: cfg.plots,
|
||||
hlines: cfg.hlines,
|
||||
};
|
||||
if (cfg.plotVisibility) visual.plotVisibility = cfg.plotVisibility;
|
||||
if (cfg.cloudColors) visual.cloudColors = cfg.cloudColors;
|
||||
if (cfg.type === 'BollingerBands' && cfg.bandBackground) {
|
||||
visual.bandBackground = cfg.bandBackground;
|
||||
@@ -321,10 +325,21 @@ export function useIndicatorSettings(sessionKey = 0, enabled = true) {
|
||||
? resolveBbBandBackground(saved?.bandBackground)
|
||||
: undefined;
|
||||
|
||||
const enriched = enrichIndicatorConfig({
|
||||
id: '',
|
||||
type,
|
||||
params: (def?.defaultParams ?? {}) as Record<string, number | string | boolean>,
|
||||
plots,
|
||||
plotVisibility: saved?.plotVisibility,
|
||||
});
|
||||
|
||||
// 깊은 복사: 여러 슬롯이 같은 참조를 공유하지 않도록
|
||||
return {
|
||||
plots: plots.map(p => ({ ...p })),
|
||||
plots: (enriched.plots ?? plots).map(p => ({ ...p })),
|
||||
hlines: hlines.map(h => ({ ...h })),
|
||||
plotVisibility: enriched.plotVisibility
|
||||
? { ...enriched.plotVisibility }
|
||||
: undefined,
|
||||
cloudColors: cloudColors ? { ...cloudColors } : undefined,
|
||||
bandBackground: bandBackground ? { ...bandBackground } : undefined,
|
||||
};
|
||||
@@ -343,8 +358,10 @@ export function useIndicatorSettings(sessionKey = 0, enabled = true) {
|
||||
hlines?: HLineDef[],
|
||||
cloudColors?: IchimokuCloudColors,
|
||||
bandBackground?: HlinesBackground,
|
||||
plotVisibility?: Record<string, boolean>,
|
||||
) => {
|
||||
const visual: IndicatorVisual = { plots, hlines };
|
||||
if (plotVisibility != null) visual.plotVisibility = plotVisibility;
|
||||
if (cloudColors) visual.cloudColors = cloudColors;
|
||||
if (type === 'BollingerBands') {
|
||||
visual.bandBackground = bandBackground ?? resolveBbBandBackground(_visualCache?.[type]?.bandBackground);
|
||||
|
||||
@@ -391,25 +391,43 @@ export async function saveIndicatorSettings(
|
||||
* 앱 시작 시 호출 — 전역 시각 기본값을 DB 에서 가져온다.
|
||||
*/
|
||||
export async function loadIndicatorVisualSettings(): Promise<
|
||||
Record<string, { plots?: unknown[]; hlines?: unknown[]; cloudColors?: unknown; bandBackground?: unknown }>
|
||||
Record<string, {
|
||||
plots?: unknown[];
|
||||
hlines?: unknown[];
|
||||
plotVisibility?: Record<string, boolean>;
|
||||
cloudColors?: unknown;
|
||||
bandBackground?: unknown;
|
||||
}>
|
||||
> {
|
||||
return (
|
||||
(await request<Record<string, { plots?: unknown[]; hlines?: unknown[]; cloudColors?: unknown; bandBackground?: unknown }>>(
|
||||
(await request<Record<string, {
|
||||
plots?: unknown[];
|
||||
hlines?: unknown[];
|
||||
plotVisibility?: Record<string, boolean>;
|
||||
cloudColors?: unknown;
|
||||
bandBackground?: unknown;
|
||||
}>>(
|
||||
'/indicator-settings/visual'
|
||||
)) ?? {}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 지표의 시각 설정(색상·선굵기·수평선)을 저장.
|
||||
* 특정 지표의 시각 설정(색상·선굵기·수평선·지표선 on/off)을 저장.
|
||||
* 사용자가 IndicatorSettingsModal 에서 색상·선굵기를 변경할 때 호출.
|
||||
*
|
||||
* @param indicatorType 지표 타입 (e.g. "RSI")
|
||||
* @param visual { plots: PlotDef[], hlines: HLineDef[] }
|
||||
* @param visual { plots, hlines, plotVisibility, ... }
|
||||
*/
|
||||
export async function saveIndicatorVisualSettings(
|
||||
indicatorType: string,
|
||||
visual: { plots?: unknown[]; hlines?: unknown[]; cloudColors?: unknown; bandBackground?: unknown },
|
||||
visual: {
|
||||
plots?: unknown[];
|
||||
hlines?: unknown[];
|
||||
plotVisibility?: Record<string, boolean>;
|
||||
cloudColors?: unknown;
|
||||
bandBackground?: unknown;
|
||||
},
|
||||
): Promise<void> {
|
||||
await request(`/indicator-settings/${encodeURIComponent(indicatorType)}/visual`, {
|
||||
method: 'PATCH',
|
||||
|
||||
@@ -45,6 +45,7 @@ function overlayWorkspaceVisualOntoStrategyIndicator(
|
||||
hlines: ws.hlines?.length ? ws.hlines : ind.hlines,
|
||||
cloudColors: ws.cloudColors ?? ind.cloudColors,
|
||||
bandBackground: ws.bandBackground ?? ind.bandBackground,
|
||||
plotVisibility: ws.plotVisibility ?? ind.plotVisibility,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -153,6 +154,8 @@ export function mergeGlobalDefaultsOntoChartIndicators(
|
||||
hlines: [],
|
||||
cloudColors: undefined,
|
||||
bandBackground: undefined,
|
||||
/** strat_* — DB visual 기본값(plotVisibility)을 항상 따름 */
|
||||
plotVisibility: undefined,
|
||||
}
|
||||
: ind;
|
||||
const cfg = buildMainIndicatorConfig(ind.type, [baseInd], getParams, getVisualConfig);
|
||||
|
||||
@@ -152,6 +152,7 @@ type GetVisual = (
|
||||
) => {
|
||||
plots: PlotDef[];
|
||||
hlines: HLineDef[];
|
||||
plotVisibility?: Record<string, boolean>;
|
||||
cloudColors?: IchimokuCloudColors;
|
||||
bandBackground?: import('../types').HlinesBackground;
|
||||
};
|
||||
@@ -178,7 +179,7 @@ export function buildChartIndicatorConfig(
|
||||
if (!def) return null;
|
||||
|
||||
const params = getParams(type, def.defaultParams as ParamRecord);
|
||||
const { plots, hlines, cloudColors, bandBackground } = getVisualConfig(
|
||||
const { plots, hlines, cloudColors, bandBackground, plotVisibility } = getVisualConfig(
|
||||
type,
|
||||
def.plots,
|
||||
def.hlines ?? [],
|
||||
@@ -190,6 +191,7 @@ export function buildChartIndicatorConfig(
|
||||
params,
|
||||
plots,
|
||||
hlines,
|
||||
...(plotVisibility ? { plotVisibility } : {}),
|
||||
...(cloudColors ? { cloudColors } : {}),
|
||||
...(bandBackground ? { bandBackground } : {}),
|
||||
...extras,
|
||||
|
||||
@@ -55,7 +55,13 @@ export function initializeIndicatorConfigForEditor(
|
||||
type: string,
|
||||
defaultPlots: PlotDef[],
|
||||
defaultHlines?: HLineDef[],
|
||||
) => { plots: PlotDef[]; hlines: HLineDef[]; cloudColors?: IchimokuCloudColors; bandBackground?: HlinesBackground },
|
||||
) => {
|
||||
plots: PlotDef[];
|
||||
hlines: HLineDef[];
|
||||
plotVisibility?: Record<string, boolean>;
|
||||
cloudColors?: IchimokuCloudColors;
|
||||
bandBackground?: HlinesBackground;
|
||||
},
|
||||
): IndicatorConfig {
|
||||
const def = getIndicatorDef(raw.type);
|
||||
if (!def) return enrichIndicatorConfig(raw);
|
||||
@@ -79,6 +85,7 @@ export function initializeIndicatorConfigForEditor(
|
||||
hlines: raw.hlines?.length ? raw.hlines : visual?.hlines,
|
||||
cloudColors: raw.cloudColors ?? visual?.cloudColors,
|
||||
bandBackground: raw.bandBackground ?? visual?.bandBackground,
|
||||
plotVisibility: raw.plotVisibility ?? visual?.plotVisibility,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -9,14 +9,11 @@ import {
|
||||
getIndicatorDef,
|
||||
getHLineLabel,
|
||||
mergePlotDefs,
|
||||
normalizeObvParams,
|
||||
type HLineDef,
|
||||
} from './indicatorRegistry';
|
||||
import { initializeIndicatorConfigForEditor } from './indicatorSettingsEditor';
|
||||
import { mergeGlobalDefaultsOntoChartIndicators } from './indicatorMainConfig';
|
||||
import { buildMainIndicatorConfig } from './indicatorMainConfig';
|
||||
import { normalizeSmaConfig } from './smaConfig';
|
||||
import { normalizeIchimokuConfig } from './ichimokuConfig';
|
||||
import { buildChartIndicatorConfig } from './indicatorPaneMerge';
|
||||
import type { EditorConditionState } from './strategyConditionSerde';
|
||||
import { collectUiEvaluationTimeframes } from './strategyTimeframeSync';
|
||||
|
||||
@@ -66,7 +63,9 @@ type GetVisual = (
|
||||
) => {
|
||||
plots: import('./indicatorRegistry').PlotDef[];
|
||||
hlines: HLineDef[];
|
||||
plotVisibility?: Record<string, boolean>;
|
||||
cloudColors?: import('./ichimokuConfig').IchimokuCloudColors;
|
||||
bandBackground?: import('../types').HlinesBackground;
|
||||
};
|
||||
|
||||
/** 알림·가상투자 등 전략에서 생성된 차트 지표 id (워크스페이스 ind_* 와 구분) */
|
||||
@@ -205,46 +204,14 @@ function buildOneIndicator(
|
||||
getVisual: GetVisual,
|
||||
stableId?: string,
|
||||
): IndicatorConfig | null {
|
||||
const def = getIndicatorDef(ref.registryType);
|
||||
if (!def) return null;
|
||||
|
||||
const params = getParams(ref.registryType, def.defaultParams as ParamRecord);
|
||||
const { plots, hlines, cloudColors } = getVisual(ref.registryType, def.plots, def.hlines ?? []);
|
||||
|
||||
let cfg: IndicatorConfig = {
|
||||
id: stableId ?? stableStrategyIndicatorId(ref),
|
||||
type: def.type,
|
||||
params: { ...params },
|
||||
plots,
|
||||
hlines,
|
||||
...(cloudColors ? { cloudColors } : {}),
|
||||
};
|
||||
|
||||
/**
|
||||
* 모든 보조지표의 기간(period)은 반드시 지표 설정(getParams, DB 저장값)을 사용한다.
|
||||
* 전략 DSL 조건의 period/leftPeriod/rightPeriod 로 덮어쓰지 않는다.
|
||||
* 조건의 기간 필드는 부정확(미설정 시 1 등)할 수 있어 SMA(1)=본선 겹침 등
|
||||
* 잘못된 그래프를 만든다. 설정에 저장된 값(예: OBV 신호선 9일)이 유일한 기준.
|
||||
* cfg.params 는 이미 getParams(저장 설정값) 결과이므로 그대로 사용.
|
||||
*/
|
||||
if (ref.registryType === 'SMA') {
|
||||
const mainSma = buildMainIndicatorConfig('SMA', [], getParams, getVisual as Parameters<typeof buildMainIndicatorConfig>[3]);
|
||||
cfg = normalizeSmaConfig({
|
||||
...cfg,
|
||||
params: mainSma?.params ?? cfg.params,
|
||||
plots: mainSma?.plots ?? cfg.plots,
|
||||
plotVisibility: mainSma?.plotVisibility ?? cfg.plotVisibility,
|
||||
});
|
||||
} else if (ref.registryType === 'OBV') {
|
||||
cfg = {
|
||||
...cfg,
|
||||
params: normalizeObvParams({ ...cfg.params }),
|
||||
};
|
||||
}
|
||||
|
||||
if (ref.registryType === 'IchimokuCloud') {
|
||||
cfg = normalizeIchimokuConfig(cfg);
|
||||
}
|
||||
const id = stableId ?? stableStrategyIndicatorId(ref);
|
||||
let cfg = buildChartIndicatorConfig(
|
||||
ref.registryType,
|
||||
id,
|
||||
getParams,
|
||||
getVisual as Parameters<typeof buildChartIndicatorConfig>[3],
|
||||
);
|
||||
if (!cfg) return null;
|
||||
|
||||
if (ref.targetValue != null) {
|
||||
cfg = { ...cfg, hlines: applyTargetHlines(cfg.hlines ?? [], ref.targetValue) };
|
||||
|
||||
Reference in New Issue
Block a user