전략편집기 수정
This commit is contained in:
@@ -135,7 +135,7 @@ public class StrategyDslToTa4jAdapter {
|
||||
case "OR" -> buildOrRule(node, ctx);
|
||||
case "NOT" -> buildNotRule(node, ctx);
|
||||
case "TIMEFRAME" -> buildTimeframeRule(node, ctx);
|
||||
default -> buildConditionRule(node.path("condition"), ctx.primarySeries(), ctx.indicatorParams());
|
||||
default -> buildConditionRule(node.path("condition"), ctx);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -261,10 +261,12 @@ public class StrategyDslToTa4jAdapter {
|
||||
|
||||
// ── CONDITION ─────────────────────────────────────────────────────────────
|
||||
|
||||
private Rule buildConditionRule(JsonNode cond, BarSeries series,
|
||||
Map<String, Map<String, Object>> params) {
|
||||
private Rule buildConditionRule(JsonNode cond, RuleBuildContext ctx) {
|
||||
if (cond == null || cond.isNull()) return new BooleanRule(false);
|
||||
|
||||
BarSeries series = ctx.primarySeries();
|
||||
Map<String, Map<String, Object>> params = ctx.indicatorParams();
|
||||
|
||||
String indType = cond.path("indicatorType").asText("");
|
||||
String condType = cond.path("conditionType").asText("GT");
|
||||
String leftField = cond.path("leftField").asText("NONE");
|
||||
@@ -275,6 +277,10 @@ public class StrategyDslToTa4jAdapter {
|
||||
int slopePeriod = cond.path("slopePeriod").asInt(3);
|
||||
int holdDays = cond.path("holdDays").asInt(3);
|
||||
|
||||
if (needsCrossTimeframeComposite(cond, ctx)) {
|
||||
return buildCrossTimeframeCompositeRule(cond, ctx);
|
||||
}
|
||||
|
||||
// DSL 타입(STOCHASTIC 등) → DB 레지스트리 키(Stochastic 등) 변환 후 파라미터 조회
|
||||
String registryKey = toRegistryKey(indType);
|
||||
Map<String, Object> indParams = params != null
|
||||
@@ -336,6 +342,104 @@ public class StrategyDslToTa4jAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
/** 복합지표 — leftCandleType / rightCandleType 이 서로 다를 때 교차 시간봉 평가 */
|
||||
private boolean needsCrossTimeframeComposite(JsonNode cond, RuleBuildContext ctx) {
|
||||
if (!cond.path("composite").asBoolean(false)) return false;
|
||||
if (ctx.storage() == null || ctx.market() == null || ctx.market().isBlank()) return false;
|
||||
String leftCt = LiveStrategyTimeframeService.normalize(
|
||||
cond.path("leftCandleType").asText("1m"));
|
||||
String rightCt = LiveStrategyTimeframeService.normalize(
|
||||
cond.path("rightCandleType").asText("1m"));
|
||||
return !leftCt.equals(rightCt);
|
||||
}
|
||||
|
||||
private Rule buildCrossTimeframeCompositeRule(JsonNode cond, RuleBuildContext ctx) {
|
||||
BarSeries trigger = ctx.primarySeries();
|
||||
String indType = cond.path("indicatorType").asText("");
|
||||
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);
|
||||
|
||||
String registryKey = toRegistryKey(indType);
|
||||
Map<String, Object> indParams = ctx.indicatorParams() != null
|
||||
? ctx.indicatorParams().getOrDefault(registryKey, Map.of())
|
||||
: Map.of();
|
||||
|
||||
String leftCt = LiveStrategyTimeframeService.normalize(
|
||||
cond.path("leftCandleType").asText("1m"));
|
||||
String rightCt = LiveStrategyTimeframeService.normalize(
|
||||
cond.path("rightCandleType").asText("1m"));
|
||||
BarSeries leftSeries = ctx.resolveSeries(leftCt);
|
||||
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);
|
||||
return new CrossTimeframeCompositeRule(condType, left, right, trigger, leftSeries, rightSeries);
|
||||
} catch (Exception e) {
|
||||
log.warn("[Adapter] 복합 교차시간봉 빌드 실패 ind={} cond={}: {}",
|
||||
indType, condType, e.getMessage());
|
||||
return new BooleanRule(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static int evalIndex(BarSeries trigger, BarSeries branch, int triggerIndex) {
|
||||
if (trigger == branch) return triggerIndex;
|
||||
int end = branch.getEndIndex();
|
||||
return end >= 0 ? end : 0;
|
||||
}
|
||||
|
||||
private static final class CrossTimeframeCompositeRule implements Rule {
|
||||
private final String condType;
|
||||
private final Indicator<Num> left;
|
||||
private final Indicator<Num> right;
|
||||
private final BarSeries triggerSeries;
|
||||
private final BarSeries leftSeries;
|
||||
private final BarSeries rightSeries;
|
||||
|
||||
CrossTimeframeCompositeRule(String condType,
|
||||
Indicator<Num> left,
|
||||
Indicator<Num> right,
|
||||
BarSeries triggerSeries,
|
||||
BarSeries leftSeries,
|
||||
BarSeries rightSeries) {
|
||||
this.condType = condType;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.triggerSeries = triggerSeries;
|
||||
this.leftSeries = leftSeries;
|
||||
this.rightSeries = rightSeries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSatisfied(int index, TradingRecord tradingRecord) {
|
||||
int li = evalIndex(triggerSeries, leftSeries, index);
|
||||
int ri = evalIndex(triggerSeries, rightSeries, index);
|
||||
if (li < 1 || ri < 1) return false;
|
||||
|
||||
Num l0 = left.getValue(li - 1);
|
||||
Num l1 = left.getValue(li);
|
||||
Num r0 = right.getValue(ri - 1);
|
||||
Num r1 = right.getValue(ri);
|
||||
if (l0 == null || l1 == null || r0 == null || r1 == null) return false;
|
||||
|
||||
return switch (condType) {
|
||||
case "GT" -> l1.isGreaterThan(r1);
|
||||
case "LT" -> l1.isLessThan(r1);
|
||||
case "GTE" -> l1.isGreaterThan(r1) || l1.isEqual(r1);
|
||||
case "LTE" -> l1.isLessThan(r1) || l1.isEqual(r1);
|
||||
case "EQ" -> l1.isEqual(r1);
|
||||
case "NEQ" -> !l1.isEqual(r1);
|
||||
case "CROSS_UP" -> l0.isLessThanOrEqual(r0) && l1.isGreaterThan(r1);
|
||||
case "CROSS_DOWN" -> l0.isGreaterThanOrEqual(r0) && l1.isLessThan(r1);
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ── 필드 Resolver ─────────────────────────────────────────────────────────
|
||||
|
||||
private Indicator<Num> resolveField(String field, String indType,
|
||||
|
||||
Reference in New Issue
Block a user