전략 수정

This commit is contained in:
Macbook
2026-06-17 01:33:30 +09:00
parent 7c3f3433e7
commit f56e925509
4 changed files with 227 additions and 29 deletions
@@ -222,10 +222,14 @@ public class LiveConditionStatusService {
Map<String, Map<String, Object>> params,
Map<String, Map<String, Object>> visual) {
try {
JsonNode buyDslRaw = objectMapper.readTree(
strategy.getBuyConditionJson() != null ? strategy.getBuyConditionJson() : "null");
JsonNode sellDslRaw = objectMapper.readTree(
strategy.getSellConditionJson() != null ? strategy.getSellConditionJson() : "null");
JsonNode buyDslRaw = timeframeNormalizer.normalize(
objectMapper.readTree(
strategy.getBuyConditionJson() != null ? strategy.getBuyConditionJson() : "null"),
strategy.getName());
JsonNode sellDslRaw = timeframeNormalizer.normalize(
objectMapper.readTree(
strategy.getSellConditionJson() != null ? strategy.getSellConditionJson() : "null"),
strategy.getName());
String primaryTf = OhlcvBarSeriesSupport.normalizeTf(chartTimeframe);
JsonNode buyDsl = timeframeNormalizer.remapForChartTimeframe(buyDslRaw, primaryTf);
@@ -325,6 +329,16 @@ public class LiveConditionStatusService {
return primarySeries;
}
/** 백테스트·scan-signals 와 동일 — 마지막 evalCount 봉만 스캔 */
private static int resolveChartScanStartIndex(BarSeries series, int evalCount) {
if (series == null || series.isEmpty() || evalCount <= 0) {
return series != null ? series.getBeginIndex() : 0;
}
int total = series.getBarCount();
if (evalCount >= total) return series.getBeginIndex();
return series.getEndIndex() - evalCount + 1;
}
private Boolean evaluateOverallRuleOnChart(JsonNode dsl, BarSeries primarySeries,
Map<String, BarSeries> seriesOverrides,
String market,
@@ -687,10 +701,15 @@ public class LiveConditionStatusService {
indicatorSettingsService.getAllVisual(userId, deviceId);
try {
JsonNode buyDslRaw = objectMapper.readTree(
strategy.getBuyConditionJson() != null ? strategy.getBuyConditionJson() : "null");
JsonNode sellDslRaw = objectMapper.readTree(
strategy.getSellConditionJson() != null ? strategy.getSellConditionJson() : "null");
String strategyName = strategy.getName();
JsonNode buyDslRaw = timeframeNormalizer.normalize(
objectMapper.readTree(
strategy.getBuyConditionJson() != null ? strategy.getBuyConditionJson() : "null"),
strategyName);
JsonNode sellDslRaw = timeframeNormalizer.normalize(
objectMapper.readTree(
strategy.getSellConditionJson() != null ? strategy.getSellConditionJson() : "null"),
strategyName);
String primaryTf = OhlcvBarSeriesSupport.normalizeTf(chartTimeframe);
JsonNode buyDsl = timeframeNormalizer.remapForChartTimeframe(buyDslRaw, primaryTf);
@@ -706,22 +725,27 @@ public class LiveConditionStatusService {
int evalCount = evaluationBarCount != null && evaluationBarCount > 0
? evaluationBarCount
: primarySeries.getBarCount();
int startIdx = Math.max(
primarySeries.getBeginIndex(),
primarySeries.getEndIndex() - evalCount + 1);
int startIdx = resolveChartScanStartIndex(primarySeries, evalCount);
StrategyDslToTa4jAdapter.RuleBuildContext ruleCtx =
new StrategyDslToTa4jAdapter.RuleBuildContext(
primarySeries, params, visual, market, null, false, seriesOverrides);
Rule entryRule = (buyDsl != null && !buyDsl.isNull())
? adapter.toRule(buyDsl, ruleCtx)
: new org.ta4j.core.rules.BooleanRule(false);
Rule exitRule = (sellDsl != null && !sellDsl.isNull())
? adapter.toRule(sellDsl, ruleCtx)
: new org.ta4j.core.rules.BooleanRule(false);
List<BacktestResponse.Signal> signals = new ArrayList<>();
int buyHits = 0;
int sellHits = 0;
for (int i = startIdx; i <= primarySeries.getEndIndex(); i++) {
long barTimeSec = barOpenEpochSec(primarySeries, i);
double close = primarySeries.getBar(i).getClosePrice().doubleValue();
Boolean entryMet = evaluateOverallRuleOnChart(
buyDsl, primarySeries, seriesOverrides, market, params, visual, barTimeSec);
Boolean exitMet = evaluateOverallRuleOnChart(
sellDsl, primarySeries, seriesOverrides, market, params, visual, barTimeSec);
// SIGNAL_ONLY — 조건 충족 봉마다 마커 (포지션 교대 없음, 차트 교차점 표시용)
if (Boolean.TRUE.equals(entryMet)) {
if (entryRule.isSatisfied(i, null)) {
buyHits++;
signals.add(BacktestResponse.Signal.builder()
.time(barTimeSec)
.type("BUY")
@@ -730,7 +754,8 @@ public class LiveConditionStatusService {
.quantity(0)
.build());
}
if (Boolean.TRUE.equals(exitMet)) {
if (exitRule.isSatisfied(i, null)) {
sellHits++;
signals.add(BacktestResponse.Signal.builder()
.time(barTimeSec)
.type("SELL")
@@ -740,6 +765,14 @@ public class LiveConditionStatusService {
.build());
}
}
if (signals.isEmpty()) {
log.info("[LiveCondition:scan] 0 signals market={} strategy={} tf={} bars={} eval={}..{}",
market, strategyId, primaryTf, primarySeries.getBarCount(), startIdx,
primarySeries.getEndIndex());
} else {
log.debug("[LiveCondition:scan] market={} strategy={} tf={} buy={} sell={}",
market, strategyId, primaryTf, buyHits, sellHits);
}
return signals;
} catch (Exception e) {
log.warn("[LiveCondition:scan] fail market={} strategy={}: {}",
@@ -393,6 +393,10 @@ public class StrategyDslToTa4jAdapter {
if (children.isArray()) {
for (JsonNode child : children) list.add(toRule(child, ctx));
}
JsonNode single = node.path("child");
if (list.isEmpty() && !single.isMissingNode() && !single.isNull()) {
list.add(toRule(single, ctx));
}
return list;
}
@@ -810,7 +814,7 @@ public class StrategyDslToTa4jAdapter {
int period = parseTrailingInt(field, "CCI_VALUE_", intP(p, "length", 13));
return new CciOnSourceIndicator(resolvePriceSource(s, CciOnSourceIndicator.normalizeParams(p)), period);
}
if (field.equals("CCI_VALUE") || indType.equals("CCI"))
if (field.equals("CCI_VALUE"))
return new CciOnSourceIndicator(resolvePriceSource(s, CciOnSourceIndicator.normalizeParams(p)),
effectivePeriod(periodOverride, p, "length", 13));
if (field.equals("CCI_SIGNAL")) {
@@ -894,7 +898,7 @@ public class StrategyDslToTa4jAdapter {
int len = parseTrailingInt(field, "TRIX_VALUE_", intP(p, "length", 12));
return buildTrixLine(close, len);
}
if (field.equals("TRIX_VALUE") || indType.equals("TRIX")) {
if (field.equals("TRIX_VALUE")) {
return buildTrixLine(close, effectivePeriod(periodOverride, p, "length", 12));
}
if (field.equals("TRIX_SIGNAL")) {
@@ -1561,14 +1565,16 @@ public class StrategyDslToTa4jAdapter {
int periodOverride, String lengthKey, int defaultLen) {
if (cond != null && !cond.isNull()) {
String leftField = cond.path("leftField").asText("");
if (leftField.startsWith(valuePrefix + "_")) {
int fromField = parseTrailingInt(leftField, valuePrefix + "_", defaultLen);
if (fromField > 0) return fromField;
if (leftField.equals(valuePrefix) || leftField.startsWith(valuePrefix + "_")) {
if (leftField.startsWith(valuePrefix + "_")) {
int fromField = parseTrailingInt(leftField, valuePrefix + "_", defaultLen);
if (fromField > 0) return fromField;
}
int leftPeriod = cond.path("leftPeriod").asInt(-1);
if (leftPeriod > 0) return leftPeriod;
int condPeriod = cond.path("period").asInt(-1);
if (condPeriod > 0) return condPeriod;
}
int leftPeriod = cond.path("leftPeriod").asInt(-1);
if (leftPeriod > 0) return leftPeriod;
int condPeriod = cond.path("period").asInt(-1);
if (condPeriod > 0) return condPeriod;
}
return effectivePeriod(periodOverride, p, lengthKey, defaultLen);
}