Files
goldenChart/frontend/src/utils/thresholdSymbols.ts
T

94 lines
3.4 KiB
TypeScript

/** 보조지표 차트 hline 역할 — DSL에 숫자(K_70) 대신 저장해 DB 설정 변경 시 자동 반영 */
export const THRESHOLD_HL_OVER = 'HL_OVER';
export const THRESHOLD_HL_MID = 'HL_MID';
export const THRESHOLD_HL_UNDER = 'HL_UNDER';
export type ThresholdHlineRole = typeof THRESHOLD_HL_OVER | typeof THRESHOLD_HL_MID | typeof THRESHOLD_HL_UNDER;
export function isThresholdSymbol(field?: string): boolean {
return field === THRESHOLD_HL_OVER || field === THRESHOLD_HL_MID || field === THRESHOLD_HL_UNDER;
}
export function isThresholdFieldOrSymbol(field?: string): boolean {
return !!field && (field.startsWith('K_') || isThresholdSymbol(field));
}
type HlThresh = { over?: number; mid?: number; under?: number };
export function resolveThresholdFromDef(
field: string | undefined,
indicatorType: string,
hlThresh: Record<string, HlThresh>,
): number | null {
if (!field) return null;
const th = hlThresh[indicatorType] ?? {};
if (field === THRESHOLD_HL_OVER) return th.over ?? null;
if (field === THRESHOLD_HL_MID) return th.mid ?? null;
if (field === THRESHOLD_HL_UNDER) return th.under ?? null;
if (field.startsWith('K_')) {
const n = parseFloat(field.slice(2));
return Number.isFinite(n) ? n : null;
}
return null;
}
/** 레거시 K_ 값 → hline 역할 심볼 (정확 일치 우선, 없으면 가장 가까운 역할) */
export function inferThresholdSymbolFromLegacy(
field: string | undefined,
indicatorType: string,
hlThresh: Record<string, HlThresh>,
): string | undefined {
if (!field?.startsWith('K_')) return field;
const val = parseFloat(field.slice(2));
if (!Number.isFinite(val)) return field;
const th = hlThresh[indicatorType] ?? {};
const eps = 0.0001;
if (th.over != null && Math.abs(th.over - val) < eps) return THRESHOLD_HL_OVER;
if (th.mid != null && Math.abs(th.mid - val) < eps) return THRESHOLD_HL_MID;
if (th.under != null && Math.abs(th.under - val) < eps) return THRESHOLD_HL_UNDER;
const candidates: { role: ThresholdHlineRole; v: number }[] = [];
if (th.over != null) candidates.push({ role: THRESHOLD_HL_OVER, v: th.over });
if (th.mid != null) candidates.push({ role: THRESHOLD_HL_MID, v: th.mid });
if (th.under != null) candidates.push({ role: THRESHOLD_HL_UNDER, v: th.under });
if (candidates.length === 0) return defaultInheritedThresholdField(indicatorType);
let best = candidates[0];
let bestDiff = Math.abs(best.v - val);
for (const c of candidates.slice(1)) {
const d = Math.abs(c.v - val);
if (d < bestDiff) {
best = c;
bestDiff = d;
}
}
return best.role;
}
/** 상속 모드(thresholdOverride=false) — K_ 스냅샷을 hline 역할 심볼로 정규화 */
export function resolveInheritedThresholdField(
field: string | undefined,
indicatorType: string,
hlThresh: Record<string, HlThresh>,
): string | undefined {
if (!field) return field;
if (isThresholdSymbol(field)) return field;
if (field.startsWith('K_')) {
return inferThresholdSymbolFromLegacy(field, indicatorType, hlThresh)
?? defaultInheritedThresholdField(indicatorType);
}
return field;
}
/** 상속 모드 기본 rightField — 과열선 역할 */
export function defaultInheritedThresholdField(indicatorType: string): string {
switch (indicatorType) {
case 'DISPARITY':
case 'VOLUME_OSC':
case 'TRIX':
return THRESHOLD_HL_MID;
default:
return THRESHOLD_HL_OVER;
}
}