225 lines
6.1 KiB
TypeScript
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),
|
|
};
|
|
}
|