전략편집기 기준값 보조지표 설정값 동기화
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
package com.goldenchart.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 보조지표 visual_config hline — 전략 DSL HL_OVER/HL_MID/HL_UNDER 역할 해석.
|
||||
*/
|
||||
public final class IndicatorHlineResolver {
|
||||
|
||||
private IndicatorHlineResolver() {}
|
||||
|
||||
public static Double resolveThresholdField(
|
||||
JsonNode cond,
|
||||
String field,
|
||||
String dslIndicatorType,
|
||||
Map<String, Map<String, Object>> indicatorVisual) {
|
||||
|
||||
if (field == null || field.isBlank()) return null;
|
||||
|
||||
boolean override = cond != null && cond.path("thresholdOverride").asBoolean(false);
|
||||
if (override) {
|
||||
if (cond.has("targetValue") && !cond.path("targetValue").isNull()) {
|
||||
return cond.path("targetValue").asDouble();
|
||||
}
|
||||
if (field.startsWith("K_")) {
|
||||
try { return Double.parseDouble(field.substring(2)); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
return legacyNumericField(field);
|
||||
}
|
||||
|
||||
if ("HL_OVER".equals(field) || "HL_MID".equals(field) || "HL_UNDER".equals(field)) {
|
||||
Double fromVisual = fromHlineRole(field, dslIndicatorType, indicatorVisual);
|
||||
if (fromVisual != null) return fromVisual;
|
||||
}
|
||||
|
||||
if (field.startsWith("K_")) {
|
||||
Double fromVisual = inferRoleFromLegacyK(field, dslIndicatorType, indicatorVisual);
|
||||
if (fromVisual != null) return fromVisual;
|
||||
try { return Double.parseDouble(field.substring(2)); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
return legacyNumericField(field);
|
||||
}
|
||||
|
||||
private static Double fromHlineRole(
|
||||
String role,
|
||||
String dslIndicatorType,
|
||||
Map<String, Map<String, Object>> indicatorVisual) {
|
||||
|
||||
String registryKey = StrategyDslToTa4jAdapter.registryKeyForDsl(dslIndicatorType);
|
||||
if (indicatorVisual == null || registryKey == null) return defaultForRole(dslIndicatorType, role);
|
||||
|
||||
Map<String, Object> visual = indicatorVisual.get(registryKey);
|
||||
if (visual == null) return defaultForRole(dslIndicatorType, role);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> hlines = (List<Map<String, Object>>) visual.get("hlines");
|
||||
if (hlines == null || hlines.isEmpty()) return defaultForRole(dslIndicatorType, role);
|
||||
|
||||
String label = switch (role) {
|
||||
case "HL_OVER" -> "과열선";
|
||||
case "HL_MID" -> "중앙선";
|
||||
case "HL_UNDER" -> "침체선";
|
||||
default -> null;
|
||||
};
|
||||
if (label == null) return null;
|
||||
|
||||
for (Map<String, Object> h : hlines) {
|
||||
Object lbl = h.get("label");
|
||||
if (lbl != null && label.equals(lbl.toString())) {
|
||||
Object price = h.get("price");
|
||||
if (price instanceof Number n) return n.doubleValue();
|
||||
}
|
||||
}
|
||||
// 기준선 폴백 (Disparity 등)
|
||||
if ("HL_MID".equals(role)) {
|
||||
for (Map<String, Object> h : hlines) {
|
||||
Object lbl = h.get("label");
|
||||
if (lbl != null && "기준선".equals(lbl.toString())) {
|
||||
Object price = h.get("price");
|
||||
if (price instanceof Number n) return n.doubleValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultForRole(dslIndicatorType, role);
|
||||
}
|
||||
|
||||
private static Double inferRoleFromLegacyK(
|
||||
String kField,
|
||||
String dslIndicatorType,
|
||||
Map<String, Map<String, Object>> indicatorVisual) {
|
||||
try {
|
||||
double val = Double.parseDouble(kField.substring(2));
|
||||
for (String role : List.of("HL_OVER", "HL_MID", "HL_UNDER")) {
|
||||
Double roleVal = fromHlineRole(role, dslIndicatorType, indicatorVisual);
|
||||
if (roleVal != null && Math.abs(roleVal - val) < 0.0001) {
|
||||
return roleVal;
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException ignored) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Double legacyNumericField(String field) {
|
||||
if (field.contains("_NEG")) {
|
||||
String[] parts = field.split("_NEG");
|
||||
try { return -Double.parseDouble(parts[parts.length - 1]); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
if ("ZERO_LINE".equals(field)) return 0.0;
|
||||
int idx = field.lastIndexOf('_');
|
||||
if (idx >= 0 && idx < field.length() - 1) {
|
||||
try { return Double.parseDouble(field.substring(idx + 1)); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Double defaultForRole(String dslIndicatorType, String role) {
|
||||
return switch (dslIndicatorType + ":" + role) {
|
||||
case "RSI:HL_OVER" -> 70.0;
|
||||
case "RSI:HL_MID" -> 50.0;
|
||||
case "RSI:HL_UNDER" -> 30.0;
|
||||
case "CCI:HL_OVER" -> 100.0;
|
||||
case "CCI:HL_MID" -> 0.0;
|
||||
case "CCI:HL_UNDER" -> -100.0;
|
||||
case "STOCHASTIC:HL_OVER" -> 80.0;
|
||||
case "STOCHASTIC:HL_MID" -> 50.0;
|
||||
case "STOCHASTIC:HL_UNDER" -> 20.0;
|
||||
case "ADX:HL_MID" -> 25.0;
|
||||
case "WILLIAMS_R:HL_OVER" -> -20.0;
|
||||
case "DISPARITY:HL_MID" -> 100.0;
|
||||
case "VOLUME_OSC:HL_MID" -> 0.0;
|
||||
case "TRIX:HL_MID" -> 0.0;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,8 @@ public class LiveConditionStatusService {
|
||||
|
||||
Map<String, Map<String, Object>> params =
|
||||
indicatorSettingsService.getAll(userId, deviceId);
|
||||
Map<String, Map<String, Object>> visual =
|
||||
indicatorSettingsService.getAllVisual(userId, deviceId);
|
||||
|
||||
List<PendingCond> pending = new ArrayList<>();
|
||||
collectConditions(strategy.getBuyConditionJson(), "1m", "buy", pending);
|
||||
@@ -84,12 +86,12 @@ public class LiveConditionStatusService {
|
||||
for (PendingCond p : pending) {
|
||||
String tf = LiveStrategyTimeframeService.normalize(p.timeframe());
|
||||
if (!ta4jStorage.exists(market, tf)) {
|
||||
rows.add(toRowUnevaluated(p));
|
||||
rows.add(toRowUnevaluated(p, visual));
|
||||
continue;
|
||||
}
|
||||
BarSeries series = ta4jStorage.getOrCreate(market, tf);
|
||||
if (series.isEmpty()) {
|
||||
rows.add(toRowUnevaluated(p));
|
||||
rows.add(toRowUnevaluated(p, visual));
|
||||
continue;
|
||||
}
|
||||
int index = series.getEndIndex();
|
||||
@@ -100,12 +102,12 @@ public class LiveConditionStatusService {
|
||||
|
||||
StrategyDslToTa4jAdapter.RuleBuildContext ctx =
|
||||
new StrategyDslToTa4jAdapter.RuleBuildContext(
|
||||
series, params, market, ta4jStorage);
|
||||
series, params, visual, market, ta4jStorage);
|
||||
Rule rule = adapter.toRule(wrapper, ctx);
|
||||
boolean satisfied = rule.isSatisfied(index, null);
|
||||
Double current = adapter.readConditionFieldValue(
|
||||
p.condition(), series, params, index, true);
|
||||
Double target = readTargetNumeric(p.condition());
|
||||
Double target = readTargetNumeric(p.condition(), visual);
|
||||
if (target == null) {
|
||||
target = adapter.readConditionFieldValue(
|
||||
p.condition(), series, params, index, false);
|
||||
@@ -115,7 +117,7 @@ public class LiveConditionStatusService {
|
||||
if (satisfied) met++;
|
||||
} catch (Exception e) {
|
||||
log.debug("[LiveCondition] eval fail {} {}: {}", market, p.id(), e.getMessage());
|
||||
rows.add(toRowUnevaluated(p));
|
||||
rows.add(toRowUnevaluated(p, visual));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,13 +196,14 @@ public class LiveConditionStatusService {
|
||||
}
|
||||
}
|
||||
|
||||
private LiveConditionRowDto toRowUnevaluated(PendingCond p) {
|
||||
private LiveConditionRowDto toRowUnevaluated(PendingCond p,
|
||||
Map<String, Map<String, Object>> visual) {
|
||||
JsonNode c = p.condition();
|
||||
String indType = c.path("indicatorType").asText("");
|
||||
String plotKey = plotKeyFromCondition(c, indType);
|
||||
String condType = c.path("conditionType").asText("GT");
|
||||
String condLabel = CONDITION_LABEL.getOrDefault(condType, condType);
|
||||
Double target = readTargetNumeric(c);
|
||||
Double target = readTargetNumeric(c, visual);
|
||||
return LiveConditionRowDto.builder()
|
||||
.id(p.id())
|
||||
.indicatorType(indType)
|
||||
@@ -300,14 +303,17 @@ public class LiveConditionStatusService {
|
||||
return String.format("%.2f", v);
|
||||
}
|
||||
|
||||
private Double readTargetNumeric(JsonNode cond) {
|
||||
private Double readTargetNumeric(JsonNode cond, Map<String, Map<String, Object>> visual) {
|
||||
String indType = cond.path("indicatorType").asText("");
|
||||
String rf = cond.path("rightField").asText("");
|
||||
Double resolved = IndicatorHlineResolver.resolveThresholdField(cond, rf, indType, visual);
|
||||
if (resolved != null) return resolved;
|
||||
if (cond.has("targetValue") && !cond.path("targetValue").isNull()) {
|
||||
return cond.path("targetValue").asDouble();
|
||||
}
|
||||
if (cond.has("compareValue") && !cond.path("compareValue").isNull()) {
|
||||
return cond.path("compareValue").asDouble();
|
||||
}
|
||||
String rf = cond.path("rightField").asText("");
|
||||
if (rf.startsWith("K_")) {
|
||||
try { return Double.parseDouble(rf.substring(2)); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
@@ -322,6 +322,8 @@ public class LiveStrategyEvaluator {
|
||||
BarSeries series = ta4jStorage.getOrCreate(market, candleType);
|
||||
Map<String, Map<String, Object>> indicatorParams =
|
||||
indicatorSettingsService.getAll(userId, deviceId);
|
||||
Map<String, Map<String, Object>> indicatorVisual =
|
||||
indicatorSettingsService.getAllVisual(userId, deviceId);
|
||||
|
||||
int barCount = series.getBarCount();
|
||||
if (barCount < 2) {
|
||||
@@ -333,10 +335,10 @@ public class LiveStrategyEvaluator {
|
||||
try {
|
||||
Rule entryRule = triggerBranchEvaluator.buildTriggerRule(
|
||||
strategy.getBuyConditionJson(), market, strategyId, "buy",
|
||||
candleType, indicatorParams, ta4jStorage);
|
||||
candleType, indicatorParams, indicatorVisual, ta4jStorage);
|
||||
Rule exitRule = triggerBranchEvaluator.buildTriggerRule(
|
||||
strategy.getSellConditionJson(), market, strategyId, "sell",
|
||||
candleType, indicatorParams, ta4jStorage);
|
||||
candleType, indicatorParams, indicatorVisual, ta4jStorage);
|
||||
return new TriggerRules(entryRule, exitRule);
|
||||
} catch (Exception e) {
|
||||
log.error("[Evaluator] Trigger Rule 빌드 실패 strategyId={}: {}", strategyId, e.getMessage());
|
||||
|
||||
@@ -96,6 +96,11 @@ public class StrategyDslToTa4jAdapter {
|
||||
* 매핑 없으면 원본 반환 (이미 레지스트리 형식인 경우).
|
||||
*/
|
||||
private static String toRegistryKey(String dslType) {
|
||||
return registryKeyForDsl(dslType);
|
||||
}
|
||||
|
||||
/** DSL indicatorType → DB 레지스트리 키 (외부 hline resolver용) */
|
||||
public static String registryKeyForDsl(String dslType) {
|
||||
return DSL_TO_REGISTRY.getOrDefault(dslType, dslType);
|
||||
}
|
||||
|
||||
@@ -104,9 +109,18 @@ public class StrategyDslToTa4jAdapter {
|
||||
public record RuleBuildContext(
|
||||
BarSeries primarySeries,
|
||||
Map<String, Map<String, Object>> indicatorParams,
|
||||
Map<String, Map<String, Object>> indicatorVisual,
|
||||
String market,
|
||||
Ta4jStorage storage
|
||||
) {
|
||||
/** visual 미전달 호환 */
|
||||
public RuleBuildContext(BarSeries primarySeries,
|
||||
Map<String, Map<String, Object>> indicatorParams,
|
||||
String market,
|
||||
Ta4jStorage storage) {
|
||||
this(primarySeries, indicatorParams, Map.of(), market, storage);
|
||||
}
|
||||
|
||||
BarSeries resolveSeries(String candleType) {
|
||||
String ct = LiveStrategyTimeframeService.normalize(candleType);
|
||||
if (storage != null && market != null && !market.isBlank()
|
||||
@@ -119,13 +133,20 @@ public class StrategyDslToTa4jAdapter {
|
||||
|
||||
public Rule toRule(JsonNode node, BarSeries series,
|
||||
Map<String, Map<String, Object>> indicatorParams) {
|
||||
return toRule(node, new RuleBuildContext(series, indicatorParams, null, null));
|
||||
return toRule(node, new RuleBuildContext(series, indicatorParams, Map.of(), null, null));
|
||||
}
|
||||
|
||||
public Rule toRule(JsonNode node, BarSeries series,
|
||||
Map<String, Map<String, Object>> indicatorParams,
|
||||
String market, Ta4jStorage storage) {
|
||||
return toRule(node, new RuleBuildContext(series, indicatorParams, market, storage));
|
||||
return toRule(node, new RuleBuildContext(series, indicatorParams, Map.of(), market, storage));
|
||||
}
|
||||
|
||||
public Rule toRule(JsonNode node, BarSeries series,
|
||||
Map<String, Map<String, Object>> indicatorParams,
|
||||
Map<String, Map<String, Object>> indicatorVisual,
|
||||
String market, Ta4jStorage storage) {
|
||||
return toRule(node, new RuleBuildContext(series, indicatorParams, indicatorVisual, market, storage));
|
||||
}
|
||||
|
||||
public Rule toRule(JsonNode node, RuleBuildContext ctx) {
|
||||
@@ -178,7 +199,7 @@ public class StrategyDslToTa4jAdapter {
|
||||
String ct = node.path("candleType").asText("1m");
|
||||
BarSeries branchSeries = ctx.resolveSeries(ct);
|
||||
RuleBuildContext branchCtx = new RuleBuildContext(
|
||||
branchSeries, ctx.indicatorParams(), ctx.market(), ctx.storage());
|
||||
branchSeries, ctx.indicatorParams(), ctx.indicatorVisual(), ctx.market(), ctx.storage());
|
||||
List<Rule> rules = childRules(node, branchCtx);
|
||||
if (rules.isEmpty()) return new BooleanRule(false);
|
||||
Rule inner = rules.get(0);
|
||||
@@ -243,22 +264,22 @@ public class StrategyDslToTa4jAdapter {
|
||||
|
||||
private Rule buildAndRule(JsonNode node, BarSeries series,
|
||||
Map<String, Map<String, Object>> p) {
|
||||
return buildAndRule(node, new RuleBuildContext(series, p, null, null));
|
||||
return buildAndRule(node, new RuleBuildContext(series, p, Map.of(), null, null));
|
||||
}
|
||||
|
||||
private Rule buildOrRule(JsonNode node, BarSeries series,
|
||||
Map<String, Map<String, Object>> p) {
|
||||
return buildOrRule(node, new RuleBuildContext(series, p, null, null));
|
||||
return buildOrRule(node, new RuleBuildContext(series, p, Map.of(), null, null));
|
||||
}
|
||||
|
||||
private Rule buildNotRule(JsonNode node, BarSeries series,
|
||||
Map<String, Map<String, Object>> p) {
|
||||
return buildNotRule(node, new RuleBuildContext(series, p, null, null));
|
||||
return buildNotRule(node, new RuleBuildContext(series, p, Map.of(), null, null));
|
||||
}
|
||||
|
||||
private List<Rule> childRules(JsonNode node, BarSeries series,
|
||||
Map<String, Map<String, Object>> p) {
|
||||
return childRules(node, new RuleBuildContext(series, p, null, null));
|
||||
return childRules(node, new RuleBuildContext(series, p, Map.of(), null, null));
|
||||
}
|
||||
|
||||
// ── CONDITION ─────────────────────────────────────────────────────────────
|
||||
@@ -299,8 +320,8 @@ public class StrategyDslToTa4jAdapter {
|
||||
: Map.of();
|
||||
|
||||
try {
|
||||
Indicator<Num> left = resolveField(leftField, indType, indParams, series, condPeriod, leftPeriod);
|
||||
Indicator<Num> right = resolveField(rightField, indType, indParams, series, condPeriod, rightPeriod);
|
||||
Indicator<Num> left = resolveField(leftField, indType, indParams, series, condPeriod, leftPeriod, cond, ctx);
|
||||
Indicator<Num> right = resolveField(rightField, indType, indParams, series, condPeriod, rightPeriod, cond, ctx);
|
||||
|
||||
Rule core = switch (condType) {
|
||||
case "GT" -> new OverIndicatorRule(left, right);
|
||||
@@ -447,8 +468,8 @@ public class StrategyDslToTa4jAdapter {
|
||||
BarSeries rightSeries = ctx.resolveSeries(rightCt);
|
||||
|
||||
try {
|
||||
Indicator<Num> left = resolveField(leftField, indType, indParams, leftSeries, condPeriod, leftPeriod);
|
||||
Indicator<Num> right = resolveField(rightField, indType, indParams, rightSeries, condPeriod, rightPeriod);
|
||||
Indicator<Num> left = resolveField(leftField, indType, indParams, leftSeries, condPeriod, leftPeriod, cond, ctx);
|
||||
Indicator<Num> right = resolveField(rightField, indType, indParams, rightSeries, condPeriod, rightPeriod, cond, ctx);
|
||||
return new CrossTimeframeCompositeRule(condType, left, right, trigger, leftSeries, rightSeries);
|
||||
} catch (Exception e) {
|
||||
log.warn("[Adapter] 복합 교차시간봉 빌드 실패 ind={} cond={}: {}",
|
||||
@@ -515,7 +536,8 @@ public class StrategyDslToTa4jAdapter {
|
||||
|
||||
private Indicator<Num> resolveField(String field, String indType,
|
||||
Map<String, Object> p, BarSeries s,
|
||||
int condPeriod, int sidePeriod) {
|
||||
int condPeriod, int sidePeriod,
|
||||
JsonNode cond, RuleBuildContext ctx) {
|
||||
if (field == null || field.isBlank() || field.equals("NONE")) {
|
||||
return new ConstantIndicator<>(s, s.numFactory().numOf(0));
|
||||
}
|
||||
@@ -526,6 +548,11 @@ public class StrategyDslToTa4jAdapter {
|
||||
case "LOW_PRICE" -> new LowPriceIndicator(s);
|
||||
case "VOLUME_VALUE" -> new VolumeIndicator(s, 1);
|
||||
default -> {
|
||||
Double hlineVal = IndicatorHlineResolver.resolveThresholdField(
|
||||
cond, field, indType, ctx.indicatorVisual());
|
||||
if (hlineVal != null) {
|
||||
yield new ConstantIndicator<>(s, s.numFactory().numOf(hlineVal));
|
||||
}
|
||||
double constant = resolveConstantField(field);
|
||||
if (!Double.isNaN(constant)) {
|
||||
yield new ConstantIndicator<>(s, s.numFactory().numOf(constant));
|
||||
@@ -535,6 +562,14 @@ public class StrategyDslToTa4jAdapter {
|
||||
};
|
||||
}
|
||||
|
||||
/** @deprecated cond/ctx 없이 호출 — hline 역할(HL_OVER) 미해석 */
|
||||
private Indicator<Num> resolveField(String field, String indType,
|
||||
Map<String, Object> p, BarSeries s,
|
||||
int condPeriod, int sidePeriod) {
|
||||
return resolveField(field, indType, p, s, condPeriod, sidePeriod, null,
|
||||
new RuleBuildContext(s, Map.of(), Map.of(), null, null));
|
||||
}
|
||||
|
||||
private double resolveConstantField(String field) {
|
||||
// K_ 접두사: 프론트엔드가 hline 실제값으로 생성한 상수 필드
|
||||
// 예) K_60 → 60, K_-100 → -100, K_0 → 0
|
||||
|
||||
@@ -47,6 +47,7 @@ public class StrategyTriggerBranchEvaluator {
|
||||
String side,
|
||||
String triggerCandleType,
|
||||
Map<String, Map<String, Object>> indicatorParams,
|
||||
Map<String, Map<String, Object>> indicatorVisual,
|
||||
Ta4jStorage storage) {
|
||||
BranchScope scope = parseScope(conditionJson);
|
||||
if (scope.branches().isEmpty()) return new BooleanRule(false);
|
||||
@@ -56,6 +57,7 @@ public class StrategyTriggerBranchEvaluator {
|
||||
new StrategyDslToTa4jAdapter.RuleBuildContext(
|
||||
storage.getOrCreate(market, trigger),
|
||||
indicatorParams,
|
||||
indicatorVisual != null ? indicatorVisual : Map.of(),
|
||||
market,
|
||||
storage);
|
||||
|
||||
@@ -230,7 +232,7 @@ public class StrategyTriggerBranchEvaluator {
|
||||
|
||||
StrategyDslToTa4jAdapter.RuleBuildContext branchCtx =
|
||||
new StrategyDslToTa4jAdapter.RuleBuildContext(
|
||||
branchSeries, ctx.indicatorParams(), ctx.market(), ctx.storage());
|
||||
branchSeries, ctx.indicatorParams(), ctx.indicatorVisual(), ctx.market(), ctx.storage());
|
||||
try {
|
||||
Rule rule = adapter.toRule(subtree, branchCtx);
|
||||
return rule.isSatisfied(branchIndex, null);
|
||||
|
||||
+7
-7
@@ -66,7 +66,7 @@ class StrategyTriggerBranchEvaluatorTest {
|
||||
""";
|
||||
|
||||
Rule rule1m = evaluator.buildTriggerRule(json, MARKET, STRATEGY_ID, "buy",
|
||||
"1m", Map.of(), storage);
|
||||
"1m", Map.of(), Map.of(), storage);
|
||||
BarSeries s1m = storage.getOrCreate(MARKET, "1m");
|
||||
int idx = s1m.getEndIndex();
|
||||
assertDoesNotThrow(() -> rule1m.isSatisfied(idx, null));
|
||||
@@ -98,7 +98,7 @@ class StrategyTriggerBranchEvaluatorTest {
|
||||
""";
|
||||
|
||||
Rule rule1m = evaluator.buildTriggerRule(json, MARKET, STRATEGY_ID, "sell",
|
||||
"1m", Map.of(), storage);
|
||||
"1m", Map.of(), Map.of(), storage);
|
||||
BarSeries s1m = storage.getOrCreate(MARKET, "1m");
|
||||
int idx1m = s1m.getEndIndex();
|
||||
|
||||
@@ -134,7 +134,7 @@ class StrategyTriggerBranchEvaluatorTest {
|
||||
assertEquals("5m", scope.branches().get(2).candleType());
|
||||
|
||||
Rule on1m = evaluator.buildTriggerRule(json, MARKET, STRATEGY_ID, "buy",
|
||||
"1m", Map.of(), storage);
|
||||
"1m", Map.of(), Map.of(), storage);
|
||||
BarSeries s1m = storage.getOrCreate(MARKET, "1m");
|
||||
on1m.isSatisfied(s1m.getEndIndex(), null);
|
||||
assertNotNull(branchCache.get(MARKET, STRATEGY_ID, "buy", "1m"));
|
||||
@@ -144,14 +144,14 @@ class StrategyTriggerBranchEvaluatorTest {
|
||||
|
||||
branchCache.invalidateStrategy(STRATEGY_ID);
|
||||
Rule on3m = evaluator.buildTriggerRule(json, MARKET, STRATEGY_ID, "buy",
|
||||
"3m", Map.of(), storage);
|
||||
"3m", Map.of(), Map.of(), storage);
|
||||
BarSeries s3m = storage.getOrCreate(MARKET, "3m");
|
||||
on3m.isSatisfied(s3m.getEndIndex(), null);
|
||||
assertNotNull(branchCache.get(MARKET, STRATEGY_ID, "buy", "3m"));
|
||||
assertNull(branchCache.get(MARKET, STRATEGY_ID, "buy", "1m"));
|
||||
|
||||
Rule on5mWrongTrigger = evaluator.buildTriggerRule(json, MARKET, STRATEGY_ID, "buy",
|
||||
"1m", Map.of(), storage);
|
||||
"1m", Map.of(), Map.of(), storage);
|
||||
assertFalse(on5mWrongTrigger.isSatisfied(s3m.getEndIndex(), null),
|
||||
"3m 인덱스로 1m 트리거 Rule 평가 시 false");
|
||||
}
|
||||
@@ -177,12 +177,12 @@ class StrategyTriggerBranchEvaluatorTest {
|
||||
assertEquals("3m", scope.branches().get(0).candleType());
|
||||
|
||||
Rule on3m = evaluator.buildTriggerRule(json, MARKET, STRATEGY_ID, "buy",
|
||||
"3m", Map.of(), storage);
|
||||
"3m", Map.of(), Map.of(), storage);
|
||||
BarSeries s3m = storage.getOrCreate(MARKET, "3m");
|
||||
assertDoesNotThrow(() -> on3m.isSatisfied(s3m.getEndIndex(), null));
|
||||
|
||||
Rule on1m = evaluator.buildTriggerRule(json, MARKET, STRATEGY_ID, "buy",
|
||||
"1m", Map.of(), storage);
|
||||
"1m", Map.of(), Map.of(), storage);
|
||||
assertFalse(on1m.isSatisfied(storage.getOrCreate(MARKET, "1m").getEndIndex(), null));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user