import React from 'react'; import type { IndicatorConfig, HlinesBackground, Timeframe } from '../types'; import type { PlotDef, HLineDef } from '../utils/indicatorRegistry'; import { getHLineLabel } from '../utils/indicatorRegistry'; import { getParamLabel, getPlotLabel, getSrcOptionLabel } from '../utils/indicatorLabels'; import { getIchimokuPlotTitle } from '../utils/ichimokuConfig'; import { getNumericParamSpec, getHlinePriceSpec, } from '../utils/indicatorParamSpec'; import { getGlobalParamKeys, getPlotParamKeys, plotParamsOnGlobalRowOnly, plotRowHasNoInputs, } from '../utils/indicatorSettingsLayout'; import NumericParamInput from './NumericParamInput'; import PlotLineStylePicker from './PlotLineStylePicker'; import ColorInput from './ColorInput'; import { colorToRgbaString, cloudColorOpacityPercent } from '../utils/ichimokuConfig'; import { resolveHistogramUpColor, resolveHistogramDownColor } from '../utils/plotColorUtils'; import type { IchimokuCloudColors } from '../utils/ichimokuConfig'; const SRC_OPTIONS = ['close', 'open', 'high', 'low', 'hl2', 'hlc3', 'ohlc4']; const MA_TYPE_OPTIONS_CCI = ['None', 'SMA', 'EMA', 'WMA'] as const; const MA_TYPE_OPTIONS_OBV = ['SMA', 'EMA', 'WMA'] as const; /** 업비트 OBV 드롭다운 표기 */ const OBV_MA_TYPE_LABELS: Record = { SMA: '단순이동평균', EMA: '지수 이동 평균', WMA: '가중이동평균', }; const MA_TYPE_LABELS: Record = { None: '없음 (None)', SMA: '단순이동평균 (SMA)', EMA: '지수이동평균 (EMA)', WMA: '가중이동평균 (WMA)', 'SMMA (RMA)': 'RMA (SMMA)', VWMA: '거래량가중이동평균 (VWMA)', }; function isMaTypeParam(indicatorType: string, key: string, value: unknown): boolean { if (key !== 'maType' || typeof value !== 'string') return false; return indicatorType === 'CCI' || indicatorType === 'OBV' || indicatorType === 'RSI'; } function maTypeOptionsFor(indicatorType: string): readonly string[] { if (indicatorType === 'OBV') return MA_TYPE_OPTIONS_OBV; return MA_TYPE_OPTIONS_CCI; } function isSrcParam(key: string, value: unknown): boolean { return (key === 'src' || key.toLowerCase().includes('src')) && typeof value === 'string'; } export { ColorInput }; /* ── 단일 파라미터 컨트롤 ─────────────────────────────────────── */ export const ParamField: React.FC<{ indicatorType: string; paramKey: string; value: number | string | boolean; disabled?: boolean; onChange: (key: string, raw: string | boolean) => void; }> = ({ indicatorType, paramKey, value, disabled, onChange }) => { if (typeof value === 'boolean') { return ( ); } if (isMaTypeParam(indicatorType, paramKey, value)) { return ( ); } if (isSrcParam(paramKey, value)) { return ( ); } if (typeof value === 'number') { return ( onChange(paramKey, String(v))} /> ); } return ( onChange(paramKey, e.target.value)} /> ); }; /* ── 플롯 설정 행 (on/off · 이름 · 입력 · 색상·선) ─────────────── */ export const PlotSettingsRow: React.FC<{ indicatorType: string; plot: PlotDef; plotIndex: number; plots: PlotDef[]; params: Record; enabled: boolean; inputsOnly?: boolean; onToggle: (plotId: string, on: boolean) => void; onParamChange: (key: string, raw: string | boolean) => void; onPlotStyle: (plotIndex: number, patch: Partial) => void; }> = ({ indicatorType, plot, plotIndex, plots, params, enabled, inputsOnly, onToggle, onParamChange, onPlotStyle, }) => { const paramKeys = getPlotParamKeys(indicatorType, plot.id, plotIndex, plots, params); const noInputs = plotRowHasNoInputs(indicatorType, plot.id, plotIndex, plots, params); const globalOnlyInputs = plotParamsOnGlobalRowOnly(indicatorType, plot.id); const label = indicatorType === 'IchimokuCloud' ? getIchimokuPlotTitle(plot.id) : getPlotLabel(plot.title); const isHistogram = plot.type === 'histogram'; return (
{noInputs && globalOnlyInputs ? ( ) : noInputs ? ( 자동 ) : (
{paramKeys.map(key => ( ))}
)} {!inputsOnly && ( isHistogram && indicatorType === 'MACD' ? (
0선 위 onPlotStyle(plotIndex, { histogramUpColor: v, color: v })} />
0선 아래 onPlotStyle(plotIndex, { histogramDownColor: v })} />
) : isHistogram ? ( onPlotStyle(plotIndex, { color: v })} /> ) : ( onPlotStyle(plotIndex, patch)} /> ) )}
); }; /* ── 전역 파라미터 행 (토글 없음) ─────────────────────────────── */ export const GlobalParamRow: React.FC<{ indicatorType: string; paramKey: string; value: number | string | boolean; onChange: (key: string, raw: string | boolean) => void; }> = ({ indicatorType, paramKey, value, onChange }) => (
{getParamLabel(paramKey, indicatorType)}
); /* ── 수평선 행 ───────────────────────────────────────────────── */ export const HLineSettingsRow: React.FC<{ indicatorType: string; hl: HLineDef; idx: number; allPrices: number[]; inputsOnly?: boolean; onVisible: (idx: number, v: boolean) => void; onPrice: (idx: number, v: number) => void; onStyle: (idx: number, patch: Partial) => void; }> = ({ indicatorType, hl, idx, allPrices, inputsOnly, onVisible, onPrice, onStyle }) => { const label = hl.label ?? getHLineLabel(hl.price, allPrices); const isOn = hl.visible !== false; return (
onPrice(idx, v)} /> {!inputsOnly && ( onStyle(idx, patch)} /> )}
); }; /* ── 출력 · 타임프레임 (하단 공통) ───────────────────────────── */ export const SettingsOutputFooter: React.FC<{ lastValueVisible: boolean; onLastValueVisible: (v: boolean) => void; timeframeVis: Partial>; onTimeframeVis: (tf: Timeframe, v: boolean) => void; timeframes: { tf: Timeframe; label: string }[]; /** 개별 설정 모달: 지표 전체 숨김 */ chartHidden?: boolean; onChartHidden?: (showOnChart: boolean) => void; }> = ({ lastValueVisible, onLastValueVisible, timeframeVis, onTimeframeVis, timeframes, chartHidden, onChartHidden, }) => ( <>
출력 · 표시
{onChartHidden != null && (
차트에 표시
)}
가격축 라벨·설명
{timeframes.map(({ tf, label }) => ( ))}
); /* ── 일목 구름 색상 ─────────────────────────────────────────── */ export const IchimokuCloudSection: React.FC<{ cloudColors: IchimokuCloudColors; onChange: (patch: Partial) => void; }> = ({ cloudColors, onChange }) => { const bullishOn = cloudColors.bullishVisible !== false; const bearishOn = cloudColors.bearishVisible !== false; return ( <>

구름 영역

선행스팬1·선행스팬2가 모두 켜져 있을 때만 구름을 그릴 수 있습니다. 상승·하락 구름은 각각 on/off 할 수 있습니다.

상승 구름 (선행1 > 선행2)
onChange({ bullishColor: v })} />
{colorToRgbaString(cloudColors.bullishColor)} {' · '}투명도 {cloudColorOpacityPercent(cloudColors.bullishColor)}%
하락 구름 (선행1 < 선행2)
onChange({ bearishColor: v })} />
{colorToRgbaString(cloudColors.bearishColor)} {' · '}투명도 {cloudColorOpacityPercent(cloudColors.bearishColor)}%
); }; /* ── 볼린저밴드 백그라운드 (업비트 모습 탭) ───────────────────── */ export const BollingerBackgroundRow: React.FC<{ value: HlinesBackground; onChange: (v: HlinesBackground) => void; }> = ({ value, onChange }) => (
백그라운드 그리기
onChange({ ...value, color: c })} />
); /* ── 수평선 배경 ─────────────────────────────────────────────── */ export const HlinesBackgroundRow: React.FC<{ value: HlinesBackground; onChange: (v: HlinesBackground) => void; }> = ({ value, onChange }) => ( <>
수평선 배경
onChange({ ...value, color: c })} />
); /* ── 볼린저밴드 심볼 (업비트 인풋) ───────────────────────────── */ export const BollingerSymbolSection: React.FC<{ symbolMode: string; refSymbol: string; chartMarket: string; onChange: (patch: { symbolMode?: string; refSymbol?: string }) => void; }> = ({ symbolMode, refSymbol, chartMarket, onChange }) => { const isOther = symbolMode === 'other'; return (
심볼
); }; /* ── 통합 본문 (일반 지표) ───────────────────────────────────── */ export const UnifiedIndicatorBody: React.FC<{ config: IndicatorConfig; params: Record; plots: PlotDef[]; plotVis: Record; hlines: HLineDef[]; hlinesBg: HlinesBackground; hasHLines: boolean; lastValueVisible: boolean; timeframeVis: Partial>; timeframes: { tf: Timeframe; label: string }[]; onParamChange: (key: string, raw: string | boolean) => void; onPlotToggle: (plotId: string, on: boolean) => void; onPlotStyle: (idx: number, patch: Partial) => void; onHLineVisible: (idx: number, v: boolean) => void; onHLinePrice: (idx: number, v: number) => void; onHLineStyle: (idx: number, patch: Partial) => void; onHlinesBg: (v: HlinesBackground) => void; onLastValueVisible: (v: boolean) => void; onTimeframeVis: (tf: Timeframe, v: boolean) => void; cloudSection?: React.ReactNode; symbolSection?: React.ReactNode; /** 플롯 행 아래 추가 섹션 (BB 백그라운드 등) */ afterPlotsSection?: React.ReactNode; /** false면 가격 스케일·타임프레임 보임 설정 숨김 (BB) */ showOutputFooter?: boolean; inputsOnly?: boolean; chartHidden?: boolean; onChartHidden?: (showOnChart: boolean) => void; }> = (props) => { const { config, params, plots, plotVis, hlines, hlinesBg, hasHLines, lastValueVisible, timeframeVis, timeframes, onParamChange, onPlotToggle, onPlotStyle, onHLineVisible, onHLinePrice, onHLineStyle, onHlinesBg, onLastValueVisible, onTimeframeVis, cloudSection, symbolSection, afterPlotsSection, showOutputFooter = true, inputsOnly = false, chartHidden, onChartHidden, } = props; const globalKeys = getGlobalParamKeys(config.type, params, plots); const hlinePrices = hlines.map(h => h.price); return (
{symbolSection} {symbolSection && globalKeys.length > 0 &&
} {globalKeys.map(key => ( ))} {globalKeys.length > 0 && plots.length > 0 &&
} {plots.map((plot, idx) => ( ))} {cloudSection} {afterPlotsSection} {hlines.length > 0 && ( <>
수평선
{hlines.map((hl, idx) => ( ))} )} {hasHLines && !inputsOnly && } {showOutputFooter && ( )}
); };