직접입력값 평가오류 수정
This commit is contained in:
@@ -22,13 +22,15 @@ public final class IndicatorHlineResolver {
|
||||
|
||||
boolean override = cond != null && cond.path("thresholdOverride").asBoolean(false);
|
||||
|
||||
// K_ 직접입력 — targetValue·override 시 필드명/스냅샷 숫자 우선
|
||||
// K_* = 직접입력 숫자 스냅샷 — hline 역할로 치환하지 않음 (K_55 → 55)
|
||||
if (field.startsWith("K_")) {
|
||||
if (cond != null && cond.has("targetValue") && !cond.path("targetValue").isNull()) {
|
||||
return cond.path("targetValue").asDouble();
|
||||
}
|
||||
if (override) {
|
||||
try { return Double.parseDouble(field.substring(2)); } catch (NumberFormatException ignored) {}
|
||||
try {
|
||||
return Double.parseDouble(field.substring(2));
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,9 +38,6 @@ public final class IndicatorHlineResolver {
|
||||
if (cond.has("targetValue") && !cond.path("targetValue").isNull()) {
|
||||
return cond.path("targetValue").asDouble();
|
||||
}
|
||||
if (field.startsWith("K_")) {
|
||||
try { return Double.parseDouble(field.substring(2)); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
return legacyNumericField(field);
|
||||
}
|
||||
|
||||
@@ -47,12 +46,6 @@ public final class IndicatorHlineResolver {
|
||||
if (fromVisual != null) return fromVisual;
|
||||
}
|
||||
|
||||
if (field.startsWith("K_")) {
|
||||
Double fromVisual = inferRoleFromLegacyK(field, dslIndicatorType, indicatorVisual);
|
||||
if (fromVisual != null) return fromVisual;
|
||||
try { return Double.parseDouble(field.substring(2)); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
return legacyNumericField(field);
|
||||
}
|
||||
|
||||
@@ -99,22 +92,6 @@ public final class IndicatorHlineResolver {
|
||||
return defaultForRole(dslIndicatorType, role);
|
||||
}
|
||||
|
||||
private static Double inferRoleFromLegacyK(
|
||||
String kField,
|
||||
String dslIndicatorType,
|
||||
Map<String, Map<String, Object>> indicatorVisual) {
|
||||
try {
|
||||
double val = Double.parseDouble(kField.substring(2));
|
||||
for (String role : List.of("HL_OVER", "HL_MID", "HL_UNDER")) {
|
||||
Double roleVal = fromHlineRole(role, dslIndicatorType, indicatorVisual);
|
||||
if (roleVal != null && Math.abs(roleVal - val) < 0.0001) {
|
||||
return roleVal;
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException ignored) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Double legacyNumericField(String field) {
|
||||
if (field.contains("_NEG")) {
|
||||
String[] parts = field.split("_NEG");
|
||||
|
||||
+14
-74
@@ -3,12 +3,11 @@ package com.goldenchart.service;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 전략 조건 DSL — K_50 등 직접입력 스냅샷을 hline 역할(HL_MID 등)로 정규화.
|
||||
* 프론트 normalizeConditionForPersistence 와 동일 의도.
|
||||
* 전략 조건 DSL — 직접입력 K_* 필드에 targetValue·thresholdOverride 동기화.
|
||||
* HL_* 프리셋은 그대로 두며, K_55 등 입력값을 중앙선 역할로 치환하지 않는다.
|
||||
*/
|
||||
public final class StrategyConditionThresholdNormalizer {
|
||||
|
||||
@@ -44,82 +43,23 @@ public final class StrategyConditionThresholdNormalizer {
|
||||
if (!indType.isBlank()) {
|
||||
cond.put("indicatorType", indType);
|
||||
}
|
||||
normalizeThresholdField(cond, "rightField", indType);
|
||||
normalizeThresholdField(cond, "leftField", indType);
|
||||
syncDirectKField(cond, "rightField");
|
||||
syncDirectKField(cond, "leftField");
|
||||
}
|
||||
|
||||
private static void normalizeThresholdField(ObjectNode cond, String fieldKey, String indType) {
|
||||
/** K_55 → targetValue 55, thresholdOverride true (평가 시 입력 숫자 그대로) */
|
||||
private static void syncDirectKField(ObjectNode cond, String fieldKey) {
|
||||
String field = cond.path(fieldKey).asText("");
|
||||
if (field.isBlank()) return;
|
||||
|
||||
boolean override = cond.path("thresholdOverride").asBoolean(false);
|
||||
Double value = readThresholdValue(cond, field);
|
||||
|
||||
if (override || field.startsWith("K_")) {
|
||||
String role = inferRoleForValue(indType, value, field);
|
||||
if (role != null) {
|
||||
cond.put(fieldKey, role);
|
||||
cond.remove("targetValue");
|
||||
cond.put("thresholdOverride", false);
|
||||
return;
|
||||
if (!field.startsWith("K_")) return;
|
||||
try {
|
||||
double val = Double.parseDouble(field.substring(2));
|
||||
if (!cond.has("targetValue") || cond.path("targetValue").isNull()) {
|
||||
cond.put("targetValue", val);
|
||||
}
|
||||
cond.put("thresholdOverride", true);
|
||||
} catch (NumberFormatException ignored) {
|
||||
// 비정상 K_ 필드는 그대로 둠
|
||||
}
|
||||
|
||||
if (!override && field.startsWith("K_")) {
|
||||
String role = inferRoleForValue(indType, value, field);
|
||||
if (role != null) {
|
||||
cond.put(fieldKey, role);
|
||||
cond.remove("targetValue");
|
||||
cond.put("thresholdOverride", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Double readThresholdValue(ObjectNode cond, String field) {
|
||||
if (cond.has("targetValue") && !cond.path("targetValue").isNull()) {
|
||||
return cond.path("targetValue").asDouble();
|
||||
}
|
||||
if (field.startsWith("K_")) {
|
||||
try {
|
||||
return Double.parseDouble(field.substring(2));
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String inferRoleForValue(String indicatorType, Double value, String field) {
|
||||
if (value == null && field.startsWith("K_")) {
|
||||
try {
|
||||
value = Double.parseDouble(field.substring(2));
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (value == null || !Double.isFinite(value)) return null;
|
||||
|
||||
double eps = 0.0001;
|
||||
for (Map.Entry<String, Double> e : defaultRoles(indicatorType).entrySet()) {
|
||||
if (Math.abs(e.getValue() - value) < eps) {
|
||||
return e.getKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** HL_OVER / HL_MID / HL_UNDER → 기본 숫자 (IndicatorHlineResolver.defaultForRole 과 동일) */
|
||||
private static Map<String, Double> defaultRoles(String indicatorType) {
|
||||
return switch (indicatorType) {
|
||||
case "RSI" -> Map.of("HL_OVER", 70.0, "HL_MID", 50.0, "HL_UNDER", 30.0);
|
||||
case "STOCHASTIC" -> Map.of("HL_OVER", 80.0, "HL_MID", 50.0, "HL_UNDER", 20.0);
|
||||
case "CCI" -> Map.of("HL_OVER", 100.0, "HL_MID", 0.0, "HL_UNDER", -100.0);
|
||||
case "WILLIAMS_R" -> Map.of("HL_OVER", -20.0, "HL_MID", -50.0, "HL_UNDER", -80.0);
|
||||
case "ADX" -> Map.of("HL_MID", 25.0);
|
||||
case "DISPARITY" -> Map.of("HL_MID", 100.0);
|
||||
case "VOLUME_OSC", "TRIX", "MACD" -> Map.of("HL_MID", 0.0);
|
||||
default -> Map.of();
|
||||
};
|
||||
}
|
||||
|
||||
/** JSON 문자열 보정 — 파싱 실패 시 원본 반환 */
|
||||
|
||||
Reference in New Issue
Block a user