N봉 기울기 판단로직 수정

This commit is contained in:
Macbook
2026-06-16 09:12:24 +09:00
parent 518b69cdc6
commit f85c0bef20
10 changed files with 642 additions and 367 deletions
@@ -426,7 +426,7 @@ public class StrategyDslToTa4jAdapter {
if (needsCrossTimeframeComposite(cond, ctx)) {
Rule composite = buildCrossTimeframeCompositeRule(cond, ctx);
return applyCandleRangeWindow(composite, candleRangeMode, candleRange);
return applyCandleRangeWindow(composite, candleRangeMode, candleRange, condType);
}
PriceExtremeNorm px = normalizePriceExtremeCondition(cond);
@@ -466,8 +466,8 @@ public class StrategyDslToTa4jAdapter {
// CrossedUp/Down: ta4j 0.22 에서는 2인자 생성자만 있음
case "CROSS_UP" -> new CrossedUpIndicatorRule(left, right);
case "CROSS_DOWN" -> new CrossedDownIndicatorRule(left, right);
case "SLOPE_UP" -> new IsRisingRule(left, slopePeriod);
case "SLOPE_DOWN" -> new IsFallingRule(left, slopePeriod);
case "SLOPE_UP" -> buildSlopeTrendRule(left, true, candleRangeMode, candleRange, slopePeriod);
case "SLOPE_DOWN" -> buildSlopeTrendRule(left, false, candleRangeMode, candleRange, slopePeriod);
case "DIFF_GT" -> {
double v = cond.path("compareValue").asDouble(0);
yield new OverIndicatorRule(
@@ -508,7 +508,7 @@ public class StrategyDslToTa4jAdapter {
yield new BooleanRule(false);
}
};
Rule ranged = applyCandleRangeWindow(core, candleRangeMode, candleRange);
Rule ranged = applyCandleRangeWindow(core, candleRangeMode, candleRange, condType);
if (px != null) {
return nanSafeCompareRule(ranged, left, right);
}
@@ -789,6 +789,18 @@ public class StrategyDslToTa4jAdapter {
if (field.equals("CCI_VALUE") || indType.equals("CCI"))
return new CciOnSourceIndicator(resolvePriceSource(s, CciOnSourceIndicator.normalizeParams(p)),
periodOverride > 0 ? periodOverride : intP(p, "length", 13));
if (field.equals("CCI_SIGNAL")) {
Map<String, Object> norm = CciOnSourceIndicator.normalizeParams(p);
CciOnSourceIndicator cci = new CciOnSourceIndicator(resolvePriceSource(s, norm), intP(p, "length", 13));
String maType = p != null ? p.getOrDefault("maType", "SMA").toString() : "SMA";
int maLen = intP(p, "maLength", 10);
if ("None".equals(maType)) return cci;
return switch (maType) {
case "EMA" -> new EMAIndicator(cci, maLen);
case "WMA" -> new WMAIndicator(cci, maLen);
default -> new SMAIndicator(cci, maLen);
};
}
// RSI — 기간 접미사(RSI_VALUE_9) 또는 기본 RSI_VALUE
if (field.startsWith("RSI_VALUE_")) {
int period = parseTrailingInt(field, "RSI_VALUE_", intP(p, "length", 14));
@@ -796,6 +808,17 @@ public class StrategyDslToTa4jAdapter {
}
if (field.equals("RSI_VALUE"))
return new RSIIndicator(close, periodOverride > 0 ? periodOverride : intP(p, "length", 14));
if (field.equals("RSI_SIGNAL")) {
RSIIndicator rsi = new RSIIndicator(close, intP(p, "length", 14));
String maType = p != null ? p.getOrDefault("maType", "SMA").toString() : "SMA";
int maLen = intP(p, "maLength", 14);
if ("None".equals(maType)) return rsi;
return switch (maType) {
case "EMA" -> new EMAIndicator(rsi, maLen);
case "WMA" -> new WMAIndicator(rsi, maLen);
default -> new SMAIndicator(rsi, maLen);
};
}
// MACD
if (field.equals("MACD_LINE")) {
return new MACDIndicator(close, intP(p, "fastLength", 12), intP(p, "slowLength", 26));
@@ -1062,8 +1085,16 @@ public class StrategyDslToTa4jAdapter {
if (cond == null || cond.isNull()) return "";
int candleRange = cond.path("candleRange").asInt(1);
String mode = cond.path("candleRangeMode").asText("");
String condType = cond.path("conditionType").asText("");
if (mode.isBlank()) return "";
int n = Math.max(2, candleRange);
if (isSlopeConditionType(condType) && ("EXISTS_IN".equals(mode) || "NOT_EXISTS_IN".equals(mode))) {
return switch (mode) {
case "EXISTS_IN" -> "최근 " + n + "봉 구간 ";
case "NOT_EXISTS_IN" -> "최근 " + n + "봉 구간 비추세 · ";
default -> "";
};
}
return switch (mode) {
case "EXISTS_IN" -> "최근 " + n + "봉 내 ";
case "NOT_EXISTS_IN" -> "최근 " + n + "봉 내 없음 · ";
@@ -1079,8 +1110,10 @@ public class StrategyDslToTa4jAdapter {
}
/** 최근 N봉(현재 봉 포함) 윈도우에 inner 규칙 적용 */
private Rule applyCandleRangeWindow(Rule core, String mode, int candleRange) {
private Rule applyCandleRangeWindow(Rule core, String mode, int candleRange, String condType) {
if ("CURRENT".equals(mode)) return core;
// 기울기 + N봉 모드: buildSlopeTrendRule 에서 구간 추세로 이미 평가
if (isSlopeConditionType(condType)) return core;
int n = Math.max(2, candleRange);
return switch (mode) {
case "EXISTS_IN" -> buildExistsInWindowRule(core, n);
@@ -1089,6 +1122,35 @@ public class StrategyDslToTa4jAdapter {
};
}
/**
* 기울기 조건.
* CURRENT: slopePeriod 구간 추세(현재 봉 기준).
* EXISTS_IN / NOT_EXISTS_IN: candleRange(N) 구간 전체 그래프가 상승/하락 상태인지 현재 봉에서 판정
* (N봉 내 임의 시점의 짧은 기울기 존재 여부가 아님).
*/
private Rule buildSlopeTrendRule(
Indicator<Num> left,
boolean up,
String candleRangeMode,
int candleRange,
int slopePeriod) {
int bars = slopePeriod;
if ("EXISTS_IN".equals(candleRangeMode) || "NOT_EXISTS_IN".equals(candleRangeMode)) {
bars = Math.max(2, candleRange);
} else {
bars = Math.max(1, slopePeriod);
}
Rule trend = up ? new IsRisingRule(left, bars) : new IsFallingRule(left, bars);
if ("NOT_EXISTS_IN".equals(candleRangeMode)) {
return new NotRule(trend);
}
return trend;
}
private static boolean isSlopeConditionType(String condType) {
return "SLOPE_UP".equals(condType) || "SLOPE_DOWN".equals(condType);
}
/** 최근 N봉 중 어느 한 봉이라도 inner 가 true */
private Rule buildExistsInWindowRule(Rule inner, int n) {
if (n <= 1) return inner;