94 lines
3.4 KiB
TypeScript
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;
|
|
}
|
|
}
|