전략편집기 복합지표 기능 반영

This commit is contained in:
Macbook
2026-05-25 01:34:52 +09:00
parent ca249ad318
commit 20dd257c29
21 changed files with 1781 additions and 183 deletions
@@ -158,6 +158,9 @@ public class StrategyDslToTa4jAdapter {
String condType = cond.path("conditionType").asText("GT");
String leftField = cond.path("leftField").asText("NONE");
String rightField = cond.path("rightField").asText("NONE");
int condPeriod = cond.path("period").asInt(-1);
int leftPeriod = cond.path("leftPeriod").asInt(-1);
int rightPeriod = cond.path("rightPeriod").asInt(-1);
int slopePeriod = cond.path("slopePeriod").asInt(3);
int holdDays = cond.path("holdDays").asInt(3);
@@ -168,8 +171,8 @@ public class StrategyDslToTa4jAdapter {
: Map.of();
try {
Indicator<Num> left = resolveField(leftField, indType, indParams, series);
Indicator<Num> right = resolveField(rightField, indType, indParams, series);
Indicator<Num> left = resolveField(leftField, indType, indParams, series, condPeriod, leftPeriod);
Indicator<Num> right = resolveField(rightField, indType, indParams, series, condPeriod, rightPeriod);
return switch (condType) {
case "GT" -> new OverIndicatorRule(left, right);
@@ -225,7 +228,8 @@ public class StrategyDslToTa4jAdapter {
// ── 필드 Resolver ─────────────────────────────────────────────────────────
private Indicator<Num> resolveField(String field, String indType,
Map<String, Object> p, BarSeries s) {
Map<String, Object> p, BarSeries s,
int condPeriod, int sidePeriod) {
if (field == null || field.isBlank() || field.equals("NONE")) {
return new ConstantIndicator<>(s, s.numFactory().numOf(0));
}
@@ -240,7 +244,7 @@ public class StrategyDslToTa4jAdapter {
if (!Double.isNaN(constant)) {
yield new ConstantIndicator<>(s, s.numFactory().numOf(constant));
}
yield resolveIndicatorField(field, indType, p, s);
yield resolveIndicatorField(field, indType, p, s, condPeriod, sidePeriod);
}
};
}
@@ -266,15 +270,25 @@ public class StrategyDslToTa4jAdapter {
}
private Indicator<Num> resolveIndicatorField(String field, String indType,
Map<String, Object> p, BarSeries s) {
Map<String, Object> p, BarSeries s,
int condPeriod, int sidePeriod) {
ClosePriceIndicator close = new ClosePriceIndicator(s);
int periodOverride = sidePeriod > 0 ? sidePeriod : (condPeriod > 0 ? condPeriod : -1);
// CCI — 프론트엔드 indicatorRegistry 기본값 20 과 통일
// CCI — 기간 접미사(CCI_VALUE_13) 또는 기본 CCI_VALUE
if (field.startsWith("CCI_VALUE_")) {
int period = parseTrailingInt(field, "CCI_VALUE_", intP(p, "length", 13));
return new CCIIndicator(s, period);
}
if (field.equals("CCI_VALUE") || indType.equals("CCI"))
return new CCIIndicator(s, intP(p, "length", 13));
// RSI — 표준 기본값 14
return new CCIIndicator(s, periodOverride > 0 ? periodOverride : intP(p, "length", 13));
// RSI — 기간 접미사(RSI_VALUE_9) 또는 기본 RSI_VALUE
if (field.startsWith("RSI_VALUE_")) {
int period = parseTrailingInt(field, "RSI_VALUE_", intP(p, "length", 14));
return new RSIIndicator(close, period);
}
if (field.equals("RSI_VALUE"))
return new RSIIndicator(close, intP(p, "length", 14));
return new RSIIndicator(close, periodOverride > 0 ? periodOverride : intP(p, "length", 14));
// MACD
if (field.equals("MACD_LINE")) {
return new MACDIndicator(close, intP(p, "fastLength", 12), intP(p, "slowLength", 26));
@@ -289,6 +303,10 @@ public class StrategyDslToTa4jAdapter {
return NumericIndicator.of(macd).minus(new EMAIndicator(macd, intP(p, "signalLength", 9)));
}
// ADX / DMI
if (field.startsWith("ADX_VALUE_")) {
int period = parseTrailingInt(field, "ADX_VALUE_", intP(p, "adxSmoothing", 14));
return new ADXIndicator(s, intP(p, "diLength", 14), period);
}
if (field.equals("ADX_VALUE"))
return new ADXIndicator(s, intP(p, "diLength", 14), intP(p, "adxSmoothing", 14));
if (field.equals("PDI") || field.equals("PLUS_DI")) {
@@ -316,6 +334,12 @@ public class StrategyDslToTa4jAdapter {
return new SMAIndicator(slowK, dSmooth);
}
// TRIX — ln(종가) → 3×EMA → Δ×10000 (업비트)
if (field.startsWith("TRIX_VALUE_")) {
int len = parseTrailingInt(field, "TRIX_VALUE_", intP(p, "length", 12));
EMAIndicator e3 = tripleLogEma(close, len);
PreviousValueIndicator prev3 = new PreviousValueIndicator(e3);
return NumericIndicator.of(e3).minus(prev3).multipliedBy(10_000);
}
if (field.equals("TRIX_VALUE")) {
int len = intP(p, "length", 12);
EMAIndicator e3 = tripleLogEma(close, len);
@@ -342,6 +366,10 @@ public class StrategyDslToTa4jAdapter {
};
}
// VR
if (field.startsWith("VR_VALUE_")) {
int period = parseTrailingInt(field, "VR_VALUE_", intP(p, "length", 10));
return vrIndicator(s, period);
}
if (field.equals("VR_VALUE") || indType.equals("VR"))
return vrIndicator(s, intP(p, "length", 10));
// 이격도 DISPARITY5 → period 5
@@ -349,12 +377,24 @@ public class StrategyDslToTa4jAdapter {
int period = parseTrailingInt(field, "DISPARITY", 20);
return disparityIndicator(s, period, p);
}
if (field.startsWith("PSY_VALUE_")) {
int period = parseTrailingInt(field, "PSY_VALUE_", intP(p, "length", 12));
return newPsychIndicator(s, period, false);
}
if (field.equals("PSY_VALUE") || field.equals("NEW_PSY_VALUE")
|| indType.equals("PSYCHOLOGICAL") || indType.equals("NEW_PSYCHOLOGICAL"))
return newPsychIndicator(s, intP(p, "length", 12), false);
if (field.startsWith("INVEST_PSY_VALUE_")) {
int period = parseTrailingInt(field, "INVEST_PSY_VALUE_", intP(p, "length", 10));
return newPsychIndicator(s, period, true);
}
if (field.equals("INVEST_PSY_VALUE") || indType.equals("INVEST_PSYCHOLOGICAL"))
return newPsychIndicator(s, intP(p, "length", 10), true);
// Williams %R
if (field.startsWith("WILLIAMS_R_VALUE_")) {
int period = parseTrailingInt(field, "WILLIAMS_R_VALUE_", intP(p, "length", 14));
return new WilliamsRIndicator(s, period);
}
if (field.equals("WILLIAMS_R_VALUE")) return new WilliamsRIndicator(s, intP(p, "length", 14));
// Donchian Channel — DC_UPPER_20 / DC_LOWER_10 등 기간 접미사
if (field.startsWith("DC_UPPER_")) {