전략편집기 기준값 보조지표 설정값 동기화

This commit is contained in:
Macbook
2026-05-28 09:20:06 +09:00
parent 9137864f48
commit e2816b037f
18 changed files with 710 additions and 153 deletions
@@ -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