전략평가 메뉴 추가
This commit is contained in:
@@ -53,6 +53,21 @@ public class LiveConditionStatusService {
|
||||
@Transactional(readOnly = true)
|
||||
public LiveConditionStatusDto evaluate(Long userId, String deviceId,
|
||||
String market, long strategyId) {
|
||||
return evaluate(userId, deviceId, market, strategyId, null);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public LiveConditionStatusDto evaluate(Long userId, String deviceId,
|
||||
String market, long strategyId,
|
||||
Long barTimeSec) {
|
||||
return evaluate(userId, deviceId, market, strategyId, barTimeSec, null);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public LiveConditionStatusDto evaluate(Long userId, String deviceId,
|
||||
String market, long strategyId,
|
||||
Long barTimeSec,
|
||||
Map<String, Map<String, Object>> indicatorParamsOverride) {
|
||||
Optional<GcStrategy> opt = strategyRepo.findById(strategyId);
|
||||
if (opt.isEmpty()) {
|
||||
return empty(market, strategyId);
|
||||
@@ -60,7 +75,9 @@ public class LiveConditionStatusService {
|
||||
GcStrategy strategy = opt.get();
|
||||
|
||||
Map<String, Map<String, Object>> params =
|
||||
indicatorSettingsService.getAll(userId, deviceId);
|
||||
mergeIndicatorParams(
|
||||
indicatorSettingsService.getAll(userId, deviceId),
|
||||
indicatorParamsOverride);
|
||||
Map<String, Map<String, Object>> visual =
|
||||
indicatorSettingsService.getAllVisual(userId, deviceId);
|
||||
|
||||
@@ -72,18 +89,20 @@ public class LiveConditionStatusService {
|
||||
return empty(market, strategyId);
|
||||
}
|
||||
|
||||
// 마켓 구독 + 비동기 warm-up 시작 — 현재 봉 유무에 따라 즉시 or 수집 후 평가
|
||||
// 마켓 구독 + warm-up — 과거 봉 평가 시 더 많은 이력 확보
|
||||
int warmupBars = barTimeSec != null ? 500 : 60;
|
||||
Set<String> timeframes = new LinkedHashSet<>();
|
||||
for (PendingCond p : pending) {
|
||||
String tf = LiveStrategyTimeframeService.normalize(p.timeframe());
|
||||
timeframes.add(tf);
|
||||
subscriptionManager.ensureBarsReadySync(market, tf, 60);
|
||||
subscriptionManager.ensureBarsReadySync(market, tf, warmupBars);
|
||||
}
|
||||
subscriptionManager.ensureBarsReadySync(market, "1m", 60);
|
||||
subscriptionManager.ensureBarsReadySync(market, "1m", warmupBars);
|
||||
|
||||
List<LiveConditionRowDto> rows = new ArrayList<>();
|
||||
int met = 0;
|
||||
int evaluable = 0;
|
||||
Integer primaryEvalIndex = null;
|
||||
|
||||
for (PendingCond p : pending) {
|
||||
String tf = LiveStrategyTimeframeService.normalize(p.timeframe());
|
||||
@@ -96,7 +115,8 @@ public class LiveConditionStatusService {
|
||||
rows.add(toRowUnevaluated(p, visual));
|
||||
continue;
|
||||
}
|
||||
int index = series.getEndIndex();
|
||||
int index = resolveBarIndex(series, barTimeSec);
|
||||
if (primaryEvalIndex == null) primaryEvalIndex = index;
|
||||
try {
|
||||
ObjectNode wrapper = objectMapper.createObjectNode();
|
||||
wrapper.put("type", "CONDITION");
|
||||
@@ -128,9 +148,9 @@ public class LiveConditionStatusService {
|
||||
|
||||
// [항목6] 전체 논리 트리 평가 — matchRate 와 달리 AND/OR/NOT 게이트 완전 반영
|
||||
Boolean overallEntryMet = evaluateOverallRule(
|
||||
strategy.getBuyConditionJson(), market, params, visual);
|
||||
strategy.getBuyConditionJson(), market, params, visual, barTimeSec);
|
||||
Boolean overallExitMet = evaluateOverallRule(
|
||||
strategy.getSellConditionJson(), market, params, visual);
|
||||
strategy.getSellConditionJson(), market, params, visual, barTimeSec);
|
||||
|
||||
return LiveConditionStatusDto.builder()
|
||||
.market(market)
|
||||
@@ -141,9 +161,36 @@ public class LiveConditionStatusService {
|
||||
.overallExitMet(overallExitMet)
|
||||
.rows(rows)
|
||||
.updatedAt(System.currentTimeMillis())
|
||||
.barTimeSec(barTimeSec)
|
||||
.evalBarIndex(primaryEvalIndex)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* barTimeSec(초)에 가장 가까운 bar index — null 이면 최신 봉.
|
||||
* 차트·업비트 API 와 동일하게 봉 시작(open) 시각 기준으로 매칭한다.
|
||||
*/
|
||||
private int resolveBarIndex(BarSeries series, Long barTimeSec) {
|
||||
if (barTimeSec == null) return series.getEndIndex();
|
||||
int bestIdx = series.getEndIndex();
|
||||
long bestDist = Long.MAX_VALUE;
|
||||
for (int i = series.getBeginIndex(); i <= series.getEndIndex(); i++) {
|
||||
long openSec = barOpenEpochSec(series, i);
|
||||
long dist = Math.abs(openSec - barTimeSec);
|
||||
if (dist < bestDist) {
|
||||
bestDist = dist;
|
||||
bestIdx = i;
|
||||
}
|
||||
}
|
||||
return bestIdx;
|
||||
}
|
||||
|
||||
/** Ta4j Bar → 차트용 봉 시작 시각(초) — BacktestingService.barStartEpoch 와 동일 */
|
||||
private static long barOpenEpochSec(BarSeries series, int index) {
|
||||
var bar = series.getBar(index);
|
||||
return bar.getEndTime().getEpochSecond() - bar.getTimePeriod().getSeconds();
|
||||
}
|
||||
|
||||
private LiveConditionStatusDto empty(String market, long strategyId) {
|
||||
return LiveConditionStatusDto.builder()
|
||||
.market(market)
|
||||
@@ -169,7 +216,8 @@ public class LiveConditionStatusService {
|
||||
*/
|
||||
private Boolean evaluateOverallRule(String conditionJson, String market,
|
||||
Map<String, Map<String, Object>> params,
|
||||
Map<String, Map<String, Object>> visual) {
|
||||
Map<String, Map<String, Object>> visual,
|
||||
Long barTimeSec) {
|
||||
if (conditionJson == null || conditionJson.isBlank()) return null;
|
||||
try {
|
||||
com.fasterxml.jackson.databind.JsonNode dsl =
|
||||
@@ -191,7 +239,7 @@ public class LiveConditionStatusService {
|
||||
new StrategyDslToTa4jAdapter.RuleBuildContext(
|
||||
series, params, visual, market, ta4jStorage, false, java.util.Map.of());
|
||||
org.ta4j.core.Rule rule = adapter.toRule(dsl, ctx);
|
||||
return rule.isSatisfied(series.getEndIndex(), null);
|
||||
return rule.isSatisfied(resolveBarIndex(series, barTimeSec), null);
|
||||
} catch (Exception e) {
|
||||
log.debug("[LiveCondition] overall rule eval fail market={}: {}", market, e.getMessage());
|
||||
return null;
|
||||
@@ -421,4 +469,24 @@ public class LiveConditionStatusService {
|
||||
return indicatorType;
|
||||
}
|
||||
|
||||
private static Map<String, Map<String, Object>> mergeIndicatorParams(
|
||||
Map<String, Map<String, Object>> base,
|
||||
Map<String, Map<String, Object>> override) {
|
||||
if (override == null || override.isEmpty()) return base;
|
||||
if (base == null || base.isEmpty()) return override;
|
||||
|
||||
Map<String, Map<String, Object>> merged = new LinkedHashMap<>(base);
|
||||
override.forEach((type, overrideParams) -> {
|
||||
Map<String, Object> baseParams = merged.get(type);
|
||||
if (baseParams == null || baseParams.isEmpty()) {
|
||||
merged.put(type, overrideParams);
|
||||
} else {
|
||||
Map<String, Object> inner = new LinkedHashMap<>(baseParams);
|
||||
inner.putAll(overrideParams);
|
||||
merged.put(type, inner);
|
||||
}
|
||||
});
|
||||
return merged;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user