백엔드 전략 조건 평가 버그 3건 수정

1. evaluateOverallRule: 5m 전략 평가 시 1m 고정 시리즈 사용 버그 수정
   - DSL에서 주 시간봉(TIMEFRAME 노드 candleType)을 추출하여 해당 시리즈 사용
   - 없으면 1m 폴백 유지

2. buildConditionRule: kLength 등 지표 파라미터를 조건 노드 params에서 우선 읽도록 수정
   - 조건 DSL의 params 필드가 있으면 전역 indicator settings보다 우선 적용
   - 서버 DB에 indicator settings 없어도 전략 저장 당시 값 사용 가능

3. LiveConditionController: deviceId 헤더를 indicatorSettingsService에 전달
   - x-device-id 헤더를 읽어 device 단위 지표 설정 조회 지원

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Macbook
2026-06-11 08:28:05 +09:00
parent b21d40333c
commit 090752f42c
3 changed files with 89 additions and 7 deletions
@@ -174,9 +174,15 @@ public class LiveConditionStatusService {
objectMapper.readTree(conditionJson);
if (dsl == null || dsl.isNull()) return null;
// 기준 시리즈: 1m 봉
if (!ta4jStorage.exists(market, "1m")) return null;
BarSeries series = ta4jStorage.getOrCreate(market, "1m");
// DSL 루트의 주 시간봉 추출 (TIMEFRAME 노드가 있으면 해당 봉 사용)
// 5m 전략에서 1m 시리즈로 평가하면 CROSS_UP 등이 부정확해지는 버그 수정
String primaryTf = extractPrimaryTimeframe(dsl);
if (!ta4jStorage.exists(market, primaryTf)) {
// 지정 봉이 없으면 1m으로 폴백
primaryTf = "1m";
if (!ta4jStorage.exists(market, "1m")) return null;
}
BarSeries series = ta4jStorage.getOrCreate(market, primaryTf);
if (series.isEmpty()) return null;
StrategyDslToTa4jAdapter.RuleBuildContext ctx =
@@ -190,6 +196,38 @@ public class LiveConditionStatusService {
}
}
/**
* DSL 트리에서 주 시간봉을 추출한다.
* TIMEFRAME 노드가 있으면 그 candleType을 반환, 없으면 "1m".
* 복수의 TIMEFRAME이 있으면 가장 길게 등장하는 봉 우선 (다수결).
*/
private String extractPrimaryTimeframe(com.fasterxml.jackson.databind.JsonNode dsl) {
Map<String, Integer> tfCount = new java.util.LinkedHashMap<>();
collectTimeframes(dsl, tfCount);
if (tfCount.isEmpty()) return "1m";
return tfCount.entrySet().stream()
.max(java.util.Map.Entry.comparingByValue())
.map(java.util.Map.Entry::getKey)
.orElse("1m");
}
private void collectTimeframes(com.fasterxml.jackson.databind.JsonNode node,
Map<String, Integer> out) {
if (node == null || node.isNull()) return;
String type = node.path("type").asText("CONDITION");
if ("TIMEFRAME".equals(type)) {
String tf = LiveStrategyTimeframeService.normalize(
node.path("candleType").asText("1m"));
out.merge(tf, 1, Integer::sum);
}
com.fasterxml.jackson.databind.JsonNode children = node.path("children");
if (children.isArray()) {
for (com.fasterxml.jackson.databind.JsonNode c : children) collectTimeframes(c, out);
}
com.fasterxml.jackson.databind.JsonNode child = node.path("child");
if (!child.isMissingNode()) collectTimeframes(child, out);
}
private void collectConditions(String json, String timeframe, String side,
List<PendingCond> out) {
if (json == null || json.isBlank()) return;