다중 시간봉 전략 설정기능 추가
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package com.goldenchart.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.goldenchart.storage.Ta4jStorage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.ta4j.core.BarSeries;
|
||||
@@ -98,56 +99,133 @@ public class StrategyDslToTa4jAdapter {
|
||||
|
||||
// ── Public API ────────────────────────────────────────────────────────────
|
||||
|
||||
public record RuleBuildContext(
|
||||
BarSeries primarySeries,
|
||||
Map<String, Map<String, Object>> indicatorParams,
|
||||
String market,
|
||||
Ta4jStorage storage
|
||||
) {
|
||||
BarSeries resolveSeries(String candleType) {
|
||||
String ct = LiveStrategyTimeframeService.normalize(candleType);
|
||||
if (storage != null && market != null && !market.isBlank()
|
||||
&& storage.exists(market, ct)) {
|
||||
return storage.getOrCreate(market, ct);
|
||||
}
|
||||
return primarySeries;
|
||||
}
|
||||
}
|
||||
|
||||
public Rule toRule(JsonNode node, BarSeries series,
|
||||
Map<String, Map<String, Object>> indicatorParams) {
|
||||
return toRule(node, new RuleBuildContext(series, indicatorParams, 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));
|
||||
}
|
||||
|
||||
public Rule toRule(JsonNode node, RuleBuildContext ctx) {
|
||||
if (node == null || node.isNull()) return new BooleanRule(false);
|
||||
String type = node.path("type").asText("CONDITION");
|
||||
|
||||
return switch (type) {
|
||||
case "AND" -> buildAndRule(node, series, indicatorParams);
|
||||
case "OR" -> buildOrRule(node, series, indicatorParams);
|
||||
case "NOT" -> buildNotRule(node, series, indicatorParams);
|
||||
default -> buildConditionRule(node.path("condition"), series, indicatorParams);
|
||||
case "AND" -> buildAndRule(node, ctx);
|
||||
case "OR" -> buildOrRule(node, ctx);
|
||||
case "NOT" -> buildNotRule(node, ctx);
|
||||
case "TIMEFRAME" -> buildTimeframeRule(node, ctx);
|
||||
default -> buildConditionRule(node.path("condition"), ctx.primarySeries(), ctx.indicatorParams());
|
||||
};
|
||||
}
|
||||
|
||||
private Rule buildTimeframeRule(JsonNode node, RuleBuildContext ctx) {
|
||||
String ct = node.path("candleType").asText("1m");
|
||||
BarSeries branchSeries = ctx.resolveSeries(ct);
|
||||
RuleBuildContext branchCtx = new RuleBuildContext(
|
||||
branchSeries, ctx.indicatorParams(), ctx.market(), ctx.storage());
|
||||
List<Rule> rules = childRules(node, branchCtx);
|
||||
if (rules.isEmpty()) return new BooleanRule(false);
|
||||
Rule inner = rules.get(0);
|
||||
if (branchSeries == ctx.primarySeries()) return inner;
|
||||
return new CrossSeriesRule(inner, branchSeries, ctx.primarySeries());
|
||||
}
|
||||
|
||||
/** 다른 시간봉 시리즈로 빌드된 Rule — 트리거 시리즈 인덱스에서 해당 분봉 최신봉으로 평가 */
|
||||
private static final class CrossSeriesRule implements Rule {
|
||||
private final Rule inner;
|
||||
private final BarSeries branchSeries;
|
||||
private final BarSeries triggerSeries;
|
||||
|
||||
CrossSeriesRule(Rule inner, BarSeries branchSeries, BarSeries triggerSeries) {
|
||||
this.inner = inner;
|
||||
this.branchSeries = branchSeries;
|
||||
this.triggerSeries = triggerSeries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSatisfied(int index, TradingRecord tradingRecord) {
|
||||
int evalIndex = branchSeries == triggerSeries
|
||||
? index
|
||||
: branchSeries.getEndIndex();
|
||||
if (evalIndex < 0 || branchSeries.getBarCount() == 0) return false;
|
||||
return inner.isSatisfied(evalIndex, tradingRecord);
|
||||
}
|
||||
}
|
||||
|
||||
// ── AND / OR / NOT ────────────────────────────────────────────────────────
|
||||
|
||||
private Rule buildAndRule(JsonNode node, BarSeries series,
|
||||
Map<String, Map<String, Object>> p) {
|
||||
List<Rule> rules = childRules(node, series, p);
|
||||
private Rule buildAndRule(JsonNode node, RuleBuildContext ctx) {
|
||||
List<Rule> rules = childRules(node, ctx);
|
||||
if (rules.isEmpty()) return new BooleanRule(false);
|
||||
Rule result = rules.get(0);
|
||||
for (int i = 1; i < rules.size(); i++) result = new AndRule(result, rules.get(i));
|
||||
return result;
|
||||
}
|
||||
|
||||
private Rule buildOrRule(JsonNode node, BarSeries series,
|
||||
Map<String, Map<String, Object>> p) {
|
||||
List<Rule> rules = childRules(node, series, p);
|
||||
private Rule buildOrRule(JsonNode node, RuleBuildContext ctx) {
|
||||
List<Rule> rules = childRules(node, ctx);
|
||||
if (rules.isEmpty()) return new BooleanRule(false);
|
||||
Rule result = rules.get(0);
|
||||
for (int i = 1; i < rules.size(); i++) result = new OrRule(result, rules.get(i));
|
||||
return result;
|
||||
}
|
||||
|
||||
private Rule buildNotRule(JsonNode node, BarSeries series,
|
||||
Map<String, Map<String, Object>> p) {
|
||||
List<Rule> rules = childRules(node, series, p);
|
||||
private Rule buildNotRule(JsonNode node, RuleBuildContext ctx) {
|
||||
List<Rule> rules = childRules(node, ctx);
|
||||
if (rules.isEmpty()) return new BooleanRule(false);
|
||||
return new NotRule(rules.get(0));
|
||||
}
|
||||
|
||||
private List<Rule> childRules(JsonNode node, BarSeries series,
|
||||
Map<String, Map<String, Object>> p) {
|
||||
private List<Rule> childRules(JsonNode node, RuleBuildContext ctx) {
|
||||
List<Rule> list = new ArrayList<>();
|
||||
JsonNode children = node.path("children");
|
||||
if (children.isArray()) {
|
||||
for (JsonNode child : children) list.add(toRule(child, series, p));
|
||||
for (JsonNode child : children) list.add(toRule(child, ctx));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private Rule buildAndRule(JsonNode node, BarSeries series,
|
||||
Map<String, Map<String, Object>> p) {
|
||||
return buildAndRule(node, new RuleBuildContext(series, p, null, null));
|
||||
}
|
||||
|
||||
private Rule buildOrRule(JsonNode node, BarSeries series,
|
||||
Map<String, Map<String, Object>> p) {
|
||||
return buildOrRule(node, new RuleBuildContext(series, p, null, null));
|
||||
}
|
||||
|
||||
private Rule buildNotRule(JsonNode node, BarSeries series,
|
||||
Map<String, Map<String, Object>> p) {
|
||||
return buildNotRule(node, new RuleBuildContext(series, p, 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));
|
||||
}
|
||||
|
||||
// ── CONDITION ─────────────────────────────────────────────────────────────
|
||||
|
||||
private Rule buildConditionRule(JsonNode cond, BarSeries series,
|
||||
|
||||
Reference in New Issue
Block a user