전략 시간봉 오류 수정

This commit is contained in:
Macbook
2026-05-28 00:36:49 +09:00
parent 2713b2951d
commit 4f6694b206
16 changed files with 285 additions and 127 deletions
@@ -68,8 +68,20 @@ public class StrategyConditionTimeframeService {
ensureDslRepaired(strategy);
Set<String> out = new LinkedHashSet<>();
collectFromJson(strategy.getBuyConditionJson(), out, strategy.getName());
collectFromJson(strategy.getSellConditionJson(), out, strategy.getName());
boolean buyScoped = collectStartScopeFromJson(strategy.getBuyConditionJson(), out);
boolean sellScoped = collectStartScopeFromJson(strategy.getSellConditionJson(), out);
if (!out.isEmpty()) {
return out;
}
// 레거시: START(TIMEFRAME) 래핑 없는 DSL
if (!buyScoped) {
collectLegacyFromJson(strategy.getBuyConditionJson(), out, strategy.getName());
}
if (!sellScoped) {
collectLegacyFromJson(strategy.getSellConditionJson(), out, strategy.getName());
}
if (out.isEmpty()) {
String fromName = StrategyDslTimeframeNormalizer.inferFromStrategyName(strategy.getName());
@@ -106,19 +118,39 @@ public class StrategyConditionTimeframeService {
}
}
private void collectFromJson(String json, Set<String> out, String strategyName) {
if (json == null || json.isBlank()) return;
/**
* START 루트 TIMEFRAME(candleTypes)만 수집 — 조건 노드 leftCandleType·내부 1m 기본값은 제외.
*
* @return JSON 에 START 스코프가 있으면 true (빈 조건이어도)
*/
private boolean collectStartScopeFromJson(String json, Set<String> out) {
if (json == null || json.isBlank()) return false;
try {
collectFromNode(objectMapper.readTree(json), out, strategyName);
return collectStartScopeFromNode(objectMapper.readTree(json), out);
} catch (Exception e) {
log.warn("[StrategyTimeframes] JSON 파싱 실패: {}", e.getMessage());
return false;
}
}
private boolean collectStartScopeFromNode(JsonNode node, Set<String> out) {
if (node == null || node.isNull()) return false;
return collectTimeframesFromNode(node, out);
}
/** 레거시 DSL — START 래핑 없을 때만 조건 트리·전략명에서 추론 */
private void collectLegacyFromJson(String json, Set<String> out, String strategyName) {
if (json == null || json.isBlank()) return;
try {
collectLegacyFromNode(objectMapper.readTree(json), out, strategyName);
} catch (Exception e) {
log.warn("[StrategyTimeframes] legacy JSON 파싱 실패: {}", e.getMessage());
addFallbackTimeframe(out, strategyName);
}
}
private void collectFromNode(JsonNode node, Set<String> out, String strategyName) {
private void collectLegacyFromNode(JsonNode node, Set<String> out, String strategyName) {
if (node == null || node.isNull()) return;
if (collectTimeframesFromNode(node, out)) return;
Set<String> meaningful = StrategyDslTimeframeNormalizer.collectMeaningfulCandleTypes(node);
if (!meaningful.isEmpty()) {
@@ -101,6 +101,38 @@ class StrategyConditionTimeframeServiceTest {
assertTrue(service.usesTimeframe(3L, "3m"));
}
@Test
void collectForStrategy_buyStartScopeIgnoresSellLegacy1m() {
GcStrategy mixed = new GcStrategy();
mixed.setId(5L);
mixed.setBuyConditionJson("""
{
"type": "TIMEFRAME",
"candleType": "3m",
"candleTypes": ["3m", "5m"],
"children": [{
"type": "CONDITION",
"condition": { "indicatorType": "STOCHASTIC", "period": 14 }
}]
}
""");
mixed.setSellConditionJson("""
{
"type": "TIMEFRAME",
"candleType": "1m",
"children": [{
"type": "CONDITION",
"condition": { "indicatorType": "STOCHASTIC", "period": 14 }
}]
}
""");
when(strategyRepo.findById(5L)).thenReturn(Optional.of(mixed));
Set<String> tfs = service.collectForStrategy(5L);
assertEquals(Set.of("3m", "5m"), tfs);
assertFalse(service.usesTimeframe(5L, "1m"));
}
@Test
void collectForStrategy_legacyTreeDefaultsTo1m() {
GcStrategy legacy = new GcStrategy();