전략편집기 기준값 보조지표 설정값 동기화
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
package com.goldenchart.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 보조지표 visual_config hline — 전략 DSL HL_OVER/HL_MID/HL_UNDER 역할 해석.
|
||||
*/
|
||||
public final class IndicatorHlineResolver {
|
||||
|
||||
private IndicatorHlineResolver() {}
|
||||
|
||||
public static Double resolveThresholdField(
|
||||
JsonNode cond,
|
||||
String field,
|
||||
String dslIndicatorType,
|
||||
Map<String, Map<String, Object>> indicatorVisual) {
|
||||
|
||||
if (field == null || field.isBlank()) return null;
|
||||
|
||||
boolean override = cond != null && cond.path("thresholdOverride").asBoolean(false);
|
||||
if (override) {
|
||||
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);
|
||||
}
|
||||
|
||||
if ("HL_OVER".equals(field) || "HL_MID".equals(field) || "HL_UNDER".equals(field)) {
|
||||
Double fromVisual = fromHlineRole(field, dslIndicatorType, indicatorVisual);
|
||||
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);
|
||||
}
|
||||
|
||||
private static Double fromHlineRole(
|
||||
String role,
|
||||
String dslIndicatorType,
|
||||
Map<String, Map<String, Object>> indicatorVisual) {
|
||||
|
||||
String registryKey = StrategyDslToTa4jAdapter.registryKeyForDsl(dslIndicatorType);
|
||||
if (indicatorVisual == null || registryKey == null) return defaultForRole(dslIndicatorType, role);
|
||||
|
||||
Map<String, Object> visual = indicatorVisual.get(registryKey);
|
||||
if (visual == null) return defaultForRole(dslIndicatorType, role);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> hlines = (List<Map<String, Object>>) visual.get("hlines");
|
||||
if (hlines == null || hlines.isEmpty()) return defaultForRole(dslIndicatorType, role);
|
||||
|
||||
String label = switch (role) {
|
||||
case "HL_OVER" -> "과열선";
|
||||
case "HL_MID" -> "중앙선";
|
||||
case "HL_UNDER" -> "침체선";
|
||||
default -> null;
|
||||
};
|
||||
if (label == null) return null;
|
||||
|
||||
for (Map<String, Object> h : hlines) {
|
||||
Object lbl = h.get("label");
|
||||
if (lbl != null && label.equals(lbl.toString())) {
|
||||
Object price = h.get("price");
|
||||
if (price instanceof Number n) return n.doubleValue();
|
||||
}
|
||||
}
|
||||
// 기준선 폴백 (Disparity 등)
|
||||
if ("HL_MID".equals(role)) {
|
||||
for (Map<String, Object> h : hlines) {
|
||||
Object lbl = h.get("label");
|
||||
if (lbl != null && "기준선".equals(lbl.toString())) {
|
||||
Object price = h.get("price");
|
||||
if (price instanceof Number n) return n.doubleValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
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");
|
||||
try { return -Double.parseDouble(parts[parts.length - 1]); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
if ("ZERO_LINE".equals(field)) return 0.0;
|
||||
int idx = field.lastIndexOf('_');
|
||||
if (idx >= 0 && idx < field.length() - 1) {
|
||||
try { return Double.parseDouble(field.substring(idx + 1)); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Double defaultForRole(String dslIndicatorType, String role) {
|
||||
return switch (dslIndicatorType + ":" + role) {
|
||||
case "RSI:HL_OVER" -> 70.0;
|
||||
case "RSI:HL_MID" -> 50.0;
|
||||
case "RSI:HL_UNDER" -> 30.0;
|
||||
case "CCI:HL_OVER" -> 100.0;
|
||||
case "CCI:HL_MID" -> 0.0;
|
||||
case "CCI:HL_UNDER" -> -100.0;
|
||||
case "STOCHASTIC:HL_OVER" -> 80.0;
|
||||
case "STOCHASTIC:HL_MID" -> 50.0;
|
||||
case "STOCHASTIC:HL_UNDER" -> 20.0;
|
||||
case "ADX:HL_MID" -> 25.0;
|
||||
case "WILLIAMS_R:HL_OVER" -> -20.0;
|
||||
case "DISPARITY:HL_MID" -> 100.0;
|
||||
case "VOLUME_OSC:HL_MID" -> 0.0;
|
||||
case "TRIX:HL_MID" -> 0.0;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user