|
1 | 1 | package com.synergyx.trading.service.stockService.ranking; |
2 | 2 |
|
3 | | -import com.synergyx.trading.apiPayload.code.status.ErrorStatus; |
4 | | -import com.synergyx.trading.apiPayload.exception.GeneralException; |
5 | 3 | import com.synergyx.trading.dto.stockDetail.RankedStockDTO; |
6 | 4 | import com.synergyx.trading.model.Stock; |
7 | 5 | import com.synergyx.trading.model.StockDetail; |
|
19 | 17 | import java.util.Collections; |
20 | 18 | import java.util.List; |
21 | 19 | import java.util.Map; |
22 | | -import java.util.Optional; |
23 | 20 | import java.util.concurrent.atomic.AtomicInteger; |
24 | 21 | import java.util.stream.Collectors; |
25 | | -import java.util.stream.IntStream; |
26 | 22 |
|
27 | 23 | import static com.synergyx.trading.util.ParsingUtil.toFormattedNumber; |
28 | 24 | import static com.synergyx.trading.util.ParsingUtil.toFormattedPercentage; |
@@ -100,102 +96,64 @@ public List<RankedStockDTO> getTop20() { |
100 | 96 | } |
101 | 97 |
|
102 | 98 | /** |
103 | | - * AI 예측값의 상승폭을 기준으로 상위 20개 종목을 조회합니다. |
| 99 | + * AI 예측값의 상승률을 기준으로 상위 20개 종목을 조회합니다. |
104 | 100 | * |
105 | | - * @return AI 예측값의 상승폭 기준 TOP 20 종목 목록 |
| 101 | + * @return AI 예측값의 15일간 평균 상승률 기준 TOP 20 종목 목록 |
106 | 102 | */ |
107 | 103 | @Override |
108 | 104 | @Transactional(readOnly = true) |
109 | 105 | public List<RankedStockDTO> getAiTop20() { |
110 | 106 | log.info("[StockRanking] AI 예측 기반 TOP20 랭킹 조회 시작"); |
111 | 107 |
|
112 | | - // todo: 임시 데이터 |
113 | | - List<Long> stockIds = IntStream.rangeClosed(1, 20) |
114 | | - .mapToObj(Long::valueOf) |
| 108 | + // AI 예측 랭킹 상위 20개 (평균 상승률 기준) |
| 109 | + List<StockDetail> topDetails = stockDetailRepository.findTop20ByOrderByAiRankAsc(); |
| 110 | + if (topDetails.isEmpty()) { |
| 111 | + log.warn("[StockRanking] AI 예측 랭킹 데이터가 없습니다."); |
| 112 | + return Collections.emptyList(); |
| 113 | + } |
| 114 | + |
| 115 | + // 필요한 종목 ID 수집 및 매핑 |
| 116 | + List<Long> stockIds = topDetails.stream() |
| 117 | + .map(detail -> detail.getStock().getId()) |
115 | 118 | .toList(); |
116 | 119 |
|
117 | | - // stock 조회 |
118 | 120 | Map<Long, Stock> stockMap = stockRepository.findAllById(stockIds).stream() |
119 | 121 | .collect(Collectors.toMap(Stock::getId, s -> s)); |
120 | 122 |
|
121 | | - List<RankedStockDTO> result = IntStream.rangeClosed(1, 20) |
122 | | - .mapToObj(rank -> { |
123 | | - long stockId = rank; |
124 | | - Stock stock = Optional.ofNullable(stockMap.get(stockId)) |
125 | | - .orElseThrow(() -> new GeneralException(ErrorStatus.STOCK_NOT_FOUND)); |
| 123 | + // Ohlcv 종가 데이터 가져오기 |
| 124 | + LocalDateTime latestTimestamp = stockOhlcvRepository.findLatestTimestamp(); |
| 125 | + List<StockOhlcv> latestOhlcvs = stockOhlcvRepository.findByTimestampAndStockIdIn(latestTimestamp, stockIds); |
| 126 | + Map<Long, StockOhlcv> ohlcvMap = latestOhlcvs.stream() |
| 127 | + .collect(Collectors.toMap(ohlcv -> ohlcv.getStock().getId(), ohlcv -> ohlcv)); |
| 128 | + |
| 129 | + // DTO 변환 |
| 130 | + AtomicInteger rankCounter = new AtomicInteger(1); |
| 131 | + |
| 132 | + List<RankedStockDTO> rankedList = topDetails.stream() |
| 133 | + .map(detail -> { |
| 134 | + Stock stock = stockMap.get(detail.getStock().getId()); |
| 135 | + StockOhlcv ohlcv = ohlcvMap.get(detail.getStock().getId()); |
| 136 | + |
| 137 | + // 현재 종가 |
| 138 | + String formattedPrice = (ohlcv != null) |
| 139 | + ? toFormattedNumber(ohlcv.getClose()) |
| 140 | + : "N/A"; |
| 141 | + |
| 142 | + // 예측 평균 상승률 |
| 143 | + String formattedPredictedIncrease = toFormattedPercentage(detail.getAiAvgIncrease(), 2); |
126 | 144 |
|
127 | 145 | return RankedStockDTO.builder() |
128 | | - .rank(rank) |
129 | | - .stockId(stockId) |
| 146 | + .rank(rankCounter.getAndIncrement()) |
| 147 | + .stockId(stock.getId()) |
130 | 148 | .stockName(stock.getName()) |
131 | | - .price(getMockPrice(rank)) // todo: 임시 예측 종가 |
132 | | - .changeRate(getMockChangeRate(rank)) // todo: 임시 상승률 |
| 149 | + .price(formattedPrice) |
| 150 | + .changeRate(formattedPredictedIncrease) |
133 | 151 | .imageUrl(stock.getImageUrl()) |
134 | 152 | .build(); |
135 | | - }).toList(); |
136 | | - |
137 | | -// log.info("[StockRanking] AI 랭킹 조회 완료 - {}개 반환", result.size()); |
138 | | - return result; |
139 | | - } |
140 | | - |
141 | | - /** |
142 | | - * 임시 목데이터 - 예측 종가 |
143 | | - * todo: delete |
144 | | - */ |
145 | | - private String getMockPrice(int rank) { |
146 | | - return switch (rank) { |
147 | | - case 1 -> "59,100"; |
148 | | - case 2 -> "112,000"; |
149 | | - case 3 -> "74,300"; |
150 | | - case 4 -> "48,900"; |
151 | | - case 5 -> "126,000"; |
152 | | - case 6 -> "191,000"; |
153 | | - case 7 -> "91,400"; |
154 | | - case 8 -> "474,000"; |
155 | | - case 9 -> "513,000"; |
156 | | - case 10 -> "169,000"; |
157 | | - case 11 -> "28,500"; |
158 | | - case 12 -> "41,600"; |
159 | | - case 13 -> "495,000"; |
160 | | - case 14 -> "19,600"; |
161 | | - case 15 -> "144,000"; |
162 | | - case 16 -> "23,900"; |
163 | | - case 17 -> "345,000"; |
164 | | - case 18 -> "151,000"; |
165 | | - case 19 -> "85,200"; |
166 | | - case 20 -> "92,300"; |
167 | | - default -> "0"; |
168 | | - }; |
169 | | - } |
| 153 | + }) |
| 154 | + .toList(); |
170 | 155 |
|
171 | | - /** |
172 | | - * 임시 목데이터 - 예측 상승률 |
173 | | - * todo: delete |
174 | | - */ |
175 | | - private String getMockChangeRate(int rank) { |
176 | | - return switch (rank) { |
177 | | - case 1 -> "2.25%"; |
178 | | - case 2 -> "-0.82%"; |
179 | | - case 3 -> "1.12%"; |
180 | | - case 4 -> "-1.20%"; |
181 | | - case 5 -> "0.95%"; |
182 | | - case 6 -> "3.11%"; |
183 | | - case 7 -> "2.78%"; |
184 | | - case 8 -> "-0.65%"; |
185 | | - case 9 -> "0.85%"; |
186 | | - case 10 -> "1.02%"; |
187 | | - case 11 -> "2.00%"; |
188 | | - case 12 -> "-0.45%"; |
189 | | - case 13 -> "1.57%"; |
190 | | - case 14 -> "-1.90%"; |
191 | | - case 15 -> "0.65%"; |
192 | | - case 16 -> "1.33%"; |
193 | | - case 17 -> "0.48%"; |
194 | | - case 18 -> "-0.95%"; |
195 | | - case 19 -> "0.75%"; |
196 | | - case 20 -> "1.18%"; |
197 | | - default -> "0.00%"; |
198 | | - }; |
| 156 | +// log.info("[StockRanking] AI 예측 기반 TOP20 조회 완료 - {}개 반환", rankedList.size()); |
| 157 | + return rankedList; |
199 | 158 | } |
200 | | - |
201 | 159 | } |
0 commit comments