실시간 차트 보조지표 탭 문제 수정

This commit is contained in:
Macbook
2026-05-28 01:26:53 +09:00
parent 4f6694b206
commit 98dfb3613c
19 changed files with 801 additions and 182 deletions
@@ -67,13 +67,20 @@ public class StrategyConditionTimeframeService {
GcStrategy strategy = opt.get();
ensureDslRepaired(strategy);
Set<String> out = new LinkedHashSet<>();
boolean buyScoped = collectStartScopeFromJson(strategy.getBuyConditionJson(), out);
boolean sellScoped = collectStartScopeFromJson(strategy.getSellConditionJson(), out);
Set<String> buyOut = new LinkedHashSet<>();
Set<String> sellOut = new LinkedHashSet<>();
boolean buyScoped = collectStartScopeFromJson(strategy.getBuyConditionJson(), buyOut);
boolean sellScoped = collectStartScopeFromJson(strategy.getSellConditionJson(), sellOut);
if (!out.isEmpty()) {
return out;
// 매수 START 분봉 우선 — 매도 쪽 레거시 1m TIMEFRAME 이 합집합에 섞이지 않도록
if (buyScoped && !buyOut.isEmpty()) {
return buyOut;
}
if (sellScoped && !sellOut.isEmpty()) {
return sellOut;
}
Set<String> out = new LinkedHashSet<>();
// 레거시: START(TIMEFRAME) 래핑 없는 DSL
if (!buyScoped) {
@@ -95,6 +102,18 @@ public class StrategyConditionTimeframeService {
boolean changed = false;
String name = strategy.getName();
String buyJson = strategy.getBuyConditionJson();
String sellJson = strategy.getSellConditionJson();
if (buyJson != null && !buyJson.isBlank()
&& sellJson != null && !sellJson.isBlank()) {
String alignedSell = dslNormalizer.alignSellStartTimeframeToBuy(buyJson, sellJson);
if (!alignedSell.equals(sellJson)) {
strategy.setSellConditionJson(alignedSell);
sellJson = alignedSell;
changed = true;
}
}
if (strategy.getBuyConditionJson() != null && !strategy.getBuyConditionJson().isBlank()) {
String fixed = dslNormalizer.normalizeJson(strategy.getBuyConditionJson(), name);
if (!fixed.equals(strategy.getBuyConditionJson())) {
@@ -102,8 +121,8 @@ public class StrategyConditionTimeframeService {
changed = true;
}
}
if (strategy.getSellConditionJson() != null && !strategy.getSellConditionJson().isBlank()) {
String fixed = dslNormalizer.normalizeJson(strategy.getSellConditionJson(), name);
if (sellJson != null && !sellJson.isBlank()) {
String fixed = dslNormalizer.normalizeJson(sellJson, name);
if (!fixed.equals(strategy.getSellConditionJson())) {
strategy.setSellConditionJson(fixed);
changed = true;
@@ -83,6 +83,70 @@ public class StrategyDslTimeframeNormalizer {
}
}
/**
* 매도 DSL START TIMEFRAME 을 매수와 동일 분봉으로 맞춤.
* (편집기 저장 전 매도만 1m 이 남은 레거시 전략 보정)
*/
public String alignSellStartTimeframeToBuy(String buyJson, String sellJson) {
if (buyJson == null || buyJson.isBlank() || sellJson == null || sellJson.isBlank()) {
return sellJson;
}
try {
JsonNode buyRoot = objectMapper.readTree(buyJson);
JsonNode sellRoot = objectMapper.readTree(sellJson);
JsonNode buyTf = primaryStartTimeframeNode(buyRoot);
if (buyTf == null || !hasExplicitStartTimeframe(buyTf)) return sellJson;
ObjectNode sellCopy = (ObjectNode) sellRoot.deepCopy();
JsonNode sellTf = primaryStartTimeframeNode(sellCopy);
if (sellTf == null || !sellTf.isObject()) return sellJson;
copyIntoStartTimeframe(buyTf, (ObjectNode) sellTf);
return objectMapper.writeValueAsString(sellCopy);
} catch (Exception e) {
return sellJson;
}
}
private static boolean hasExplicitStartTimeframe(JsonNode buyTf) {
if (hasCandleTypesArray(buyTf)) return true;
String ct = LiveStrategyTimeframeService.normalize(buyTf.path("candleType").asText("1m"));
return !"1m".equals(ct);
}
private static JsonNode primaryStartTimeframeNode(JsonNode root) {
if (root == null || root.isNull()) return null;
String type = root.path("type").asText("");
if ("TIMEFRAME".equals(type)) return root;
if ("AND".equals(type) || "OR".equals(type)) {
JsonNode children = root.path("children");
if (children.isArray() && !children.isEmpty() && allTimeframeChildren(children)) {
return children.get(0);
}
}
return null;
}
private void copyIntoStartTimeframe(JsonNode from, ObjectNode to) {
if (hasCandleTypesArray(from)) {
ArrayNode arr = objectMapper.createArrayNode();
for (JsonNode t : from.path("candleTypes")) {
arr.add(LiveStrategyTimeframeService.normalize(t.asText("")));
}
to.set("candleTypes", arr);
to.put("candleType", LiveStrategyTimeframeService.normalize(arr.get(0).asText("1m")));
} else {
to.remove("candleTypes");
to.put("candleType", LiveStrategyTimeframeService.normalize(from.path("candleType").asText("1m")));
}
}
private static boolean allTimeframeChildren(JsonNode children) {
for (JsonNode c : children) {
if (!"TIMEFRAME".equals(c.path("type").asText(""))) return false;
}
return true;
}
/** 전략 이름에서 평가 분봉 추론 (예: cci_3분봉 테스트 → 3m) */
public static String inferFromStrategyName(String name) {
if (name == null || name.isBlank()) return null;