143 lines
5.5 KiB
TypeScript
143 lines
5.5 KiB
TypeScript
/**
|
|
* 보조지표 설정 화면 — 플롯 행·전역 파라미터 배치 (일목/SMA 단일 화면 컨셉)
|
|
*/
|
|
import type { PlotDef } from './indicatorRegistry';
|
|
|
|
/** 플롯 행에 붙지 않고 상단에만 표시할 키 (우선 순서) */
|
|
const GLOBAL_FIRST_KEYS: string[] = [
|
|
'src', 'maType', 'mult', 'multiplier', 'percent', 'bbMult', 'offset', 'sigma',
|
|
];
|
|
|
|
const GLOBAL_FIRST_SET = new Set(GLOBAL_FIRST_KEYS);
|
|
|
|
/** 지표별 플롯 ID → 파라미터 키 (빈 배열 = 자동/공유) */
|
|
export const INDICATOR_PLOT_PARAMS: Record<string, Record<string, string[]>> = {
|
|
MACross: { plot0: ['fastLength'], plot1: ['slowLength'] },
|
|
Stochastic: { plot0: ['kLength', 'smooth'], plot1: ['dSmoothing'] },
|
|
StochRSI: { plot0: ['lengthRSI', 'lengthStoch', 'smoothK'], plot1: ['smoothD'] },
|
|
CCI: { plot0: ['length'], plot1: ['maLength'] },
|
|
RSI: { plot0: ['length'], plot1: ['maLength'] },
|
|
/** 업비트: OBV 행=이동평균 종류, 신호선 행=기간 (색상 컨트롤 좌측) */
|
|
OBV: { plot0: ['maType'], plot1: ['maLength'] },
|
|
/** 업비트: 길이·곱·오프셋은 상단 전역 행, 플롯 행은 스타일만 */
|
|
BollingerBands: { plot0: [], plot1: [], plot2: [] },
|
|
/** 업비트: DI 길이·ADX 스무딩은 상단 전역, 플롯 행은 스타일만 */
|
|
DMI: { plot0: [], plot1: [], plot2: [], plot3: [], plot4: [] },
|
|
KeltnerChannels: { plot0: ['length', 'multiplier'], plot1: [], plot2: [] },
|
|
DonchianChannels: { plot0: ['length'], plot1: [], plot2: [] },
|
|
Envelope: { plot0: ['length', 'percent'], plot1: [], plot2: [] },
|
|
RVI: { plot0: ['length'], plot1: [] },
|
|
FisherTransform: { plot0: ['length'], plot1: [] },
|
|
MACD: { plot0: ['fastLength', 'slowLength', 'signalLength'], plot1: [], plot2: [] },
|
|
TSI: { plot0: ['longLength', 'shortLength'], plot1: ['signalLength'] },
|
|
TRIX: { plot0: ['length'], plot1: ['signalLength'] },
|
|
SMIErgodic: { plot0: ['shortLength', 'longLength'], plot1: ['signalLength'] },
|
|
SMIErgodicOscillator: { plot0: ['shortLength', 'longLength'], plot1: ['signalLength'] },
|
|
UltimateOscillator: { plot0: ['length1', 'length2', 'length3'] },
|
|
ConnorsRSI: { plot0: ['rsiLength', 'upDownLength', 'rocLength'] },
|
|
Psychological: { plot0: ['length'] },
|
|
NewPsychological: { plot0: ['length'] },
|
|
InvestPsychological: { plot0: ['length'] },
|
|
Disparity: {
|
|
plot0: ['ultraLength'],
|
|
plot1: ['shortLength'],
|
|
plot2: ['midLength'],
|
|
plot3: ['longLength'],
|
|
},
|
|
IchimokuCloud: {
|
|
plot0: ['conversionPeriods'],
|
|
plot1: ['basePeriods'],
|
|
/** 후행스팬 — 과거 이동 기간 */
|
|
plot2: ['chikouDisplacement'],
|
|
/** 선행스팬1 — 미래 이동 기간 */
|
|
plot3: ['senkouDisplacement'],
|
|
/** 선행스팬2 — Donchian 기간 */
|
|
plot4: ['laggingSpan2Periods'],
|
|
},
|
|
};
|
|
|
|
const PLOT_INDEX_FALLBACK: Record<number, string[]> = {
|
|
0: [
|
|
'length', 'len', 'kLength', 'fastLength', 'length1', 'lengthRSI', 'rsiLength',
|
|
'shortLength', 'ultraLength', 'conversionPeriods', 'turbolen',
|
|
],
|
|
1: ['dSmoothing', 'slowLength', 'length2', 'smoothD', 'maLength', 'signalLength', 'midLength', 'basePeriods'],
|
|
2: ['smooth', 'length3', 'longLength', 'laggingSpan2Periods'],
|
|
3: ['longLength', 'displacement'],
|
|
4: ['displacement'],
|
|
};
|
|
|
|
function keysInParams(keys: string[], params: Record<string, unknown>): string[] {
|
|
return keys.filter(k => k in params);
|
|
}
|
|
|
|
/** 플롯 한 줄에 표시할 숫자·선택 파라미터 키 */
|
|
export function getPlotParamKeys(
|
|
indicatorType: string,
|
|
plotId: string,
|
|
plotIndex: number,
|
|
plots: PlotDef[],
|
|
params: Record<string, number | string | boolean>,
|
|
): string[] {
|
|
const mapped = INDICATOR_PLOT_PARAMS[indicatorType]?.[plotId];
|
|
if (mapped !== undefined) {
|
|
return keysInParams(mapped, params);
|
|
}
|
|
|
|
if (plots.length === 1) {
|
|
return Object.keys(params).filter(
|
|
k => !GLOBAL_FIRST_SET.has(k) && typeof params[k] === 'number',
|
|
);
|
|
}
|
|
|
|
const fallback = keysInParams(PLOT_INDEX_FALLBACK[plotIndex] ?? [], params);
|
|
if (fallback.length) return fallback;
|
|
|
|
return [];
|
|
}
|
|
|
|
/** 업비트 BB 인풋 탭 순서 */
|
|
const BOLLINGER_GLOBAL_KEYS = ['length', 'mult', 'offset'] as const;
|
|
|
|
/** 플롯에 할당되지 않은 전역 파라미터 (상단 행) */
|
|
export function getGlobalParamKeys(
|
|
indicatorType: string,
|
|
params: Record<string, number | string | boolean>,
|
|
plots: PlotDef[],
|
|
): string[] {
|
|
if (indicatorType === 'BollingerBands') {
|
|
return BOLLINGER_GLOBAL_KEYS.filter(k => k in params);
|
|
}
|
|
|
|
const assigned = new Set<string>();
|
|
for (let i = 0; i < plots.length; i++) {
|
|
for (const k of getPlotParamKeys(indicatorType, plots[i].id, i, plots, params)) {
|
|
assigned.add(k);
|
|
}
|
|
}
|
|
|
|
const remaining = Object.keys(params).filter(
|
|
k => !assigned.has(k) && k !== 'symbolMode' && k !== 'refSymbol' && k !== 'src' && k !== 'maType',
|
|
);
|
|
const ordered = GLOBAL_FIRST_KEYS.filter(k => remaining.includes(k));
|
|
const rest = remaining.filter(k => !ordered.includes(k));
|
|
return [...ordered, ...rest];
|
|
}
|
|
|
|
/** 플롯 행 입력란에 표시할 내용 없음 */
|
|
export function plotRowHasNoInputs(
|
|
indicatorType: string,
|
|
plotId: string,
|
|
plotIndex: number,
|
|
plots: PlotDef[],
|
|
params: Record<string, number | string | boolean>,
|
|
): boolean {
|
|
return getPlotParamKeys(indicatorType, plotId, plotIndex, plots, params).length === 0;
|
|
}
|
|
|
|
/** 파라미터가 상단 전역 행에만 있음 (OBV: maType·maLength) — 플롯 행 '자동' 미표시 */
|
|
export function plotParamsOnGlobalRowOnly(indicatorType: string, plotId: string): boolean {
|
|
const mapped = INDICATOR_PLOT_PARAMS[indicatorType]?.[plotId];
|
|
return mapped !== undefined && mapped.length === 0;
|
|
}
|