전략평가 화면 수정
This commit is contained in:
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.goldenchart.dto.LiveConditionRowDto;
|
||||
import com.goldenchart.dto.LiveConditionStatusDto;
|
||||
import com.goldenchart.dto.OhlcvBar;
|
||||
import com.goldenchart.entity.GcStrategy;
|
||||
import com.goldenchart.repository.GcStrategyRepository;
|
||||
import com.goldenchart.storage.Ta4jStorage;
|
||||
@@ -68,6 +69,16 @@ public class LiveConditionStatusService {
|
||||
String market, long strategyId,
|
||||
Long barTimeSec,
|
||||
Map<String, Map<String, Object>> indicatorParamsOverride) {
|
||||
return evaluate(userId, deviceId, market, strategyId, barTimeSec, indicatorParamsOverride, null, null);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public LiveConditionStatusDto evaluate(Long userId, String deviceId,
|
||||
String market, long strategyId,
|
||||
Long barTimeSec,
|
||||
Map<String, Map<String, Object>> indicatorParamsOverride,
|
||||
List<OhlcvBar> chartBars,
|
||||
String chartTimeframe) {
|
||||
Optional<GcStrategy> opt = strategyRepo.findById(strategyId);
|
||||
if (opt.isEmpty()) {
|
||||
return empty(market, strategyId);
|
||||
@@ -89,6 +100,11 @@ public class LiveConditionStatusService {
|
||||
return empty(market, strategyId);
|
||||
}
|
||||
|
||||
if (chartBars != null && !chartBars.isEmpty() && chartTimeframe != null && !chartTimeframe.isBlank()) {
|
||||
return evaluateOnChartBars(strategy, market, strategyId, pending, chartBars, chartTimeframe,
|
||||
barTimeSec, params, visual);
|
||||
}
|
||||
|
||||
// 마켓 구독 + warm-up — 과거 봉 평가 시 더 많은 이력 확보
|
||||
int warmupBars = barTimeSec != null ? 500 : 60;
|
||||
Set<String> timeframes = new LinkedHashSet<>();
|
||||
@@ -191,6 +207,137 @@ public class LiveConditionStatusService {
|
||||
return bar.getEndTime().getEpochSecond() - bar.getTimePeriod().getSeconds();
|
||||
}
|
||||
|
||||
/**
|
||||
* 차트·백테스트와 동일 OHLCV 로 조건 평가 — Ta4jStorage 와의 불일치 방지.
|
||||
*/
|
||||
private LiveConditionStatusDto evaluateOnChartBars(
|
||||
GcStrategy strategy,
|
||||
String market,
|
||||
long strategyId,
|
||||
List<PendingCond> pending,
|
||||
List<OhlcvBar> chartBars,
|
||||
String chartTimeframe,
|
||||
Long barTimeSec,
|
||||
Map<String, Map<String, Object>> params,
|
||||
Map<String, Map<String, Object>> visual) {
|
||||
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 empty(market, strategyId);
|
||||
}
|
||||
|
||||
Map<String, BarSeries> seriesOverrides = OhlcvBarSeriesSupport.buildSeriesOverrides(
|
||||
primarySeries, primaryTf, buyDsl, sellDsl);
|
||||
|
||||
List<LiveConditionRowDto> rows = new ArrayList<>();
|
||||
int met = 0;
|
||||
int evaluable = 0;
|
||||
Integer primaryEvalIndex = OhlcvBarSeriesSupport.resolveBarIndex(primarySeries, barTimeSec);
|
||||
|
||||
for (PendingCond p : pending) {
|
||||
String tf = LiveStrategyTimeframeService.normalize(p.timeframe());
|
||||
BarSeries series = resolveChartSeries(tf, primaryTf, primarySeries, seriesOverrides, market);
|
||||
if (series == null || series.isEmpty()) {
|
||||
rows.add(toRowUnevaluated(p, visual));
|
||||
continue;
|
||||
}
|
||||
int index = OhlcvBarSeriesSupport.resolveBarIndex(series, barTimeSec);
|
||||
try {
|
||||
ObjectNode wrapper = objectMapper.createObjectNode();
|
||||
wrapper.put("type", "CONDITION");
|
||||
wrapper.set("condition", p.condition());
|
||||
|
||||
StrategyDslToTa4jAdapter.RuleBuildContext ctx =
|
||||
new StrategyDslToTa4jAdapter.RuleBuildContext(
|
||||
series, params, visual, market, ta4jStorage, false, seriesOverrides);
|
||||
Rule rule = adapter.toRule(wrapper, ctx);
|
||||
boolean satisfied = rule.isSatisfied(index, null);
|
||||
Double current = adapter.readConditionFieldValue(
|
||||
p.condition(), series, params, index, true);
|
||||
Double target = readTargetNumeric(p.condition(), visual);
|
||||
if (target == null) {
|
||||
target = adapter.readConditionFieldValue(
|
||||
p.condition(), series, params, index, false);
|
||||
}
|
||||
rows.add(toRow(p, current, target, satisfied, series, params, index));
|
||||
evaluable++;
|
||||
if (satisfied) met++;
|
||||
} catch (Exception e) {
|
||||
log.debug("[LiveCondition:chart] eval fail {} {}: {}", market, p.id(), e.getMessage());
|
||||
rows.add(toRowUnevaluated(p, visual));
|
||||
}
|
||||
}
|
||||
|
||||
int matchRate = evaluable > 0 ? Math.round(100f * met / evaluable) : 0;
|
||||
Boolean overallEntryMet = evaluateOverallRuleOnChart(
|
||||
buyDsl, primarySeries, seriesOverrides, market, params, visual, barTimeSec);
|
||||
Boolean overallExitMet = evaluateOverallRuleOnChart(
|
||||
sellDsl, primarySeries, seriesOverrides, market, params, visual, barTimeSec);
|
||||
|
||||
return LiveConditionStatusDto.builder()
|
||||
.market(market)
|
||||
.strategyId(strategyId)
|
||||
.timeframe(primaryTf)
|
||||
.matchRate(matchRate)
|
||||
.overallEntryMet(overallEntryMet)
|
||||
.overallExitMet(overallExitMet)
|
||||
.rows(rows)
|
||||
.updatedAt(System.currentTimeMillis())
|
||||
.barTimeSec(barTimeSec)
|
||||
.evalBarIndex(primaryEvalIndex)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.warn("[LiveCondition:chart] evaluate fail market={}: {}", market, e.getMessage());
|
||||
return empty(market, strategyId);
|
||||
}
|
||||
}
|
||||
|
||||
private BarSeries resolveChartSeries(String tf, String primaryTf, BarSeries primarySeries,
|
||||
Map<String, BarSeries> overrides, String market) {
|
||||
String normalized = OhlcvBarSeriesSupport.normalizeTf(tf);
|
||||
if (overrides.containsKey(normalized)) {
|
||||
return overrides.get(normalized);
|
||||
}
|
||||
if (normalized.equals(primaryTf)) {
|
||||
return primarySeries;
|
||||
}
|
||||
if (ta4jStorage.exists(market, normalized)) {
|
||||
return ta4jStorage.getOrCreate(market, normalized);
|
||||
}
|
||||
return primarySeries;
|
||||
}
|
||||
|
||||
private Boolean evaluateOverallRuleOnChart(JsonNode dsl, BarSeries primarySeries,
|
||||
Map<String, BarSeries> seriesOverrides,
|
||||
String market,
|
||||
Map<String, Map<String, Object>> params,
|
||||
Map<String, Map<String, Object>> visual,
|
||||
Long barTimeSec) {
|
||||
if (dsl == null || dsl.isNull()) return null;
|
||||
try {
|
||||
String primaryTf = extractPrimaryTimeframe(dsl);
|
||||
BarSeries series = resolveChartSeries(primaryTf, OhlcvBarSeriesSupport.normalizeTf(primaryTf),
|
||||
primarySeries, seriesOverrides, market);
|
||||
if (series == null || series.isEmpty()) return null;
|
||||
|
||||
StrategyDslToTa4jAdapter.RuleBuildContext ctx =
|
||||
new StrategyDslToTa4jAdapter.RuleBuildContext(
|
||||
series, params, visual, market, ta4jStorage, false, seriesOverrides);
|
||||
Rule rule = adapter.toRule(dsl, ctx);
|
||||
int index = OhlcvBarSeriesSupport.resolveBarIndex(series, barTimeSec);
|
||||
return rule.isSatisfied(index, null);
|
||||
} catch (Exception e) {
|
||||
log.debug("[LiveCondition:chart] overall rule fail market={}: {}", market, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private LiveConditionStatusDto empty(String market, long strategyId) {
|
||||
return LiveConditionStatusDto.builder()
|
||||
.market(market)
|
||||
|
||||
Reference in New Issue
Block a user