전략평가 MA 참조에러 수정
This commit is contained in:
@@ -500,7 +500,7 @@ public class LiveConditionStatusService {
|
||||
.displayName(indType)
|
||||
.conditionType(condType)
|
||||
.conditionLabel(condLabel)
|
||||
.thresholdLabel(formatThresholdStatic(c, target))
|
||||
.thresholdLabel(formatThresholdStatic(c, target, visual))
|
||||
.currentValue(null)
|
||||
.targetValue(target)
|
||||
.satisfied(null)
|
||||
@@ -510,7 +510,8 @@ public class LiveConditionStatusService {
|
||||
.build();
|
||||
}
|
||||
|
||||
private String formatThresholdStatic(JsonNode cond, Double target) {
|
||||
private String formatThresholdStatic(JsonNode cond, Double target,
|
||||
Map<String, Map<String, Object>> params) {
|
||||
if (target != null && !target.isNaN()) {
|
||||
String ct = cond.path("conditionType").asText("GT");
|
||||
String v = formatNum(target);
|
||||
@@ -525,7 +526,7 @@ public class LiveConditionStatusService {
|
||||
}
|
||||
String rf = cond.path("rightField").asText("");
|
||||
if (rf != null && !rf.isBlank() && !"NONE".equals(rf)) {
|
||||
return rf.replace('_', ' ');
|
||||
return adapter.formatMovingAverageFieldLabel(rf, params);
|
||||
}
|
||||
return "—";
|
||||
}
|
||||
@@ -576,16 +577,17 @@ public class LiveConditionStatusService {
|
||||
Double rightLive = adapter.readConditionFieldValue(cond, series, params, index, false);
|
||||
if (rightLive != null && !rightLive.isNaN()) {
|
||||
String v = formatNum(rightLive);
|
||||
String fieldLabel = adapter.formatMovingAverageFieldLabel(rf, params);
|
||||
return switch (ct) {
|
||||
case "LT", "CROSS_DOWN" -> "< " + v;
|
||||
case "GT", "CROSS_UP" -> "> " + v;
|
||||
case "GTE" -> "≥ " + v;
|
||||
case "LTE" -> "≤ " + v;
|
||||
case "EQ" -> "= " + v;
|
||||
default -> rf.replace('_', ' ') + " (" + v + ")";
|
||||
default -> fieldLabel + " (" + v + ")";
|
||||
};
|
||||
}
|
||||
return rf.replace('_', ' ');
|
||||
return adapter.formatMovingAverageFieldLabel(rf, params);
|
||||
}
|
||||
return "—";
|
||||
}
|
||||
|
||||
@@ -138,6 +138,13 @@ public class StrategyDslToTa4jAdapter {
|
||||
market, storage, useConfirmedOnly, seriesOverrides);
|
||||
}
|
||||
|
||||
/** MA1~11 슬롯 → period1~11 (SMA 보조지표 설정) */
|
||||
public Map<String, Object> smaParams() {
|
||||
if (indicatorParams == null) return Map.of();
|
||||
Map<String, Object> sma = indicatorParams.get("SMA");
|
||||
return sma != null ? sma : Map.of();
|
||||
}
|
||||
|
||||
/** 사용 중인 시리즈 오버라이드가 있으면 백테스트 모드 */
|
||||
public boolean isBacktest() {
|
||||
return seriesOverrides != null && !seriesOverrides.isEmpty();
|
||||
@@ -233,7 +240,9 @@ public class StrategyDslToTa4jAdapter {
|
||||
: Map.of();
|
||||
|
||||
try {
|
||||
Indicator<Num> ind = resolveField(field, indType, indParams, series, condPeriod, sidePeriod);
|
||||
RuleBuildContext ctx = new RuleBuildContext(series, params != null ? params : Map.of(),
|
||||
Map.of(), null, null, false, Map.of());
|
||||
Indicator<Num> ind = resolveField(field, indType, indParams, series, condPeriod, sidePeriod, cond, ctx);
|
||||
Num v = ind.getValue(index);
|
||||
return v != null ? v.doubleValue() : null;
|
||||
} catch (Exception e) {
|
||||
@@ -722,7 +731,7 @@ public class StrategyDslToTa4jAdapter {
|
||||
case "VOLUME_VALUE" -> new VolumeIndicator(s, 1);
|
||||
default -> {
|
||||
Double hlineVal = IndicatorHlineResolver.resolveThresholdField(
|
||||
cond, field, indType, ctx.indicatorVisual());
|
||||
cond, field, indType, ctx != null ? ctx.indicatorVisual() : Map.of());
|
||||
if (hlineVal != null) {
|
||||
yield new ConstantIndicator<>(s, s.numFactory().numOf(hlineVal));
|
||||
}
|
||||
@@ -730,7 +739,8 @@ public class StrategyDslToTa4jAdapter {
|
||||
if (!Double.isNaN(constant)) {
|
||||
yield new ConstantIndicator<>(s, s.numFactory().numOf(constant));
|
||||
}
|
||||
yield resolveIndicatorField(field, indType, p, s, condPeriod, sidePeriod);
|
||||
Map<String, Object> sma = ctx != null ? ctx.smaParams() : Map.of();
|
||||
yield resolveIndicatorField(field, indType, p, sma, s, condPeriod, sidePeriod);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -764,7 +774,9 @@ public class StrategyDslToTa4jAdapter {
|
||||
}
|
||||
|
||||
private Indicator<Num> resolveIndicatorField(String field, String indType,
|
||||
Map<String, Object> p, BarSeries s,
|
||||
Map<String, Object> p,
|
||||
Map<String, Object> smaParams,
|
||||
BarSeries s,
|
||||
int condPeriod, int sidePeriod) {
|
||||
ClosePriceIndicator close = new ClosePriceIndicator(s);
|
||||
int periodOverride = sidePeriod > 0 ? sidePeriod : (condPeriod > 0 ? condPeriod : -1);
|
||||
@@ -943,12 +955,12 @@ public class StrategyDslToTa4jAdapter {
|
||||
return new LowestValueIndicator(close, period);
|
||||
}
|
||||
if (field.startsWith("MA") && !field.startsWith("MACD")) {
|
||||
try { return new SMAIndicator(close, Integer.parseInt(field.substring(2))); }
|
||||
catch (NumberFormatException ignored) { /* fall */ }
|
||||
int period = resolveMovingAveragePeriod(field, "MA", smaParams);
|
||||
if (period > 0) return new SMAIndicator(close, period);
|
||||
}
|
||||
if (field.startsWith("EMA")) {
|
||||
try { return new EMAIndicator(close, Integer.parseInt(field.substring(3))); }
|
||||
catch (NumberFormatException ignored) { /* fall */ }
|
||||
int period = resolveMovingAveragePeriod(field, "EMA", smaParams);
|
||||
if (period > 0) return new EMAIndicator(close, period);
|
||||
}
|
||||
// 일목균형표
|
||||
if (field.equals("CONVERSION_LINE")) return ichimokuTenkan(s, p);
|
||||
@@ -1304,6 +1316,61 @@ public class StrategyDslToTa4jAdapter {
|
||||
return new PreviousValueIndicator(lowest, 1);
|
||||
}
|
||||
|
||||
// ── MA/EMA DSL (MA1~11 ↔ SMA period1~11) ─────────────────────────────────
|
||||
|
||||
/** SMA 차트 MA1~MA11 기본 기간 — IndicatorService 와 동일 */
|
||||
private static final int[] SMA_DEFAULT_PERIODS =
|
||||
{ 5, 12, 16, 20, 26, 34, 50, 60, 72, 90, 120 };
|
||||
private static final int MA_DSL_SLOT_MAX = 11;
|
||||
|
||||
/**
|
||||
* MA/EMA DSL 필드 → SMA/EMA 계산 기간.
|
||||
* MA3·MA5(1~11): SMA 설정 period1~11. MA20·MA60(>11): 레거시 기간 인코딩.
|
||||
*/
|
||||
public int resolveMovingAveragePeriod(String field, String prefix, Map<String, Object> smaParams) {
|
||||
if (field == null || !field.startsWith(prefix)) return -1;
|
||||
if ("MA".equals(prefix) && field.startsWith("MACD")) return -1;
|
||||
try {
|
||||
int n = Integer.parseInt(field.substring(prefix.length()));
|
||||
if (n >= 1 && n <= MA_DSL_SLOT_MAX) {
|
||||
String periodKey = "period" + n;
|
||||
int def = SMA_DEFAULT_PERIODS[n - 1];
|
||||
return Math.max(1, intP(smaParams, periodKey, def));
|
||||
}
|
||||
if (n > MA_DSL_SLOT_MAX) return n;
|
||||
} catch (NumberFormatException ignored) { /* fall */ }
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** live-conditions·해석 UI — MA5(60일) 형식 */
|
||||
public String formatMovingAverageFieldLabel(String field, Map<String, Map<String, Object>> params) {
|
||||
if (field == null || field.isBlank()) return field;
|
||||
Map<String, Object> sma = params != null ? params.getOrDefault("SMA", Map.of()) : Map.of();
|
||||
if (field.startsWith("MA") && !field.startsWith("MACD")) {
|
||||
int period = resolveMovingAveragePeriod(field, "MA", sma);
|
||||
if (period <= 0) return field;
|
||||
try {
|
||||
int n = Integer.parseInt(field.substring(2));
|
||||
if (n >= 1 && n <= MA_DSL_SLOT_MAX) return "MA" + n + "(" + period + "일)";
|
||||
return "MA(" + period + "일)";
|
||||
} catch (NumberFormatException e) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
if (field.startsWith("EMA")) {
|
||||
int period = resolveMovingAveragePeriod(field, "EMA", sma);
|
||||
if (period <= 0) return field;
|
||||
try {
|
||||
int n = Integer.parseInt(field.substring(3));
|
||||
if (n >= 1 && n <= MA_DSL_SLOT_MAX) return "EMA" + n + "(" + period + "일)";
|
||||
return "EMA(" + period + "일)";
|
||||
} catch (NumberFormatException e) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
// ── 파라미터 헬퍼 ─────────────────────────────────────────────────────────
|
||||
|
||||
private int intP(Map<String, Object> p, String k, int def) {
|
||||
|
||||
Reference in New Issue
Block a user