/** * MA/EMA DSL 필드 — MA1~MA11 슬롯 ↔ SMA period1~11 (보조지표 설정과 동일) * * - MA3 → period3 (예: 20일) * - MA20 → 레거시: 숫자>11 이면 기간 20일 그대로 */ import { SMA_DEFAULT_PERIODS, SMA_MA_COUNT, formatSmaPeriodDisplayLabel } from './smaConfig'; export const MA_DSL_SLOT_MAX = SMA_MA_COUNT; export function buildMaLinesFromSmaParams( params?: Record, ): number[] { const lines: number[] = []; for (let slot = 1; slot <= SMA_MA_COUNT; slot++) { const raw = params?.[`period${slot}`]; const p = typeof raw === 'number' && raw > 0 ? Math.floor(raw) : SMA_DEFAULT_PERIODS[slot - 1]; lines.push(p); } return lines; } /** DSL MA/EMA 필드 → 실제 SMA/EMA 계산 기간 */ export function resolveMaDslFieldPeriod( field: string | undefined, smaParams?: Record, ): number | null { if (!field) return null; const ma = /^MA(\d+)$/.exec(field); if (ma && !field.startsWith('MACD')) { const n = parseInt(ma[1], 10); if (!Number.isFinite(n) || n <= 0) return null; if (n <= MA_DSL_SLOT_MAX) return buildMaLinesFromSmaParams(smaParams)[n - 1]; return n; } const ema = /^EMA(\d+)$/.exec(field); if (ema) { const n = parseInt(ema[1], 10); if (!Number.isFinite(n) || n <= 0) return null; if (n <= MA_DSL_SLOT_MAX) return buildMaLinesFromSmaParams(smaParams)[n - 1]; return n; } return null; } /** EMA 설정 기간 → EMA5일 */ export function formatEmaPeriodDisplayLabel(period: number | string | boolean | undefined): string { const raw = typeof period === 'number' ? period : Number(period); const p = Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 1; return `EMA${p}일`; } /** UI·해석용 라벨 — MA20일 */ export function formatMaDslFieldLabel( field: string | undefined, smaParams?: Record, ): string { if (!field) return ''; const period = resolveMaDslFieldPeriod(field, smaParams); if (period == null) return field; if (field.startsWith('EMA')) return formatEmaPeriodDisplayLabel(period); if (field.startsWith('MA') && !field.startsWith('MACD')) return formatSmaPeriodDisplayLabel(period); return field; } export function buildMaFieldOptions( maLines: number[], ): { value: string; label: string }[] { return Array.from({ length: SMA_MA_COUNT }, (_, i) => { const slot = i + 1; const p = maLines[i] ?? SMA_DEFAULT_PERIODS[i]; return { value: `MA${slot}`, label: formatSmaPeriodDisplayLabel(p) }; }); } export function buildEmaFieldOptions( maLines: number[], maxSlots = 4, ): { value: string; label: string }[] { const n = Math.min(maxSlots, SMA_MA_COUNT); return Array.from({ length: n }, (_, i) => { const slot = i + 1; const p = maLines[i] ?? SMA_DEFAULT_PERIODS[i]; return { value: `EMA${slot}`, label: formatEmaPeriodDisplayLabel(p) }; }); } /** 레거시 MA20·MA60(기간 인코딩) 옵션 유지 */ export function appendLegacyMaFieldOption( opts: { value: string; label: string }[], field: string | undefined, ): { value: string; label: string }[] { if (!field || opts.some(o => o.value === field)) return opts; const period = resolveMaDslFieldPeriod(field); if (period == null) return opts; const ma = /^MA(\d+)$/.exec(field); if (ma && !field.startsWith('MACD')) { const n = parseInt(ma[1], 10); if (n > MA_DSL_SLOT_MAX) { return [...opts, { value: field, label: formatSmaPeriodDisplayLabel(period) }]; } } const ema = /^EMA(\d+)$/.exec(field); if (ema) { const n = parseInt(ema[1], 10); if (n > MA_DSL_SLOT_MAX) { return [...opts, { value: field, label: formatEmaPeriodDisplayLabel(period) }]; } } return opts; }