백테스트 전략이름, 이동평균선 그래프 오류 수정
This commit is contained in:
@@ -41,6 +41,7 @@ import {
|
|||||||
import '../styles/strategyEditorTheme.css';
|
import '../styles/strategyEditorTheme.css';
|
||||||
import { getKoreanName } from '../utils/marketNameCache';
|
import { getKoreanName } from '../utils/marketNameCache';
|
||||||
import { repairUtf8Mojibake } from '../utils/textEncoding';
|
import { repairUtf8Mojibake } from '../utils/textEncoding';
|
||||||
|
import { enrichStrategyNameMap, strategyNamesFromList } from '../utils/strategyNameResolver';
|
||||||
|
|
||||||
const LEFT_KEY = 'btd-left-width';
|
const LEFT_KEY = 'btd-left-width';
|
||||||
const RIGHT_KEY = 'btd-right-width';
|
const RIGHT_KEY = 'btd-right-width';
|
||||||
@@ -114,7 +115,11 @@ export function BacktestHistoryPage({ theme = 'dark' }: Props) {
|
|||||||
loadPaperSummary(),
|
loadPaperSummary(),
|
||||||
loadStrategies(),
|
loadStrategies(),
|
||||||
]);
|
]);
|
||||||
const strategyNames = Object.fromEntries(strategies.map(s => [s.id, s.name]));
|
const baseNames = strategyNamesFromList(strategies);
|
||||||
|
const strategyNames = await enrichStrategyNameMap(
|
||||||
|
baseNames,
|
||||||
|
trades.map(t => t.strategyId),
|
||||||
|
);
|
||||||
const live = buildLiveExecutionItems(trades, summary, strategyNames);
|
const live = buildLiveExecutionItems(trades, summary, strategyNames);
|
||||||
setLiveItems(live);
|
setLiveItems(live);
|
||||||
setSelectedLive(prev => (prev && live.some(x => x.id === prev.id) ? prev : live[0] ?? null));
|
setSelectedLive(prev => (prev && live.some(x => x.id === prev.id) ? prev : live[0] ?? null));
|
||||||
@@ -128,10 +133,24 @@ export function BacktestHistoryPage({ theme = 'dark' }: Props) {
|
|||||||
loadPaperSummary(),
|
loadPaperSummary(),
|
||||||
loadStrategies(),
|
loadStrategies(),
|
||||||
]);
|
]);
|
||||||
const strategyNames = Object.fromEntries(strategies.map(s => [s.id, s.name]));
|
const baseNames = strategyNamesFromList(strategies);
|
||||||
|
const strategyNames = await enrichStrategyNameMap(
|
||||||
|
baseNames,
|
||||||
|
[
|
||||||
|
...trades.map(t => t.strategyId),
|
||||||
|
...list.map(r => r.strategyId),
|
||||||
|
],
|
||||||
|
);
|
||||||
const live = buildLiveExecutionItems(trades, summary, strategyNames);
|
const live = buildLiveExecutionItems(trades, summary, strategyNames);
|
||||||
|
const enrichedRecords = list.map(r => ({
|
||||||
|
...r,
|
||||||
|
strategyName: r.strategyName?.trim()
|
||||||
|
|| (r.strategyId != null ? strategyNames[r.strategyId] : undefined)
|
||||||
|
|| r.strategyName
|
||||||
|
|| '전략 없음',
|
||||||
|
}));
|
||||||
setStrategies(strategies);
|
setStrategies(strategies);
|
||||||
setRecords(list);
|
setRecords(enrichedRecords);
|
||||||
setLiveItems(live);
|
setLiveItems(live);
|
||||||
setSelectedBacktest(prev => (prev && list.some(x => x.id === prev.id) ? prev : list[0] ?? null));
|
setSelectedBacktest(prev => (prev && list.some(x => x.id === prev.id) ? prev : list[0] ?? null));
|
||||||
setSelectedLive(prev => (prev && live.some(x => x.id === prev.id) ? prev : live[0] ?? null));
|
setSelectedLive(prev => (prev && live.some(x => x.id === prev.id) ? prev : live[0] ?? null));
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
buildBacktestReportModel,
|
buildBacktestReportModel,
|
||||||
buildLiveReportModel,
|
buildLiveReportModel,
|
||||||
} from '../../utils/backtestReportModel';
|
} from '../../utils/backtestReportModel';
|
||||||
|
import { enrichStrategyNameMap, strategyNamesFromList } from '../../utils/strategyNameResolver';
|
||||||
import '../../styles/backtestDashboard.css';
|
import '../../styles/backtestDashboard.css';
|
||||||
import '../../styles/analysisReportPage.css';
|
import '../../styles/analysisReportPage.css';
|
||||||
|
|
||||||
@@ -53,17 +54,25 @@ export function AnalysisReportPage({ theme = 'dark' }: Props) {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [reportOpen, setReportOpen] = useState(false);
|
const [reportOpen, setReportOpen] = useState(false);
|
||||||
|
|
||||||
const refreshLive = useCallback(async () => {
|
const refreshLive = useCallback(async (backtestRecords: BacktestResultRecord[] = []) => {
|
||||||
const [trades, summary, stratList] = await Promise.all([
|
const [trades, summary, stratList] = await Promise.all([
|
||||||
loadPaperTrades(),
|
loadPaperTrades(),
|
||||||
loadPaperSummary(),
|
loadPaperSummary(),
|
||||||
loadStrategies(),
|
loadStrategies(),
|
||||||
]);
|
]);
|
||||||
setStrategies(stratList);
|
setStrategies(stratList);
|
||||||
const strategyNames = Object.fromEntries(stratList.map(s => [s.id, s.name]));
|
const baseNames = strategyNamesFromList(stratList);
|
||||||
|
const strategyNames = await enrichStrategyNameMap(
|
||||||
|
baseNames,
|
||||||
|
[
|
||||||
|
...trades.map(t => t.strategyId),
|
||||||
|
...backtestRecords.map(r => r.strategyId),
|
||||||
|
],
|
||||||
|
);
|
||||||
const live = buildLiveExecutionItems(trades, summary, strategyNames);
|
const live = buildLiveExecutionItems(trades, summary, strategyNames);
|
||||||
setLiveItems(live);
|
setLiveItems(live);
|
||||||
setSelectedLive(prev => (prev && live.some(x => x.id === prev.id) ? prev : live[0] ?? null));
|
setSelectedLive(prev => (prev && live.some(x => x.id === prev.id) ? prev : live[0] ?? null));
|
||||||
|
return strategyNames;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const fetchAll = useCallback(async () => {
|
const fetchAll = useCallback(async () => {
|
||||||
@@ -73,10 +82,19 @@ export function AnalysisReportPage({ theme = 'dark' }: Props) {
|
|||||||
loadBacktestResults(),
|
loadBacktestResults(),
|
||||||
loadStrategies(),
|
loadStrategies(),
|
||||||
]);
|
]);
|
||||||
setRecords(recs);
|
|
||||||
setStrategies(stratList);
|
setStrategies(stratList);
|
||||||
setSelectedBacktest(prev => (prev && recs.some(r => r.id === prev.id) ? prev : recs[0] ?? null));
|
const strategyNames = await refreshLive(recs);
|
||||||
await refreshLive();
|
const enrichedRecords = recs.map(r => ({
|
||||||
|
...r,
|
||||||
|
strategyName: r.strategyName?.trim()
|
||||||
|
|| (r.strategyId != null ? strategyNames[r.strategyId] : undefined)
|
||||||
|
|| r.strategyName
|
||||||
|
|| '전략 없음',
|
||||||
|
}));
|
||||||
|
setRecords(enrichedRecords);
|
||||||
|
setSelectedBacktest(prev => (
|
||||||
|
prev && enrichedRecords.some(r => r.id === prev.id) ? prev : enrichedRecords[0] ?? null
|
||||||
|
));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -84,10 +102,10 @@ export function AnalysisReportPage({ theme = 'dark' }: Props) {
|
|||||||
|
|
||||||
useEffect(() => { void fetchAll(); }, [fetchAll]);
|
useEffect(() => { void fetchAll(); }, [fetchAll]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onPaper = () => { void refreshLive(); };
|
const onPaper = () => { void refreshLive(records); };
|
||||||
window.addEventListener(PAPER_TRADES_CHANGED_EVENT, onPaper);
|
window.addEventListener(PAPER_TRADES_CHANGED_EVENT, onPaper);
|
||||||
return () => window.removeEventListener(PAPER_TRADES_CHANGED_EVENT, onPaper);
|
return () => window.removeEventListener(PAPER_TRADES_CHANGED_EVENT, onPaper);
|
||||||
}, [refreshLive]);
|
}, [refreshLive, records]);
|
||||||
|
|
||||||
const compareBacktest = useMemo(() => {
|
const compareBacktest = useMemo(() => {
|
||||||
if (!selectedBacktest) return null;
|
if (!selectedBacktest) return null;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useRef, useState, type MutableRefObject } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState, type MutableRefObject } from 'react';
|
||||||
import TradingChart from '../TradingChart';
|
import TradingChart from '../TradingChart';
|
||||||
import type { BacktestSignal, StrategyDto } from '../../utils/backendApi';
|
import type { BacktestSignal, StrategyDto } from '../../utils/backendApi';
|
||||||
import { loadStrategy } from '../../utils/backendApi';
|
import { loadStrategyForNotification } from '../../utils/backendApi';
|
||||||
import type { ChartType, IndicatorConfig, OHLCVBar, Theme, Timeframe } from '../../types';
|
import type { ChartType, IndicatorConfig, OHLCVBar, Theme, Timeframe } from '../../types';
|
||||||
import { loadAnalysisCandles } from '../../utils/analysisChartData';
|
import { loadAnalysisCandles } from '../../utils/analysisChartData';
|
||||||
import type { ChartManager } from '../../utils/ChartManager';
|
import type { ChartManager } from '../../utils/ChartManager';
|
||||||
@@ -22,12 +22,6 @@ import {
|
|||||||
chartPaneFlexRatio,
|
chartPaneFlexRatio,
|
||||||
countNonOverlayIndicatorPanes,
|
countNonOverlayIndicatorPanes,
|
||||||
} from '../../utils/strategyOscillatorSeries';
|
} from '../../utils/strategyOscillatorSeries';
|
||||||
import {
|
|
||||||
createDefaultSmaPlotVisibility,
|
|
||||||
normalizeSmaConfig,
|
|
||||||
smaPeriodKey,
|
|
||||||
smaPlotId,
|
|
||||||
} from '../../utils/smaConfig';
|
|
||||||
import { buildChartIndicatorConfig } from '../../utils/indicatorPaneMerge';
|
import { buildChartIndicatorConfig } from '../../utils/indicatorPaneMerge';
|
||||||
import {
|
import {
|
||||||
applyPaperOverlayVisibility,
|
applyPaperOverlayVisibility,
|
||||||
@@ -87,15 +81,7 @@ function defaultOverlayIndicators(
|
|||||||
const cfg = buildChartIndicatorConfig('SMA', newIndId(), getParams, getVisualConfig, {
|
const cfg = buildChartIndicatorConfig('SMA', newIndId(), getParams, getVisualConfig, {
|
||||||
timeframeVisibility: ALL_TF_VISIBLE,
|
timeframeVisibility: ALL_TF_VISIBLE,
|
||||||
});
|
});
|
||||||
if (!cfg) return [];
|
return cfg ? [cfg] : [];
|
||||||
// 전략 미선택 시 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 으로 추가 */
|
||||||
@@ -139,6 +125,7 @@ const BacktestAnalysisChart: React.FC<Props> = ({
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [chartType] = useState<ChartType>('candlestick');
|
const [chartType] = useState<ChartType>('candlestick');
|
||||||
const [strategy, setStrategy] = useState<StrategyDto | undefined>();
|
const [strategy, setStrategy] = useState<StrategyDto | undefined>();
|
||||||
|
const [strategyLoading, setStrategyLoading] = useState(false);
|
||||||
/** 개별 지표 숨김 ID 집합 (실시간 차트와 동일한 per-indicator 숨김 지원) */
|
/** 개별 지표 숨김 ID 집합 (실시간 차트와 동일한 per-indicator 숨김 지원) */
|
||||||
const [hiddenIndicatorIds, setHiddenIndicatorIds] = useState<ReadonlySet<string>>(new Set());
|
const [hiddenIndicatorIds, setHiddenIndicatorIds] = useState<ReadonlySet<string>>(new Set());
|
||||||
const managerRef = useRef<ChartManager | null>(null);
|
const managerRef = useRef<ChartManager | null>(null);
|
||||||
@@ -162,11 +149,16 @@ const BacktestAnalysisChart: React.FC<Props> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!strategyId) {
|
if (!strategyId) {
|
||||||
setStrategy(undefined);
|
setStrategy(undefined);
|
||||||
|
setStrategyLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
void loadStrategy(strategyId).then(s => {
|
setStrategyLoading(true);
|
||||||
if (!cancelled) setStrategy(s ?? undefined);
|
void loadStrategyForNotification(strategyId).then(s => {
|
||||||
|
if (!cancelled) {
|
||||||
|
setStrategy(s ?? undefined);
|
||||||
|
setStrategyLoading(false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return () => { cancelled = true; };
|
return () => { cancelled = true; };
|
||||||
}, [strategyId]);
|
}, [strategyId]);
|
||||||
@@ -181,27 +173,24 @@ const BacktestAnalysisChart: React.FC<Props> = ({
|
|||||||
const showOscillatorPanel = !overlayVisibility;
|
const showOscillatorPanel = !overlayVisibility;
|
||||||
|
|
||||||
const baseIndicators = useMemo(() => {
|
const baseIndicators = useMemo(() => {
|
||||||
|
if (strategyId && strategyLoading) return [];
|
||||||
|
|
||||||
let inds: IndicatorConfig[];
|
let inds: IndicatorConfig[];
|
||||||
if (strategy) {
|
if (strategy) {
|
||||||
inds = buildVirtualTradingChartIndicators(strategy, getParams, getVisualConfig);
|
inds = buildVirtualTradingChartIndicators(strategy, getParams, getVisualConfig);
|
||||||
inds = inds.filter(i => i.timeframeVisibility?.[chartTimeframe] !== false);
|
inds = inds.filter(i => i.timeframeVisibility?.[chartTimeframe] !== false);
|
||||||
// 전략 지표가 모두 캔들 오버레이(SMA/EMA/일목 등)인 경우 기본 오실레이터(RSI·MACD)를 추가.
|
|
||||||
// 기존 SVG 기반 StrategyOscillatorPanes 의 fallback 동작과 동일.
|
|
||||||
if (showOscillatorPanel && inds.every(i => isOverlayType(i.type))) {
|
if (showOscillatorPanel && inds.every(i => isOverlayType(i.type))) {
|
||||||
inds = [...inds, ...defaultOscillatorIndicators(getParams, getVisualConfig)];
|
inds = [...inds, ...defaultOscillatorIndicators(getParams, getVisualConfig)];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
inds = defaultOverlayIndicators(getParams, getVisualConfig);
|
inds = defaultOverlayIndicators(getParams, getVisualConfig);
|
||||||
// 전략 미선택 시 기본 오실레이터(RSI, MACD)도 TradingChart sub-pane 으로 추가
|
|
||||||
if (showOscillatorPanel) {
|
if (showOscillatorPanel) {
|
||||||
inds = [...inds, ...defaultOscillatorIndicators(getParams, getVisualConfig)];
|
inds = [...inds, ...defaultOscillatorIndicators(getParams, getVisualConfig)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (overlayVisibility) {
|
|
||||||
inds = ensurePaperChartOverlays(inds, getParams, getVisualConfig);
|
inds = ensurePaperChartOverlays(inds, getParams, getVisualConfig);
|
||||||
}
|
|
||||||
return inds;
|
return inds;
|
||||||
}, [strategy, getParams, getVisualConfig, chartTimeframe, overlayVisibility, showOscillatorPanel]);
|
}, [strategy, strategyId, strategyLoading, getParams, getVisualConfig, chartTimeframe, overlayVisibility, showOscillatorPanel]);
|
||||||
|
|
||||||
const indicators = useMemo(() => {
|
const indicators = useMemo(() => {
|
||||||
let inds = overlayVisibility
|
let inds = overlayVisibility
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { IndicatorConfig } from '../types';
|
|||||||
import { enrichIndicatorConfig, getIndicatorDef } from './indicatorRegistry';
|
import { enrichIndicatorConfig, getIndicatorDef } from './indicatorRegistry';
|
||||||
import { normalizeSmaConfig, createDefaultSmaPlotVisibility } from './smaConfig';
|
import { normalizeSmaConfig, createDefaultSmaPlotVisibility } from './smaConfig';
|
||||||
import { normalizeIchimokuConfig } from './ichimokuConfig';
|
import { normalizeIchimokuConfig } from './ichimokuConfig';
|
||||||
|
import { buildMainIndicatorConfig } from './indicatorMainConfig';
|
||||||
import type { PlotDef, HLineDef } from './indicatorRegistry';
|
import type { PlotDef, HLineDef } from './indicatorRegistry';
|
||||||
import type { IchimokuCloudColors } from './ichimokuConfig';
|
import type { IchimokuCloudColors } from './ichimokuConfig';
|
||||||
|
|
||||||
@@ -195,6 +196,13 @@ export function buildChartIndicatorConfig(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (type === 'SMA') {
|
if (type === 'SMA') {
|
||||||
|
const mainSma = buildMainIndicatorConfig(type, [], getParams, getVisualConfig);
|
||||||
|
if (mainSma?.plotVisibility) {
|
||||||
|
cfg.plotVisibility = mainSma.plotVisibility;
|
||||||
|
}
|
||||||
|
if (mainSma?.plots?.length) {
|
||||||
|
cfg.plots = mainSma.plots;
|
||||||
|
}
|
||||||
cfg = normalizeSmaConfig({
|
cfg = normalizeSmaConfig({
|
||||||
...cfg,
|
...cfg,
|
||||||
plotVisibility: cfg.plotVisibility ?? createDefaultSmaPlotVisibility(),
|
plotVisibility: cfg.plotVisibility ?? createDefaultSmaPlotVisibility(),
|
||||||
|
|||||||
@@ -32,12 +32,15 @@ function sourceLabel(source: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolveStrategyLabel(
|
function resolveStrategyLabel(
|
||||||
t: PaperTradeDto,
|
trades: PaperTradeDto[],
|
||||||
strategyNames: Record<number, string>,
|
strategyNames: Record<number, string>,
|
||||||
): string {
|
): string {
|
||||||
if (t.strategyName) return t.strategyName;
|
for (const t of trades) {
|
||||||
if (t.strategyId != null) {
|
if (t.strategyName?.trim()) return t.strategyName.trim();
|
||||||
return strategyNames[t.strategyId] ?? `전략 #${t.strategyId}`;
|
}
|
||||||
|
const first = trades[0];
|
||||||
|
if (first?.strategyId != null) {
|
||||||
|
return strategyNames[first.strategyId] ?? `전략 #${first.strategyId}`;
|
||||||
}
|
}
|
||||||
return '수동 매매';
|
return '수동 매매';
|
||||||
}
|
}
|
||||||
@@ -83,7 +86,7 @@ export function buildLiveExecutionItems(
|
|||||||
return {
|
return {
|
||||||
id: key,
|
id: key,
|
||||||
symbol: first.symbol,
|
symbol: first.symbol,
|
||||||
strategyLabel: resolveStrategyLabel(first, strategyNames),
|
strategyLabel: resolveStrategyLabel(sorted, strategyNames),
|
||||||
sourceLabel: sourceLabel(first.source),
|
sourceLabel: sourceLabel(first.source),
|
||||||
timeframe: first.candleType ?? 'unknown',
|
timeframe: first.candleType ?? 'unknown',
|
||||||
executionType: first.executionType ?? undefined,
|
executionType: first.executionType ?? undefined,
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* 전략 ID → 이름 해석 (목록에 없거나 삭제된 전략 포함)
|
||||||
|
*/
|
||||||
|
import { loadStrategyForNotification, type StrategyDto } from './backendApi';
|
||||||
|
|
||||||
|
/** 전략 목록 + 누락 ID별 단건 조회로 이름 맵 보강 */
|
||||||
|
export async function enrichStrategyNameMap(
|
||||||
|
base: Record<number, string>,
|
||||||
|
ids: Iterable<number | null | undefined>,
|
||||||
|
): Promise<Record<number, string>> {
|
||||||
|
const out = { ...base };
|
||||||
|
const missing = [...new Set(
|
||||||
|
[...ids]
|
||||||
|
.map(id => (id != null && id > 0 ? Math.trunc(id) : null))
|
||||||
|
.filter((id): id is number => id != null && !out[id]),
|
||||||
|
)];
|
||||||
|
if (!missing.length) return out;
|
||||||
|
|
||||||
|
await Promise.all(missing.map(async id => {
|
||||||
|
try {
|
||||||
|
const s = await loadStrategyForNotification(id);
|
||||||
|
if (s?.name?.trim()) out[id] = s.name.trim();
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function strategyNamesFromList(list: StrategyDto[]): Record<number, string> {
|
||||||
|
const out: Record<number, string> = {};
|
||||||
|
for (const s of list) {
|
||||||
|
if (s.id != null && s.id > 0 && s.name?.trim()) {
|
||||||
|
out[s.id] = s.name.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
@@ -10,7 +10,8 @@ import {
|
|||||||
getHLineLabel,
|
getHLineLabel,
|
||||||
type HLineDef,
|
type HLineDef,
|
||||||
} from './indicatorRegistry';
|
} from './indicatorRegistry';
|
||||||
import { createDefaultSmaPlotVisibility, normalizeSmaConfig, smaPeriodKey } from './smaConfig';
|
import { buildMainIndicatorConfig } from './indicatorMainConfig';
|
||||||
|
import { normalizeSmaConfig, smaPeriodKey } from './smaConfig';
|
||||||
import { normalizeIchimokuConfig } from './ichimokuConfig';
|
import { normalizeIchimokuConfig } from './ichimokuConfig';
|
||||||
import { extractVirtualConditions } from './virtualStrategyConditions';
|
import { extractVirtualConditions } from './virtualStrategyConditions';
|
||||||
|
|
||||||
@@ -183,18 +184,20 @@ function buildOneIndicator(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (ref.registryType === 'SMA') {
|
if (ref.registryType === 'SMA') {
|
||||||
|
const mainSma = buildMainIndicatorConfig('SMA', [], getParams, getVisual as Parameters<typeof buildMainIndicatorConfig>[3]);
|
||||||
const periods = [ref.period, ref.leftPeriod, ref.rightPeriod]
|
const periods = [ref.period, ref.leftPeriod, ref.rightPeriod]
|
||||||
.filter((p): p is number => typeof p === 'number' && p > 0);
|
.filter((p): p is number => typeof p === 'number' && p > 0);
|
||||||
if (periods.length > 0) {
|
|
||||||
const p = { ...cfg.params };
|
const p = { ...cfg.params };
|
||||||
|
if (periods.length > 0) {
|
||||||
periods.slice(0, 11).forEach((val, i) => {
|
periods.slice(0, 11).forEach((val, i) => {
|
||||||
p[smaPeriodKey(i + 1)] = val;
|
p[smaPeriodKey(i + 1)] = val;
|
||||||
});
|
});
|
||||||
cfg = { ...cfg, params: p };
|
|
||||||
}
|
}
|
||||||
cfg = normalizeSmaConfig({
|
cfg = normalizeSmaConfig({
|
||||||
...cfg,
|
...cfg,
|
||||||
plotVisibility: cfg.plotVisibility ?? createDefaultSmaPlotVisibility(),
|
params: p,
|
||||||
|
plots: mainSma?.plots ?? cfg.plots,
|
||||||
|
plotVisibility: mainSma?.plotVisibility ?? cfg.plotVisibility,
|
||||||
});
|
});
|
||||||
} else if (ref.period != null && ref.period > 0) {
|
} else if (ref.period != null && ref.period > 0) {
|
||||||
cfg = {
|
cfg = {
|
||||||
|
|||||||
Reference in New Issue
Block a user