|
30 | 30 | import java.time.format.DateTimeFormatter; |
31 | 31 | import java.util.ArrayList; |
32 | 32 | import java.util.Comparator; |
| 33 | +import java.util.EnumMap; |
33 | 34 | import java.util.HashMap; |
34 | 35 | import java.util.List; |
35 | 36 | import java.util.Map; |
@@ -820,6 +821,170 @@ public StockDetailResponse getStockDetailInfo(Integer id, COUNTRY country) { |
820 | 821 | .build(); |
821 | 822 | } |
822 | 823 |
|
| 824 | + public List<SectorAverageResponse> getSectorAverageScores(COUNTRY country) { |
| 825 | + if (country == COUNTRY.KOREA) { |
| 826 | + List<Score> scores = scoreRepository.findLatestValidScoresByCountryKorea(); |
| 827 | + return buildDomesticSectorAverages(scores); |
| 828 | + } |
| 829 | + |
| 830 | + List<Score> scores = scoreRepository.findLatestValidScoresByCountryOversea(); |
| 831 | + return buildOverseasSectorAverages(scores); |
| 832 | + } |
| 833 | + |
| 834 | + public SectorPercentileResponse getSectorPercentile(Integer stockId) { |
| 835 | + Stock stock = stockRepository.findStockById(stockId) |
| 836 | + .orElseThrow(() -> new RuntimeException("no stock found")); |
| 837 | + |
| 838 | + COUNTRY country = getCountryFromExchangeNum(stock.getExchangeNum()); |
| 839 | + if (country == COUNTRY.KOREA) { |
| 840 | + DomesticSector sector = stock.getDomesticSector(); |
| 841 | + String sectorName = sector != null ? sector.getName() : DomesticSector.UNKNOWN.getName(); |
| 842 | + String sectorKey = sector != null ? sector.name() : DomesticSector.UNKNOWN.name(); |
| 843 | + |
| 844 | + if (sector == null || sector == DomesticSector.UNKNOWN) { |
| 845 | + return buildEmptySectorPercentile(stockId, sectorKey, sectorName, null); |
| 846 | + } |
| 847 | + |
| 848 | + Optional<Score> targetScoreOpt = scoreRepository |
| 849 | + .findTopByStockIdAndScoreOverseaAndScoreKoreaNotOrderByDateDesc(stockId, 9999, 9999); |
| 850 | + if (targetScoreOpt.isEmpty()) { |
| 851 | + return buildEmptySectorPercentile(stockId, sectorKey, sectorName, null); |
| 852 | + } |
| 853 | + |
| 854 | + int targetScore = targetScoreOpt.get().getScoreKorea(); |
| 855 | + List<Score> sectorScores = scoreRepository.findLatestValidScoresByDomesticSector(sector); |
| 856 | + return buildSectorPercentileResponse(stockId, sectorKey, sectorName, targetScore, sectorScores, true); |
| 857 | + } |
| 858 | + |
| 859 | + OverseasSector sector = stock.getOverseasSector(); |
| 860 | + String sectorName = sector != null ? sector.getName() : OverseasSector.UNKNOWN.getName(); |
| 861 | + String sectorKey = sector != null ? sector.name() : OverseasSector.UNKNOWN.name(); |
| 862 | + |
| 863 | + if (sector == null || sector == OverseasSector.UNKNOWN) { |
| 864 | + return buildEmptySectorPercentile(stockId, sectorKey, sectorName, null); |
| 865 | + } |
| 866 | + |
| 867 | + Optional<Score> targetScoreOpt = scoreRepository |
| 868 | + .findTopByStockIdAndScoreKoreaAndScoreOverseaNotOrderByDateDesc(stockId, 9999, 9999); |
| 869 | + if (targetScoreOpt.isEmpty()) { |
| 870 | + return buildEmptySectorPercentile(stockId, sectorKey, sectorName, null); |
| 871 | + } |
| 872 | + |
| 873 | + int targetScore = targetScoreOpt.get().getScoreOversea(); |
| 874 | + List<Score> sectorScores = scoreRepository.findLatestValidScoresByOverseasSector(sector); |
| 875 | + return buildSectorPercentileResponse(stockId, sectorKey, sectorName, targetScore, sectorScores, false); |
| 876 | + } |
| 877 | + |
| 878 | + private List<SectorAverageResponse> buildDomesticSectorAverages(List<Score> scores) { |
| 879 | + Map<DomesticSector, long[]> stats = new EnumMap<>(DomesticSector.class); |
| 880 | + for (Score score : scores) { |
| 881 | + Stock stock = score.getStock(); |
| 882 | + if (stock == null) { |
| 883 | + continue; |
| 884 | + } |
| 885 | + DomesticSector sector = stock.getDomesticSector(); |
| 886 | + if (sector == null || sector == DomesticSector.UNKNOWN) { |
| 887 | + continue; |
| 888 | + } |
| 889 | + int value = score.getScoreKorea(); |
| 890 | + long[] acc = stats.computeIfAbsent(sector, key -> new long[2]); |
| 891 | + acc[0] += value; |
| 892 | + acc[1] += 1; |
| 893 | + } |
| 894 | + |
| 895 | + return stats.entrySet().stream() |
| 896 | + .map(entry -> buildSectorAverageResponse(entry.getKey().name(), entry.getKey().getName(), entry.getValue())) |
| 897 | + .toList(); |
| 898 | + } |
| 899 | + |
| 900 | + private List<SectorAverageResponse> buildOverseasSectorAverages(List<Score> scores) { |
| 901 | + Map<OverseasSector, long[]> stats = new EnumMap<>(OverseasSector.class); |
| 902 | + for (Score score : scores) { |
| 903 | + Stock stock = score.getStock(); |
| 904 | + if (stock == null) { |
| 905 | + continue; |
| 906 | + } |
| 907 | + OverseasSector sector = stock.getOverseasSector(); |
| 908 | + if (sector == null || sector == OverseasSector.UNKNOWN) { |
| 909 | + continue; |
| 910 | + } |
| 911 | + int value = score.getScoreOversea(); |
| 912 | + long[] acc = stats.computeIfAbsent(sector, key -> new long[2]); |
| 913 | + acc[0] += value; |
| 914 | + acc[1] += 1; |
| 915 | + } |
| 916 | + |
| 917 | + return stats.entrySet().stream() |
| 918 | + .map(entry -> buildSectorAverageResponse(entry.getKey().name(), entry.getKey().getName(), entry.getValue())) |
| 919 | + .toList(); |
| 920 | + } |
| 921 | + |
| 922 | + private SectorAverageResponse buildSectorAverageResponse(String sectorKey, String sectorName, long[] acc) { |
| 923 | + long count = acc[1]; |
| 924 | + int avgScore = count == 0 ? 0 : (int) Math.round(acc[0] / (double) count); |
| 925 | + return SectorAverageResponse.builder() |
| 926 | + .sector(sectorKey) |
| 927 | + .sectorName(sectorName) |
| 928 | + .averageScore(avgScore) |
| 929 | + .count((int) count) |
| 930 | + .build(); |
| 931 | + } |
| 932 | + |
| 933 | + private SectorPercentileResponse buildSectorPercentileResponse( |
| 934 | + Integer stockId, |
| 935 | + String sectorKey, |
| 936 | + String sectorName, |
| 937 | + int targetScore, |
| 938 | + List<Score> sectorScores, |
| 939 | + boolean isKorea |
| 940 | + ) { |
| 941 | + int total = sectorScores.size(); |
| 942 | + if (total == 0) { |
| 943 | + return buildEmptySectorPercentile(stockId, sectorKey, sectorName, targetScore); |
| 944 | + } |
| 945 | + |
| 946 | + long higher = 0; |
| 947 | + long equal = 0; |
| 948 | + for (Score score : sectorScores) { |
| 949 | + int value = isKorea ? score.getScoreKorea() : score.getScoreOversea(); |
| 950 | + if (value > targetScore) { |
| 951 | + higher++; |
| 952 | + } else if (value == targetScore) { |
| 953 | + equal++; |
| 954 | + } |
| 955 | + } |
| 956 | + |
| 957 | + int rank = (int) higher + 1; |
| 958 | + int topPercent = (int) Math.round((higher + equal) * 100.0 / total); |
| 959 | + |
| 960 | + return SectorPercentileResponse.builder() |
| 961 | + .stockId(stockId) |
| 962 | + .sector(sectorKey) |
| 963 | + .sectorName(sectorName) |
| 964 | + .score(targetScore) |
| 965 | + .rank(rank) |
| 966 | + .total(total) |
| 967 | + .topPercent(topPercent) |
| 968 | + .build(); |
| 969 | + } |
| 970 | + |
| 971 | + private SectorPercentileResponse buildEmptySectorPercentile( |
| 972 | + Integer stockId, |
| 973 | + String sectorKey, |
| 974 | + String sectorName, |
| 975 | + Integer score |
| 976 | + ) { |
| 977 | + return SectorPercentileResponse.builder() |
| 978 | + .stockId(stockId) |
| 979 | + .sector(sectorKey) |
| 980 | + .sectorName(sectorName) |
| 981 | + .score(score) |
| 982 | + .rank(0) |
| 983 | + .total(0) |
| 984 | + .topPercent(0) |
| 985 | + .build(); |
| 986 | + } |
| 987 | + |
823 | 988 | private COUNTRY getCountryFromExchangeNum(EXCHANGENUM exchangenum) { |
824 | 989 | return List.of(EXCHANGENUM.KOSPI, EXCHANGENUM.KOSDAQ, EXCHANGENUM.KOREAN_ETF) |
825 | 990 | .contains(exchangenum) ? COUNTRY.KOREA : COUNTRY.OVERSEA; |
|
0 commit comments