n봉 존재 조건 수정

This commit is contained in:
Macbook
2026-06-12 23:40:20 +09:00
parent b55349062f
commit 4a6be82c15
8 changed files with 365 additions and 32 deletions
@@ -412,9 +412,12 @@ public class StrategyDslToTa4jAdapter {
int slopePeriod = cond.path("slopePeriod").asInt(3);
int holdDays = cond.path("holdDays").asInt(3);
int lookbackPeriod = cond.path("lookbackPeriod").asInt(20);
int candleRange = cond.path("candleRange").asInt(1);
String candleRangeMode = resolveCandleRangeMode(cond, candleRange);
if (needsCrossTimeframeComposite(cond, ctx)) {
return buildCrossTimeframeCompositeRule(cond, ctx);
Rule composite = buildCrossTimeframeCompositeRule(cond, ctx);
return applyCandleRangeWindow(composite, candleRangeMode, candleRange);
}
PriceExtremeNorm px = normalizePriceExtremeCondition(cond);
@@ -496,10 +499,11 @@ public class StrategyDslToTa4jAdapter {
yield new BooleanRule(false);
}
};
Rule ranged = applyCandleRangeWindow(core, candleRangeMode, candleRange);
if (px != null) {
return nanSafeCompareRule(core, left, right);
return nanSafeCompareRule(ranged, left, right);
}
return core;
return ranged;
} catch (Exception e) {
log.warn("[Adapter] 조건 빌드 실패 ind={} cond={}: {}", indType, condType, e.getMessage());
return new BooleanRule(false);
@@ -1041,6 +1045,71 @@ public class StrategyDslToTa4jAdapter {
};
}
/** live-conditions·알림 UI — 캔들 범위 접두어 */
public static String candleRangeConditionPrefix(JsonNode cond) {
if (cond == null || cond.isNull()) return "";
int candleRange = cond.path("candleRange").asInt(1);
String mode = cond.path("candleRangeMode").asText("");
if (mode.isBlank()) mode = candleRange <= 1 ? "CURRENT" : "EXISTS_IN";
if ("CURRENT".equals(mode)) return "";
int n = Math.max(2, candleRange);
return switch (mode) {
case "EXISTS_IN" -> "최근 " + n + "봉 내 ";
case "NOT_EXISTS_IN" -> "최근 " + n + "봉 내 없음 · ";
default -> "";
};
}
/** candleRangeMode 미설정 시 candleRange>1 → EXISTS_IN (구버전 호환) */
private String resolveCandleRangeMode(JsonNode cond, int candleRange) {
String mode = cond.path("candleRangeMode").asText("");
if (!mode.isBlank()) return mode;
return candleRange <= 1 ? "CURRENT" : "EXISTS_IN";
}
/** 최근 N봉(현재 봉 포함) 윈도우에 inner 규칙 적용 */
private Rule applyCandleRangeWindow(Rule core, String mode, int candleRange) {
if ("CURRENT".equals(mode)) return core;
int n = Math.max(2, candleRange);
return switch (mode) {
case "EXISTS_IN" -> buildExistsInWindowRule(core, n);
case "NOT_EXISTS_IN" -> buildNotExistsInWindowRule(core, n);
default -> core;
};
}
/** 최근 N봉 중 어느 한 봉이라도 inner 가 true */
private Rule buildExistsInWindowRule(Rule inner, int n) {
if (n <= 1) return inner;
return new Rule() {
@Override
public boolean isSatisfied(int index, TradingRecord record) {
int start = index - n + 1;
for (int i = start; i <= index; i++) {
if (i < 0) continue;
if (inner.isSatisfied(i, record)) return true;
}
return false;
}
};
}
/** 최근 N봉 중 inner 가 true 인 봉이 하나도 없음 */
private Rule buildNotExistsInWindowRule(Rule inner, int n) {
if (n <= 1) return new NotRule(inner);
return new Rule() {
@Override
public boolean isSatisfied(int index, TradingRecord record) {
int start = index - n + 1;
for (int i = start; i <= index; i++) {
if (i < 0) continue;
if (inner.isSatisfied(i, record)) return false;
}
return true;
}
};
}
// ── TRIX 헬퍼 ─────────────────────────────────────────────────────────────
private Indicator<Num> resolvePriceSource(BarSeries s, Map<String, Object> p) {