diff --git a/backend/src/main/java/org/dgu/backend/auth/info/GoogleUserInfo.java b/backend/src/main/java/org/dgu/backend/auth/info/GoogleUserInfo.java index ca8fca2..46a13c2 100644 --- a/backend/src/main/java/org/dgu/backend/auth/info/GoogleUserInfo.java +++ b/backend/src/main/java/org/dgu/backend/auth/info/GoogleUserInfo.java @@ -1,7 +1,7 @@ package org.dgu.backend.auth.info; import lombok.AllArgsConstructor; -import org.dgu.backend.common.constant.Provider; +import org.dgu.backend.constant.Provider; import java.util.Map; diff --git a/backend/src/main/java/org/dgu/backend/auth/info/KakaoUserInfo.java b/backend/src/main/java/org/dgu/backend/auth/info/KakaoUserInfo.java index f9a8b07..9d7b11f 100644 --- a/backend/src/main/java/org/dgu/backend/auth/info/KakaoUserInfo.java +++ b/backend/src/main/java/org/dgu/backend/auth/info/KakaoUserInfo.java @@ -1,7 +1,7 @@ package org.dgu.backend.auth.info; import lombok.AllArgsConstructor; -import org.dgu.backend.common.constant.Provider; +import org.dgu.backend.constant.Provider; import java.util.Map; diff --git a/backend/src/main/java/org/dgu/backend/auth/info/NaverUserInfo.java b/backend/src/main/java/org/dgu/backend/auth/info/NaverUserInfo.java index 93e2ffc..12e76e7 100644 --- a/backend/src/main/java/org/dgu/backend/auth/info/NaverUserInfo.java +++ b/backend/src/main/java/org/dgu/backend/auth/info/NaverUserInfo.java @@ -1,7 +1,7 @@ package org.dgu.backend.auth.info; import lombok.AllArgsConstructor; -import org.dgu.backend.common.constant.Provider; +import org.dgu.backend.constant.Provider; import java.util.Map; diff --git a/backend/src/main/java/org/dgu/backend/common/constant/SuccessStatus.java b/backend/src/main/java/org/dgu/backend/common/constant/SuccessStatus.java index 8e2a758..3cac3a9 100644 --- a/backend/src/main/java/org/dgu/backend/common/constant/SuccessStatus.java +++ b/backend/src/main/java/org/dgu/backend/common/constant/SuccessStatus.java @@ -37,7 +37,9 @@ public enum SuccessStatus implements BaseCode { SUCCESS_GET_USER_COINS(HttpStatus.OK, "200", "유저 보유 코인 조회에 성공했습니다"), SUCCESS_GET_REPRESENTATIVE_COINS(HttpStatus.OK, "200", "대표 코인 조회에 성공했습니다"), // Upbit-Key - SUCCESS_ADD_UPBIT_KEYS(HttpStatus.CREATED, "201", "업비트 키 등록에 성공했습니다"); + SUCCESS_ADD_UPBIT_KEYS(HttpStatus.CREATED, "201", "업비트 키 등록에 성공했습니다"), + // Chart + SUCCESS_GET_OHLCV_CHART(HttpStatus.OK, "200", "OHLCV 차트 조회에 성공했습니다."); private final HttpStatus httpStatus; private final String code; diff --git a/backend/src/main/java/org/dgu/backend/common/constant/Coin.java b/backend/src/main/java/org/dgu/backend/constant/Coin.java similarity index 93% rename from backend/src/main/java/org/dgu/backend/common/constant/Coin.java rename to backend/src/main/java/org/dgu/backend/constant/Coin.java index 26600ff..9b27820 100644 --- a/backend/src/main/java/org/dgu/backend/common/constant/Coin.java +++ b/backend/src/main/java/org/dgu/backend/constant/Coin.java @@ -1,4 +1,4 @@ -package org.dgu.backend.common.constant; +package org.dgu.backend.constant; public enum Coin { BITCOIN("KRW-BTC", "비트코인"), diff --git a/backend/src/main/java/org/dgu/backend/common/constant/Provider.java b/backend/src/main/java/org/dgu/backend/constant/Provider.java similarity index 87% rename from backend/src/main/java/org/dgu/backend/common/constant/Provider.java rename to backend/src/main/java/org/dgu/backend/constant/Provider.java index f0f2302..2d1b466 100644 --- a/backend/src/main/java/org/dgu/backend/common/constant/Provider.java +++ b/backend/src/main/java/org/dgu/backend/constant/Provider.java @@ -1,4 +1,4 @@ -package org.dgu.backend.common.constant; +package org.dgu.backend.constant; public enum Provider { GOOGLE_PROVIDER("google"), diff --git a/backend/src/main/java/org/dgu/backend/controller/CandleInfoController.java b/backend/src/main/java/org/dgu/backend/controller/CandleInfoController.java index a23131f..8deb250 100644 --- a/backend/src/main/java/org/dgu/backend/controller/CandleInfoController.java +++ b/backend/src/main/java/org/dgu/backend/controller/CandleInfoController.java @@ -3,7 +3,6 @@ import lombok.RequiredArgsConstructor; import org.dgu.backend.common.ApiResponse; import org.dgu.backend.common.constant.SuccessStatus; -import org.dgu.backend.service.CandleInfoService; import org.dgu.backend.util.CandleDataCollector; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.ResponseEntity; @@ -15,10 +14,9 @@ @RequestMapping("/api/v1") @RequiredArgsConstructor public class CandleInfoController { - private final CandleInfoService candleInfoService; private final CandleDataCollector candleDataCollector; - // 업비트에서 캔들차트를 가져오는 API + /* 업비트에서 캔들차트를 가져오는 API @GetMapping("/candle/info") public void getCandleInfo( @RequestParam("market") String koreanName, @@ -27,16 +25,17 @@ public void getCandleInfo( @RequestParam("candle_type") String candleType) { candleInfoService.getCandleInfo(koreanName, to, count, candleType); - } + } */ // 원하는 가상화폐 & 기간 & 캔들 종류에 따른 데이터를 가져오는 API @GetMapping("/candle/info/all") - public ResponseEntity> collectBitcoinCandleData( - @RequestParam("market") String koreanName, - @RequestParam(value = "to", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime to, - @RequestParam("candle_type") String candleType) { + public ResponseEntity> collectCandleData( + @RequestParam("coin_name") String koreanName, + @RequestParam("start_date") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDate, + @RequestParam("end_date") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate, + @RequestParam("candle_name") String candleName) { - candleDataCollector.collectData(koreanName, to, candleType); + candleDataCollector.collectCandleData(koreanName, startDate, endDate, candleName); return ApiResponse.onSuccess(SuccessStatus.SUCCESS_CANDLE_INFOS); } diff --git a/backend/src/main/java/org/dgu/backend/controller/ChartController.java b/backend/src/main/java/org/dgu/backend/controller/ChartController.java new file mode 100644 index 0000000..a986d17 --- /dev/null +++ b/backend/src/main/java/org/dgu/backend/controller/ChartController.java @@ -0,0 +1,28 @@ +package org.dgu.backend.controller; + +import lombok.RequiredArgsConstructor; +import org.dgu.backend.common.ApiResponse; +import org.dgu.backend.common.constant.SuccessStatus; +import org.dgu.backend.dto.ChartDto; +import org.dgu.backend.service.ChartService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1/charts") +@RequiredArgsConstructor +public class ChartController { + private final ChartService chartService; + + // OHLCV 차트를 조회하는 API + @GetMapping + public ResponseEntity>> getOHLCVCharts( + @RequestParam("coin_name") String koreanName, + @RequestParam("candle_name") String candleName) { + + List ohlcvResponses = chartService.getOHLCVCharts(koreanName, candleName); + return ApiResponse.onSuccess(SuccessStatus.SUCCESS_GET_OHLCV_CHART, ohlcvResponses); + } +} \ No newline at end of file diff --git a/backend/src/main/java/org/dgu/backend/domain/Candle.java b/backend/src/main/java/org/dgu/backend/domain/Candle.java index e70fdac..241836c 100644 --- a/backend/src/main/java/org/dgu/backend/domain/Candle.java +++ b/backend/src/main/java/org/dgu/backend/domain/Candle.java @@ -18,7 +18,7 @@ public class Candle { private Long id; @Column(name = "candles_name") - private String name; + private String candleName; @Column(name = "korean_name") private String koreanName; diff --git a/backend/src/main/java/org/dgu/backend/domain/Market.java b/backend/src/main/java/org/dgu/backend/domain/Market.java index 6ea98f5..915ef20 100644 --- a/backend/src/main/java/org/dgu/backend/domain/Market.java +++ b/backend/src/main/java/org/dgu/backend/domain/Market.java @@ -2,7 +2,6 @@ import jakarta.persistence.*; import lombok.*; -import org.dgu.backend.dto.UpbitDto; import java.util.List; @@ -19,7 +18,7 @@ public class Market { private Long id; @Column(name = "markets_name") - private String name; + private String marketName; @Column(name = "korean_name") private String koreanName; @@ -29,12 +28,4 @@ public class Market { @OneToMany(mappedBy = "market", cascade = CascadeType.ALL, orphanRemoval = true) private List candleInfos; - - public static Market toEntity(UpbitDto.MarketResponse response) { - return Market.builder() - .name(response.getName()) - .koreanName(response.getKoreanName()) - .englishName(response.getEnglishName()) - .build(); - } } diff --git a/backend/src/main/java/org/dgu/backend/dto/ChartDto.java b/backend/src/main/java/org/dgu/backend/dto/ChartDto.java new file mode 100644 index 0000000..1680820 --- /dev/null +++ b/backend/src/main/java/org/dgu/backend/dto/ChartDto.java @@ -0,0 +1,46 @@ +package org.dgu.backend.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import org.dgu.backend.domain.CandleInfo; +import org.dgu.backend.util.BigDecimalSerializer; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +public class ChartDto { + @Builder + @Getter + @AllArgsConstructor + @JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class) + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class OHLCVResponse { + private String date; + @JsonSerialize(using = BigDecimalSerializer.class) + private BigDecimal openingPrice; + @JsonSerialize(using = BigDecimalSerializer.class) + private BigDecimal highPrice; + @JsonSerialize(using = BigDecimalSerializer.class) + private BigDecimal lowPrice; + @JsonSerialize(using = BigDecimalSerializer.class) + private BigDecimal closePrice; + @JsonSerialize(using = BigDecimalSerializer.class) + private BigDecimal volume; + + public static ChartDto.OHLCVResponse of(CandleInfo candleInfo) { + return OHLCVResponse.builder() + .date(String.valueOf(candleInfo.getDateTime())) + .openingPrice(BigDecimal.valueOf(candleInfo.getOpeningPrice())) + .lowPrice(BigDecimal.valueOf(candleInfo.getLowPrice())) + .highPrice(BigDecimal.valueOf(candleInfo.getHighPrice())) + .closePrice(BigDecimal.valueOf(candleInfo.getTradePrice())) + .volume(BigDecimal.valueOf(candleInfo.getAccTradeVolume()).setScale(3, RoundingMode.HALF_UP)) + .build(); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/org/dgu/backend/dto/UpbitDto.java b/backend/src/main/java/org/dgu/backend/dto/UpbitDto.java index 0789944..35b65e3 100644 --- a/backend/src/main/java/org/dgu/backend/dto/UpbitDto.java +++ b/backend/src/main/java/org/dgu/backend/dto/UpbitDto.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonNaming; import lombok.*; +import org.dgu.backend.domain.Market; public class UpbitDto { @Builder @@ -21,6 +22,14 @@ public static class MarketResponse { @JsonProperty("english_name") private String englishName; + + public Market to() { + return Market.builder() + .marketName(name) + .koreanName(koreanName) + .englishName(englishName) + .build(); + } } @Builder diff --git a/backend/src/main/java/org/dgu/backend/exception/BackTestingErrorResult.java b/backend/src/main/java/org/dgu/backend/exception/BackTestingErrorResult.java index 0c769bd..3254c37 100644 --- a/backend/src/main/java/org/dgu/backend/exception/BackTestingErrorResult.java +++ b/backend/src/main/java/org/dgu/backend/exception/BackTestingErrorResult.java @@ -9,7 +9,7 @@ @Getter @RequiredArgsConstructor public enum BackTestingErrorResult implements BaseErrorCode { - NOT_FOUND_START_INDEX(HttpStatus.CONFLICT, "404", "시작 인덱스를 찾을 수 없습니다."); + NOT_FOUND_START_INDEX(HttpStatus.NOT_FOUND, "404", "시작 인덱스를 찾을 수 없습니다."); private final HttpStatus httpStatus; private final String code; diff --git a/backend/src/main/java/org/dgu/backend/exception/ChartErrorResult.java b/backend/src/main/java/org/dgu/backend/exception/ChartErrorResult.java new file mode 100644 index 0000000..f18cfd2 --- /dev/null +++ b/backend/src/main/java/org/dgu/backend/exception/ChartErrorResult.java @@ -0,0 +1,36 @@ +package org.dgu.backend.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.dgu.backend.common.code.BaseErrorCode; +import org.dgu.backend.common.dto.ErrorReasonDto; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum ChartErrorResult implements BaseErrorCode { + NOT_FOUND_CHARTS(HttpStatus.NOT_FOUND, "404", "차트가 존재하지 않습니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public ErrorReasonDto getReason() { + return ErrorReasonDto.builder() + .isSuccess(false) + .code(code) + .message(message) + .build(); + } + + @Override + public ErrorReasonDto getReasonHttpStatus() { + return ErrorReasonDto.builder() + .isSuccess(false) + .httpStatus(httpStatus) + .code(code) + .message(message) + .build(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/org/dgu/backend/exception/ChartException.java b/backend/src/main/java/org/dgu/backend/exception/ChartException.java new file mode 100644 index 0000000..9334d6d --- /dev/null +++ b/backend/src/main/java/org/dgu/backend/exception/ChartException.java @@ -0,0 +1,15 @@ +package org.dgu.backend.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class ChartException extends RuntimeException { + private final ChartErrorResult chartErrorResult; + + @Override + public String getMessage() { + return chartErrorResult.getMessage(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/org/dgu/backend/exception/GlobalExceptionHandler.java b/backend/src/main/java/org/dgu/backend/exception/GlobalExceptionHandler.java index b677f8e..c63f72a 100644 --- a/backend/src/main/java/org/dgu/backend/exception/GlobalExceptionHandler.java +++ b/backend/src/main/java/org/dgu/backend/exception/GlobalExceptionHandler.java @@ -62,4 +62,10 @@ public ResponseEntity> handleEncryptionException(Encr EncryptionErrorResult errorResult = e.getEncryptionErrorResult(); return ApiResponse.onFailure(errorResult); } + // Chart + @ExceptionHandler(ChartException.class) + public ResponseEntity> handleChartException(ChartException e) { + ChartErrorResult errorResult = e.getChartErrorResult(); + return ApiResponse.onFailure(errorResult); + } } \ No newline at end of file diff --git a/backend/src/main/java/org/dgu/backend/exception/UpbitErrorResult.java b/backend/src/main/java/org/dgu/backend/exception/UpbitErrorResult.java index bd9d980..b9674e5 100644 --- a/backend/src/main/java/org/dgu/backend/exception/UpbitErrorResult.java +++ b/backend/src/main/java/org/dgu/backend/exception/UpbitErrorResult.java @@ -10,7 +10,8 @@ @RequiredArgsConstructor public enum UpbitErrorResult implements BaseErrorCode { FAIL_ACCESS_USER_ACCOUNT(HttpStatus.NOT_FOUND, "404", "업비트에서 유저 잔고를 가져오는 데 실패했습니다."), - FAIL_ACCESS_COIN_INFO(HttpStatus.NOT_FOUND, "404", "업비트에서 코인 정보를 가져오는 데 실패했습니다."); + FAIL_ACCESS_COIN_INFO(HttpStatus.NOT_FOUND, "404", "업비트에서 코인 정보를 가져오는 데 실패했습니다."), + FAIL_GET_CANDLE_INFO(HttpStatus.NOT_FOUND, "404", "업비트에서 캔들 정보를 가져오는 데 실패했습니다."); private final HttpStatus httpStatus; private final String code; diff --git a/backend/src/main/java/org/dgu/backend/repository/CandleInfoRepository.java b/backend/src/main/java/org/dgu/backend/repository/CandleInfoRepository.java index b62b33f..bf4c496 100644 --- a/backend/src/main/java/org/dgu/backend/repository/CandleInfoRepository.java +++ b/backend/src/main/java/org/dgu/backend/repository/CandleInfoRepository.java @@ -2,6 +2,7 @@ import org.dgu.backend.domain.Candle; import org.dgu.backend.domain.CandleInfo; +import org.dgu.backend.domain.Market; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -11,4 +12,7 @@ public interface CandleInfoRepository extends JpaRepository { @Query("SELECT c FROM CandleInfo c WHERE c.candle = :candle AND c.dateTime BETWEEN :startDate AND :endDate ORDER BY c.dateTime") List findFilteredCandleInfo(Candle candle, LocalDateTime startDate, LocalDateTime endDate); + + @Query("SELECT c FROM CandleInfo c WHERE c.market = :market AND c.candle = :candle AND c.dateTime > :startDate ORDER BY c.dateTime") + List findByMarketAndCandleAndDateTimeAfter(Market market, Candle candle, LocalDateTime startDate); } \ No newline at end of file diff --git a/backend/src/main/java/org/dgu/backend/repository/CandleRepository.java b/backend/src/main/java/org/dgu/backend/repository/CandleRepository.java index 519621c..62248fb 100644 --- a/backend/src/main/java/org/dgu/backend/repository/CandleRepository.java +++ b/backend/src/main/java/org/dgu/backend/repository/CandleRepository.java @@ -4,5 +4,5 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface CandleRepository extends JpaRepository { - Candle findByName(String candleName); + Candle findByCandleName(String candleName); } \ No newline at end of file diff --git a/backend/src/main/java/org/dgu/backend/service/BackTestingServiceImpl.java b/backend/src/main/java/org/dgu/backend/service/BackTestingServiceImpl.java index 706e7b9..f3f96b3 100644 --- a/backend/src/main/java/org/dgu/backend/service/BackTestingServiceImpl.java +++ b/backend/src/main/java/org/dgu/backend/service/BackTestingServiceImpl.java @@ -32,7 +32,7 @@ public class BackTestingServiceImpl implements BackTestingService { // 백테스팅 결과를 생성하는 메서드 @Override public BackTestingDto.BackTestingResponse createBackTestingResult(String authorizationHeader, BackTestingDto.StepInfo stepInfo) { - Candle candle = candleRepository.findByName(stepInfo.getCandleName()); + Candle candle = candleRepository.findByCandleName(stepInfo.getCandleName()); LocalDateTime startDate = dateUtil.convertToLocalDateTime(stepInfo.getStartDate()); LocalDateTime endDate = dateUtil.convertToLocalDateTime(stepInfo.getEndDate()); diff --git a/backend/src/main/java/org/dgu/backend/service/CandleInfoServiceImpl.java b/backend/src/main/java/org/dgu/backend/service/CandleInfoServiceImpl.java index 9699e9e..489285c 100644 --- a/backend/src/main/java/org/dgu/backend/service/CandleInfoServiceImpl.java +++ b/backend/src/main/java/org/dgu/backend/service/CandleInfoServiceImpl.java @@ -2,11 +2,12 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.dgu.backend.domain.Candle; import org.dgu.backend.domain.CandleInfo; import org.dgu.backend.domain.Market; import org.dgu.backend.dto.UpbitDto; +import org.dgu.backend.exception.UpbitErrorResult; +import org.dgu.backend.exception.UpbitException; import org.dgu.backend.repository.CandleInfoRepository; import org.dgu.backend.repository.CandleRepository; import org.dgu.backend.repository.MarketRepository; @@ -20,7 +21,6 @@ @Service @Transactional @RequiredArgsConstructor -@Slf4j public class CandleInfoServiceImpl implements CandleInfoService { private final CandleInfoRepository candleInfoRepository; @@ -29,19 +29,19 @@ public class CandleInfoServiceImpl implements CandleInfoService { private final RestTemplate restTemplate; @Override - public void getCandleInfo(String marketKoreanName, LocalDateTime to, int count, String candleType) { - Market market = marketRepository.findByKoreanName(marketKoreanName); - Candle candle = candleRepository.findByName(candleType); - String marketName = market.getName(); + public void getCandleInfo(String koreanName, LocalDateTime to, int count, String candleName) { + Market market = marketRepository.findByKoreanName(koreanName); + Candle candle = candleRepository.findByCandleName(candleName); + String marketName = market.getMarketName(); String url; - if (candleType.startsWith("minutes")) { + if (candleName.startsWith("minutes")) { // 분봉인 경우 - int unit = Integer.parseInt(candleType.substring(7)); - url = String.format("https://api.upbit.com/v1/candles/%s/%d?market=%s&count=%d", candleType.substring(0,7), unit, marketName, count); + int unit = Integer.parseInt(candleName.substring(7)); + url = String.format("https://api.upbit.com/v1/candles/%s/%d?market=%s&count=%d", candleName.substring(0,7), unit, marketName, count); } else { // 그 외 (일봉, 주봉, 월봉) - url = String.format("https://api.upbit.com/v1/candles/%s?market=%s&count=%d", candleType, marketName, count); + url = String.format("https://api.upbit.com/v1/candles/%s?market=%s&count=%d", candleName, marketName, count); } if (to != null) { @@ -67,7 +67,7 @@ public void getCandleInfo(String marketKoreanName, LocalDateTime to, int count, candleInfoRepository.save(candleInfo); } } else { - log.error("Failed to receive candle info"); + throw new UpbitException(UpbitErrorResult.FAIL_GET_CANDLE_INFO); } } } \ No newline at end of file diff --git a/backend/src/main/java/org/dgu/backend/service/ChartService.java b/backend/src/main/java/org/dgu/backend/service/ChartService.java new file mode 100644 index 0000000..8346141 --- /dev/null +++ b/backend/src/main/java/org/dgu/backend/service/ChartService.java @@ -0,0 +1,9 @@ +package org.dgu.backend.service; + +import org.dgu.backend.dto.ChartDto; + +import java.util.List; + +public interface ChartService { + List getOHLCVCharts(String koreanName, String candleType); +} \ No newline at end of file diff --git a/backend/src/main/java/org/dgu/backend/service/ChartServiceImpl.java b/backend/src/main/java/org/dgu/backend/service/ChartServiceImpl.java new file mode 100644 index 0000000..d8d9f05 --- /dev/null +++ b/backend/src/main/java/org/dgu/backend/service/ChartServiceImpl.java @@ -0,0 +1,60 @@ +package org.dgu.backend.service; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.dgu.backend.domain.Candle; +import org.dgu.backend.domain.CandleInfo; +import org.dgu.backend.domain.Market; +import org.dgu.backend.dto.ChartDto; +import org.dgu.backend.exception.ChartErrorResult; +import org.dgu.backend.exception.ChartException; +import org.dgu.backend.repository.CandleInfoRepository; +import org.dgu.backend.repository.CandleRepository; +import org.dgu.backend.repository.MarketRepository; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@Transactional +@RequiredArgsConstructor +public class ChartServiceImpl implements ChartService { + private final MarketRepository marketRepository; + private final CandleRepository candleRepository; + private final CandleInfoRepository candleInfoRepository; + + private static final List SEVEN_DAY_CANDLES = Arrays.asList("minutes1", "minutes3", "minutes5", "minutes10", "minutes15", "minutes30"); + private static final List SIX_MONTH_CANDLES = Arrays.asList("minutes60", "minutes240"); + + // OHLCV 차트를 반환하는 메서드 + @Override + public List getOHLCVCharts(String koreanName, String candleName) { + Market market = marketRepository.findByKoreanName(koreanName); + Candle candle = candleRepository.findByCandleName(candleName); + + LocalDateTime startDate = getStartDateByCandleName(candleName); + + List candleInfos = candleInfoRepository.findByMarketAndCandleAndDateTimeAfter(market, candle, startDate); + if (candleInfos.isEmpty()) { + throw new ChartException(ChartErrorResult.NOT_FOUND_CHARTS); + } + return candleInfos.stream() + .map(ChartDto.OHLCVResponse::of) + .collect(Collectors.toList()); + } + + // 캔들 종류에 따라 시작 기간을 계산해 반환하는 메서드 + private LocalDateTime getStartDateByCandleName(String candleName) { + LocalDateTime now = LocalDateTime.now(); + if (SEVEN_DAY_CANDLES.contains(candleName)) { + return now.minusDays(7); + } else if (SIX_MONTH_CANDLES.contains(candleName)) { + return now.minusMonths(6); + } else { + return LocalDateTime.of(2019, 1, 1, 0, 0); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/org/dgu/backend/service/DashBoardServiceImpl.java b/backend/src/main/java/org/dgu/backend/service/DashBoardServiceImpl.java index 11bf17f..ae4bcb5 100644 --- a/backend/src/main/java/org/dgu/backend/service/DashBoardServiceImpl.java +++ b/backend/src/main/java/org/dgu/backend/service/DashBoardServiceImpl.java @@ -3,7 +3,7 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.dgu.backend.common.constant.Coin; +import org.dgu.backend.constant.Coin; import org.dgu.backend.domain.UpbitKey; import org.dgu.backend.domain.User; import org.dgu.backend.domain.UserCoin; diff --git a/backend/src/main/java/org/dgu/backend/service/MarketServiceImpl.java b/backend/src/main/java/org/dgu/backend/service/MarketServiceImpl.java index 9dc455d..cfe24af 100644 --- a/backend/src/main/java/org/dgu/backend/service/MarketServiceImpl.java +++ b/backend/src/main/java/org/dgu/backend/service/MarketServiceImpl.java @@ -3,7 +3,6 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.dgu.backend.domain.Market; import org.dgu.backend.dto.UpbitDto; import org.dgu.backend.repository.MarketRepository; import org.springframework.http.*; @@ -39,8 +38,7 @@ public void getAllMarkets() { for (UpbitDto.MarketResponse marketResponse : responseBody) { // "KRW-"로 시작하는 가상화폐만 저장 if (marketResponse.getName().startsWith("KRW-")) { - Market market = Market.toEntity(marketResponse); - marketRepository.save(market); + marketRepository.save(marketResponse.to()); } } } else { diff --git a/backend/src/main/java/org/dgu/backend/util/CandleDataCollector.java b/backend/src/main/java/org/dgu/backend/util/CandleDataCollector.java index e424924..db953d3 100644 --- a/backend/src/main/java/org/dgu/backend/util/CandleDataCollector.java +++ b/backend/src/main/java/org/dgu/backend/util/CandleDataCollector.java @@ -6,7 +6,6 @@ import java.time.Duration; import java.time.LocalDateTime; -import java.time.Month; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -17,16 +16,15 @@ public class CandleDataCollector { private final CandleInfoService candleInfoService; private final int batchSize = 200; - private final LocalDateTime startTime = LocalDateTime.of(2018, Month.JANUARY, 1, 0, 0); private final RateLimiter rateLimiter = RateLimiter.create(10.0); // 초당 요청 허용량 10개로 제한 private final long retryDelayMillis = 100; // 재시도 대기 시간 (0.1초) - public void collectData(String marketKoreanName, LocalDateTime to, String candleType) { + public void collectCandleData(String koreanName, LocalDateTime startDate, LocalDateTime endDate, String candleName) { // 캔들을 분 기준으로 변환 - int candleInterval = calculateCandleInterval(candleType); + int candleInterval = calculateCandleInterval(candleName); // 시작 시간부터 종료 시간까지의 총 분 수 계산 - long totalMinutes = Duration.between(startTime, to).toMinutes(); + long totalMinutes = Duration.between(startDate, endDate).toMinutes(); // 한 번의 API 요청에서 지나는 시간 long oneAPI = (long) candleInterval * batchSize; @@ -37,12 +35,12 @@ public void collectData(String marketKoreanName, LocalDateTime to, String candle CompletableFuture[] futures = new CompletableFuture[(int) numIterations]; for (int i = 0; i < numIterations; i++) { - LocalDateTime currentStartTime = startTime.plusMinutes((long) i * oneAPI); + LocalDateTime currentStartTime = startDate.plusMinutes((long) i * oneAPI); LocalDateTime currentEndTime = currentStartTime.plusMinutes(oneAPI); - // 종료 시간이 endTime을 넘어가면 endTime으로 설정 - if (currentEndTime.isAfter(to)) { - currentEndTime = to; + // 종료 시간이 endDate을 넘어가면 endDate으로 설정 + if (currentEndTime.isAfter(endDate)) { + currentEndTime = endDate; } final LocalDateTime intervalEnd = currentEndTime; @@ -55,7 +53,7 @@ public void collectData(String marketKoreanName, LocalDateTime to, String candle rateLimiter.acquire(); try { - candleInfoService.getCandleInfo(marketKoreanName, intervalEnd, batchSize, candleType); + candleInfoService.getCandleInfo(koreanName, intervalEnd, batchSize, candleName); requestSuccess = true; // 성공적으로 완료됨 } catch (Exception e) { // 오류 발생 시 재시도