전략평가 오류 수정
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
package com.goldenchart.controller;
|
||||
|
||||
import com.goldenchart.dto.BacktestResponse;
|
||||
import com.goldenchart.dto.LiveConditionEvaluateRequest;
|
||||
import com.goldenchart.dto.LiveConditionScanSignalsRequest;
|
||||
import com.goldenchart.dto.LiveConditionStatusDto;
|
||||
import com.goldenchart.service.LiveConditionStatusService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -52,4 +55,27 @@ public class LiveConditionController {
|
||||
body.getBars(),
|
||||
body.getTimeframe()));
|
||||
}
|
||||
|
||||
/** 전략 평가 — 차트 마커용 시그널 스캔 (조건 패널과 동일 Rule) */
|
||||
@PostMapping("/scan-signals")
|
||||
public ResponseEntity<List<BacktestResponse.Signal>> scanSignals(
|
||||
@RequestBody LiveConditionScanSignalsRequest body,
|
||||
@RequestHeader Map<String, String> headers) {
|
||||
long uid = TradingControllerSupport.requireRegisteredUser(headers);
|
||||
String deviceId = headers.get("x-device-id");
|
||||
if (body == null || body.getMarket() == null || body.getMarket().isBlank()
|
||||
|| body.getBars() == null || body.getBars().isEmpty()
|
||||
|| body.getTimeframe() == null || body.getTimeframe().isBlank()) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
return ResponseEntity.ok(service.scanSignalsOnChartBars(
|
||||
uid,
|
||||
deviceId,
|
||||
body.getMarket(),
|
||||
body.getStrategyId(),
|
||||
body.getBars(),
|
||||
body.getTimeframe(),
|
||||
body.getIndicatorParams(),
|
||||
body.getEvaluationBarCount()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.goldenchart.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** 전략 평가 — 차트 봉 구간별 매수/매도 시그널 스캔 (live-conditions 와 동일 Rule 엔진) */
|
||||
@Data
|
||||
public class LiveConditionScanSignalsRequest {
|
||||
private String market;
|
||||
private long strategyId;
|
||||
/** indicatorType → param map (DB 저장값 위에 덮어씀) */
|
||||
private Map<String, Map<String, Object>> indicatorParams;
|
||||
/** 차트에 표시된 캔들 */
|
||||
private List<OhlcvBar> bars;
|
||||
/** bars 의 시간봉 (1m, 3m, …) */
|
||||
private String timeframe;
|
||||
/** 평가·표시 구간 봉 수 — null 이면 전체 bars */
|
||||
private Integer evaluationBarCount;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user