Files
goldenChart/frontend/src/utils/indicatorParamSpec.ts
T
2026-05-29 23:03:47 +09:00

225 lines
6.1 KiB
TypeScript

/**
* 보조지표 설정 — 숫자 파라미터 허용 범위·드롭다운 목록
*/
export interface NumericParamSpec {
min: number;
max: number;
step: number;
allowNegative: boolean;
decimal: boolean;
/** 드롭다운에 표시할 허용 값 목록 */
options: number[];
}
const COMMON_PERIODS = [
1, 2, 3, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15, 20, 21, 24, 26, 28, 34,
50, 52, 60, 72, 90, 100, 120, 200,
];
const DECIMAL_MULT = [0.1, 0.2, 0.25, 0.5, 0.75, 1, 1.5, 2, 2.5, 3, 4, 5];
function roundTo(v: number, decimals: number): number {
const f = 10 ** decimals;
return Math.round(v * f) / f;
}
function mergeOptions(
min: number,
max: number,
step: number,
decimal: boolean,
presets: number[],
maxCount = 56,
include?: number,
): number[] {
const set = new Set<number>();
const dec = decimal ? (step < 1 ? 2 : 1) : 0;
for (const p of presets) {
if (p >= min && p <= max) set.add(roundTo(p, dec));
}
if (include != null && !isNaN(include) && include >= min && include <= max) {
set.add(roundTo(include, dec));
}
for (let v = min; v <= max + step * 0.001 && set.size < maxCount; v += step) {
set.add(roundTo(v, dec));
}
return Array.from(set).sort((a, b) => a - b);
}
function isPeriodKey(key: string): boolean {
return /^(length|len|period\d*|kLength|dSmoothing|smooth|adxSmoothing|diLength|ccilength|turbolen|signalLength|fastLength|slowLength|longLength|shortLength|maLength|ultraLength|midLength|longLength|displacement|chikouDisplacement|senkouDisplacement|conversionPeriods|basePeriods|laggingSpan2Periods|length1|length2|length3|lengthRSI|lengthStoch|rsiLength|upDownLength|rocLength|stdevLength|atrLength|p|q|x)$/i.test(key);
}
function isMultKey(key: string): boolean {
return /^(mult|multiplier|percent|bbMult)$/i.test(key);
}
function isOffsetKey(key: string): boolean {
return /^offset$/i.test(key);
}
function isSigmaKey(key: string): boolean {
return /^sigma$/i.test(key);
}
/** 지표 파라미터 키별 숫자 스펙 */
export function getNumericParamSpec(
indicatorType: string,
paramKey: string,
currentValue?: number,
): NumericParamSpec {
if (isPeriodKey(paramKey)) {
const max = paramKey === 'rocLength' ? 200 : 500;
return {
min: 1,
max,
step: 1,
allowNegative: false,
decimal: false,
options: mergeOptions(1, max, 1, false, COMMON_PERIODS, 56, currentValue),
};
}
if (isMultKey(paramKey)) {
const isPercent = paramKey === 'percent';
return {
min: isPercent ? 0.01 : 0.1,
max: isPercent ? 1 : 10,
step: isPercent ? 0.01 : 0.1,
allowNegative: false,
decimal: true,
options: mergeOptions(
isPercent ? 0.01 : 0.1,
isPercent ? 1 : 10,
isPercent ? 0.01 : 0.1,
true,
isPercent
? [0.05, 0.1, 0.15, 0.2, 0.25, 0.5, 0.75, 1]
: DECIMAL_MULT,
40,
currentValue,
),
};
}
if (isOffsetKey(paramKey)) {
if (indicatorType === 'ALMA') {
return {
min: 0,
max: 1,
step: 0.05,
allowNegative: false,
decimal: true,
options: mergeOptions(0, 1, 0.05, true, [0, 0.5, 0.85, 1], 24, currentValue),
};
}
return {
min: -500,
max: 500,
step: 1,
allowNegative: true,
decimal: false,
options: mergeOptions(-500, 500, 1, false, [-26, -9, 0, 9, 26], 48, currentValue),
};
}
if (isSigmaKey(paramKey)) {
return {
min: 1,
max: 30,
step: 1,
allowNegative: false,
decimal: false,
options: mergeOptions(1, 30, 1, false, [4, 6, 8, 10, 14, 20], 30, currentValue),
};
}
// SMI 등 소수 오실레이터 임계
if (indicatorType === 'SMIErgodic' || indicatorType === 'SMIErgodicOscillator') {
return {
min: 1,
max: 100,
step: 1,
allowNegative: false,
decimal: false,
options: mergeOptions(1, 100, 1, false, [5, 10, 20, 40], 40, currentValue),
};
}
// 기본: 정수 1~100
return {
min: 1,
max: 500,
step: 1,
allowNegative: false,
decimal: false,
options: mergeOptions(1, 500, 1, false, COMMON_PERIODS, 56, currentValue),
};
}
/** 수평선(과열/침체) 기준값 */
export function getHlinePriceSpec(indicatorType: string, current: number): NumericParamSpec {
const byType: Record<string, number[]> = {
RSI: [20, 30, 50, 70, 80],
Stochastic: [20, 50, 80],
StochRSI: [20, 50, 80],
CCI: [-200, -100, 0, 100, 200],
WilliamsPercentRange: [-80, -50, -20],
MACD: [-50, 0, 50],
ADX: [20, 25, 40],
DMI: [10, 20, 30],
TRIX: [-0.05, -0.01, 0, 0.01, 0.05],
MOM: [-50, 0, 50],
ROC: [-20, 0, 20],
CMO: [-50, 0, 50],
DPO: [-100, 0, 100],
FisherTransform: [-2.5, 0, 2.5],
SMIErgodic: [-0.4, 0, 0.4],
SMIErgodicOscillator: [-0.4, 0, 0.4],
Disparity: [90, 95, 100, 105, 110],
VR: [50, 100, 150, 200, 250],
Psychological: [25, 50, 75],
NewPsychological: [-50, 0, 50],
InvestPsychological: [25, 50, 75],
};
const presets = byType[indicatorType] ?? [-200, -100, 0, 100, 200];
const allowNegative = presets.some(p => p < 0) || indicatorType !== 'RSI';
const min = Math.min(-500, ...presets, current) - 50;
const max = Math.max(500, ...presets, current) + 50;
return {
min,
max,
step: 1,
allowNegative: true,
decimal: true,
options: mergeOptions(min, max, allowNegative ? 10 : 5, true, presets, 48, current),
};
}
export function getLineWidthSpec(min = 1, max = 5): NumericParamSpec {
const options = Array.from({ length: max - min + 1 }, (_, i) => min + i);
return {
min,
max,
step: 1,
allowNegative: false,
decimal: false,
options,
};
}
export function getOpacityPercentSpec(current?: number): NumericParamSpec {
const options = Array.from({ length: 21 }, (_, i) => i * 5);
return {
min: 0,
max: 100,
step: 1,
allowNegative: false,
decimal: false,
options: mergeOptions(0, 100, 5, false, options, 21, current),
};
}