package com.goldenchart.service; import com.goldenchart.entity.GcAppSettings; import com.goldenchart.entity.GcLiveStrategySettings; import com.goldenchart.repository.GcLiveStrategySettingsRepository; import com.goldenchart.storage.Ta4jStorage; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Comparator; import java.util.Optional; import java.util.Set; /** * 종목별 전략 평가 분봉 — DB gc_live_strategy_settings.candle_type. */ @Service @RequiredArgsConstructor public class LiveStrategyTimeframeService { private static final Set ALLOWED = Ta4jStorage.CANDLE_TYPE_MAP.keySet(); private final GcLiveStrategySettingsRepository repo; private final AppSettingsService appSettingsService; @Transactional(readOnly = true) public String resolveCandleType(String market, Long userId, String deviceId) { Optional row = find(userId, deviceId, market); if (row.isPresent() && row.get().getCandleType() != null) { return normalize(row.get().getCandleType()); } GcAppSettings app = appSettingsService.getEntity(userId, deviceId); String tf = app.getDefaultTimeframe(); if (tf != null) { String mapped = mapChartTfToCandleType(tf); if (mapped != null) return mapped; } return "1m"; } /** * 워커 스레드 등 user/device 컨텍스트 없을 때 — 해당 마켓 활성 설정의 candle_type. */ @Transactional(readOnly = true) public String resolveCandleTypeForMarket(String market) { return repo.findActiveByMarket(market).stream() .filter(s -> s.getCandleType() != null && !s.getCandleType().isBlank()) .max(Comparator.comparing( GcLiveStrategySettings::getUpdatedAt, Comparator.nullsLast(Comparator.naturalOrder()))) .map(GcLiveStrategySettings::getCandleType) .map(LiveStrategyTimeframeService::normalize) .orElse("1m"); } public static String normalize(String candleType) { if (candleType == null || candleType.isBlank()) return "1m"; String c = candleType.trim().toLowerCase(); if ("1d".equals(c)) return "1d"; if ("1w".equals(c)) return "1w"; if ("1h".equals(c)) return "1h"; if ("4h".equals(c)) return "4h"; if (ALLOWED.contains(c)) return c; return "1m"; } private static String mapChartTfToCandleType(String chartTf) { return switch (chartTf) { case "1m", "3m", "5m", "10m", "15m", "30m" -> chartTf; case "1h", "4h" -> chartTf; case "1D" -> "1d"; case "1W" -> "1w"; case "1M" -> "1d"; default -> null; }; } private Optional find(Long userId, String deviceId, String market) { if (userId != null) return repo.findByUserIdAndMarket(userId, market); if (deviceId != null && !deviceId.isBlank()) return repo.findByDeviceIdAndMarket(deviceId, market); return Optional.empty(); } }