전략평가 오류 수정

This commit is contained in:
Macbook
2026-06-16 00:36:00 +09:00
parent 133e3d0413
commit e93164deda
6 changed files with 191 additions and 27 deletions
@@ -3,6 +3,7 @@ package com.goldenchart.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.goldenchart.dto.BacktestResponse;
import com.goldenchart.dto.LiveConditionRowDto;
import com.goldenchart.dto.LiveConditionStatusDto;
import com.goldenchart.dto.OhlcvBar;
@@ -642,4 +643,92 @@ public class LiveConditionStatusService {
return merged;
}
/**
* 차트 봉 구간을 스캔하여 매수/매도 시그널 목록을 반환한다.
* {@link #evaluateOnChartBars} 의 {@code overallEntryMet}/{@code overallExitMet} 와
* 동일 Rule 엔진을 사용하므로 조건 패널 "충족" 과 차트 마커가 일치한다.
*/
@Transactional(readOnly = true)
public List<BacktestResponse.Signal> scanSignalsOnChartBars(
long userId,
String deviceId,
String market,
long strategyId,
List<OhlcvBar> chartBars,
String chartTimeframe,
Map<String, Map<String, Object>> indicatorParamsOverride,
Integer evaluationBarCount) {
Optional<GcStrategy> opt = strategyRepo.findById(strategyId);
if (opt.isEmpty() || chartBars == null || chartBars.isEmpty()
|| chartTimeframe == null || chartTimeframe.isBlank()) {
return List.of();
}
GcStrategy strategy = opt.get();
Map<String, Map<String, Object>> params =
mergeIndicatorParams(
indicatorSettingsService.getAll(userId, deviceId),
indicatorParamsOverride);
Map<String, Map<String, Object>> visual =
indicatorSettingsService.getAllVisual(userId, deviceId);
try {
JsonNode buyDsl = objectMapper.readTree(
strategy.getBuyConditionJson() != null ? strategy.getBuyConditionJson() : "null");
JsonNode sellDsl = objectMapper.readTree(
strategy.getSellConditionJson() != null ? strategy.getSellConditionJson() : "null");
String primaryTf = OhlcvBarSeriesSupport.normalizeTf(chartTimeframe);
BarSeries primarySeries = OhlcvBarSeriesSupport.buildSeries(chartBars, primaryTf);
if (primarySeries.isEmpty()) {
return List.of();
}
Map<String, BarSeries> seriesOverrides = OhlcvBarSeriesSupport.buildSeriesOverrides(
primarySeries, primaryTf, buyDsl, sellDsl);
int evalCount = evaluationBarCount != null && evaluationBarCount > 0
? evaluationBarCount
: primarySeries.getBarCount();
int startIdx = Math.max(
primarySeries.getBeginIndex(),
primarySeries.getEndIndex() - evalCount + 1);
List<BacktestResponse.Signal> signals = new ArrayList<>();
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);
if (Boolean.TRUE.equals(entryMet)) {
signals.add(BacktestResponse.Signal.builder()
.time(barTimeSec)
.type("BUY")
.price(close)
.barIndex(i)
.quantity(0)
.build());
}
Boolean exitMet = evaluateOverallRuleOnChart(
sellDsl, primarySeries, seriesOverrides, market, params, visual, barTimeSec);
if (Boolean.TRUE.equals(exitMet)) {
signals.add(BacktestResponse.Signal.builder()
.time(barTimeSec)
.type("SELL")
.price(close)
.barIndex(i)
.quantity(0)
.build());
}
}
return signals;
} catch (Exception e) {
log.warn("[LiveCondition:scan] fail market={} strategy={}: {}",
market, strategyId, e.getMessage());
return List.of();
}
}
}