실시간 차트 보조지표 탭 문제 수정
This commit is contained in:
+26
-7
@@ -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;
|
||||
|
||||
+32
-2
@@ -6,7 +6,6 @@ import com.goldenchart.repository.GcStrategyRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
@@ -26,9 +25,20 @@ class StrategyConditionTimeframeServiceTest {
|
||||
@Spy
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@InjectMocks
|
||||
@Mock
|
||||
private StrategyDslTimeframeNormalizer dslNormalizer;
|
||||
|
||||
@Mock
|
||||
private LiveStrategyEvaluator liveStrategyEvaluator;
|
||||
|
||||
private StrategyConditionTimeframeService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUpService() {
|
||||
service = new StrategyConditionTimeframeService(
|
||||
strategyRepo, objectMapper, dslNormalizer, liveStrategyEvaluator);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void stubStrategy() {
|
||||
GcStrategy strategy = new GcStrategy();
|
||||
@@ -45,6 +55,10 @@ class StrategyConditionTimeframeServiceTest {
|
||||
""");
|
||||
strategy.setSellConditionJson(null);
|
||||
when(strategyRepo.findById(1L)).thenReturn(Optional.of(strategy));
|
||||
when(dslNormalizer.alignSellStartTimeframeToBuy(org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.any()))
|
||||
.thenAnswer(inv -> inv.getArgument(1));
|
||||
when(dslNormalizer.normalizeJson(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.any()))
|
||||
.thenAnswer(inv -> inv.getArgument(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -71,6 +85,10 @@ class StrategyConditionTimeframeServiceTest {
|
||||
}
|
||||
""");
|
||||
when(strategyRepo.findById(4L)).thenReturn(Optional.of(onlyUpper));
|
||||
when(dslNormalizer.alignSellStartTimeframeToBuy(org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.any()))
|
||||
.thenAnswer(inv -> inv.getArgument(1));
|
||||
when(dslNormalizer.normalizeJson(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.any()))
|
||||
.thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
Set<String> tfs = service.collectForStrategy(4L);
|
||||
assertEquals(Set.of("3m", "5m"), tfs);
|
||||
@@ -95,6 +113,10 @@ class StrategyConditionTimeframeServiceTest {
|
||||
}
|
||||
""");
|
||||
when(strategyRepo.findById(3L)).thenReturn(Optional.of(multi));
|
||||
when(dslNormalizer.alignSellStartTimeframeToBuy(org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.any()))
|
||||
.thenAnswer(inv -> inv.getArgument(1));
|
||||
when(dslNormalizer.normalizeJson(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.any()))
|
||||
.thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
Set<String> tfs = service.collectForStrategy(3L);
|
||||
assertEquals(Set.of("1m", "3m", "5m"), tfs);
|
||||
@@ -127,6 +149,10 @@ class StrategyConditionTimeframeServiceTest {
|
||||
}
|
||||
""");
|
||||
when(strategyRepo.findById(5L)).thenReturn(Optional.of(mixed));
|
||||
when(dslNormalizer.alignSellStartTimeframeToBuy(org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.any()))
|
||||
.thenAnswer(inv -> inv.getArgument(1));
|
||||
when(dslNormalizer.normalizeJson(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.any()))
|
||||
.thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
Set<String> tfs = service.collectForStrategy(5L);
|
||||
assertEquals(Set.of("3m", "5m"), tfs);
|
||||
@@ -147,6 +173,10 @@ class StrategyConditionTimeframeServiceTest {
|
||||
}
|
||||
""");
|
||||
when(strategyRepo.findById(2L)).thenReturn(Optional.of(legacy));
|
||||
when(dslNormalizer.alignSellStartTimeframeToBuy(org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.any()))
|
||||
.thenAnswer(inv -> inv.getArgument(1));
|
||||
when(dslNormalizer.normalizeJson(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.any()))
|
||||
.thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
assertEquals(Set.of("1m"), service.collectForStrategy(2L));
|
||||
}
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.goldenchart.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class StrategyDslTimeframeNormalizerAlignTest {
|
||||
|
||||
private final StrategyDslTimeframeNormalizer normalizer =
|
||||
new StrategyDslTimeframeNormalizer(new ObjectMapper());
|
||||
|
||||
@Test
|
||||
void alignSell_copiesBuyCandleTypesToSell() throws Exception {
|
||||
String buy = """
|
||||
{
|
||||
"type": "TIMEFRAME",
|
||||
"candleType": "5m",
|
||||
"candleTypes": ["5m"],
|
||||
"children": [{ "type": "CONDITION", "condition": { "indicatorType": "RSI" } }]
|
||||
}
|
||||
""";
|
||||
String sell = """
|
||||
{
|
||||
"type": "TIMEFRAME",
|
||||
"candleType": "1m",
|
||||
"children": [{ "type": "CONDITION", "condition": { "indicatorType": "RSI" } }]
|
||||
}
|
||||
""";
|
||||
String aligned = normalizer.alignSellStartTimeframeToBuy(buy, sell);
|
||||
assertTrue(aligned.contains("\"5m\""));
|
||||
assertFalse(aligned.contains("\"candleType\":\"1m\""));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user