평가로직 오류 수정

This commit is contained in:
Macbook
2026-06-17 23:57:17 +09:00
parent d4b5814bbc
commit 9c832a3ab8
7 changed files with 156 additions and 27 deletions
@@ -21,14 +21,28 @@ import {
} from './thresholdSymbols';
import { repairPriceExtremePairForest } from './priceExtremeBreakoutPair';
function coerceTargetValue(raw: unknown): number | null {
if (raw == null) return null;
if (typeof raw === 'number') return Number.isFinite(raw) ? raw : null;
if (typeof raw === 'string') {
const n = parseFloat(raw.trim());
return Number.isFinite(n) ? n : null;
}
return null;
}
function isConditionCarrier(node: LogicNode): node is LogicNode & { condition: ConditionDSL } {
return !!node.condition && (node.type === 'CONDITION' || node.type == null || node.type === undefined);
}
function migrateConditionThreshold(
cond: ConditionDSL,
def: DefType,
signalType: 'buy' | 'sell',
): ConditionDSL {
if (isThresholdOverridden(cond)) {
const val = cond.targetValue ?? parseThresholdField(cond.rightField);
if (val != null && Number.isFinite(val)) {
const val = coerceTargetValue(cond.targetValue) ?? parseThresholdField(cond.rightField);
if (val != null) {
const role = inferThresholdRoleForValue(val, cond.indicatorType, def.hlThresh);
if (role != null) {
const { targetValue: _t, ...rest } = cond;
@@ -92,12 +106,20 @@ function migrateLogicNode(
signalType: 'buy' | 'sell',
): LogicNode {
let next = node;
if (node.type === 'CONDITION' && node.condition) {
next = { ...node, condition: migrateConditionForEditor(node.condition, def, signalType) };
if (isConditionCarrier(node)) {
next = {
...node,
type: node.type ?? 'CONDITION',
condition: migrateConditionForEditor(node.condition, def, signalType),
};
}
if (next.children?.length) {
next = { ...next, children: next.children.map(c => migrateLogicNode(c, def, signalType)) };
}
const legacyChild = (next as LogicNode & { child?: LogicNode }).child;
if (legacyChild) {
next = { ...next, child: migrateLogicNode(legacyChild, def, signalType) } as LogicNode;
}
return next;
}
@@ -115,13 +137,17 @@ export function migrateLogicRootForEditor(
return repairPriceExtremePairForest(migrated.root, migrated.orphans);
}
/** 저장 직전 — 상속 모드는 숫자 스냅샷·targetValue 제거; 직접입력도 hline 역할과 일치하면 HL_*통일 */
/** 저장·표시·평가 공통 — K_50 등을 HL_MID접기 */
export function normalizeConditionForPersistence(cond: ConditionDSL): ConditionDSL {
let next: ConditionDSL = { ...cond };
let next: ConditionDSL = {
...cond,
indicatorType: cond.indicatorType?.toUpperCase?.() ?? cond.indicatorType,
};
const targetNum = coerceTargetValue(next.targetValue);
if (isThresholdOverridden(next)) {
if (next.targetValue != null && Number.isFinite(next.targetValue)) {
const role = inferThresholdRoleForValue(next.targetValue, next.indicatorType, {});
if (targetNum != null) {
const role = inferThresholdRoleForValue(targetNum, next.indicatorType, {});
if (role != null) {
const { targetValue: _t, ...rest } = next;
next = { ...rest, rightField: role, thresholdOverride: false };
@@ -168,13 +194,21 @@ export function normalizeConditionForPersistence(cond: ConditionDSL): ConditionD
}
function normalizeLogicNode(node: LogicNode): LogicNode {
let next = node;
if (node.type === 'CONDITION' && node.condition) {
next = { ...node, condition: normalizeConditionForPersistence(node.condition) };
let next: LogicNode = node;
if (isConditionCarrier(node)) {
next = {
...node,
type: node.type ?? 'CONDITION',
condition: normalizeConditionForPersistence(node.condition),
};
}
if (next.children?.length) {
next = { ...next, children: next.children.map(normalizeLogicNode) };
}
const legacyChild = (next as LogicNode & { child?: LogicNode }).child;
if (legacyChild) {
next = { ...next, child: normalizeLogicNode(legacyChild) } as LogicNode;
}
return next;
}
+6 -4
View File
@@ -45,15 +45,17 @@ export function hydrateStrategyDto(strategy: StrategyDto): StrategyDto {
if (!layout) return next;
try {
const buyState = decodeConditionForEditor(buy);
const sellState = decodeConditionForEditor(sell);
const normalizedBuy = asLogicNode(next.buyCondition);
const normalizedSell = asLogicNode(next.sellCondition);
const buyState = decodeConditionForEditor(normalizedBuy);
const sellState = decodeConditionForEditor(normalizedSell);
const encodedBuy = encodeConditionForSave(buyState);
const encodedSell = encodeConditionForSave(sellState);
if (encodedBuy || encodedSell) {
next = {
...next,
buyCondition: encodedBuy ?? buy ?? undefined,
sellCondition: encodedSell ?? sell ?? undefined,
buyCondition: encodedBuy ?? normalizedBuy ?? undefined,
sellCondition: encodedSell ?? normalizedSell ?? undefined,
};
}
} catch {
+2 -1
View File
@@ -38,7 +38,8 @@ function mergedHlThresh(
indicatorType: string,
hlThresh: Record<string, HlThresh>,
): HlThresh {
return { ...DEFAULT_HL_THRESH[indicatorType], ...hlThresh[indicatorType] };
const key = indicatorType?.toUpperCase?.() ?? indicatorType;
return { ...DEFAULT_HL_THRESH[key], ...hlThresh[key], ...hlThresh[indicatorType] };
}
export function defaultThresholdForRole(