CalcFundRankService.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. package com.simuwang.manage.service.competition;
  2. import cn.hutool.core.collection.CollUtil;
  3. import cn.hutool.core.collection.ListUtil;
  4. import cn.hutool.core.date.DateUtil;
  5. import cn.hutool.core.exceptions.ExceptionUtil;
  6. import cn.hutool.core.map.MapUtil;
  7. import cn.hutool.core.util.StrUtil;
  8. import com.simuwang.base.common.conts.DateConst;
  9. import com.simuwang.base.event.CalcFundRankEventDO;
  10. import com.simuwang.base.mapper.*;
  11. import com.simuwang.base.pojo.FundInitAndEndNavDTO;
  12. import com.simuwang.base.pojo.dos.*;
  13. import com.simuwang.base.pojo.dto.FundScoreRankDTO;
  14. import com.smppw.common.pojo.dto.DateValue;
  15. import com.smppw.utils.BigDecimalUtils;
  16. import org.slf4j.Logger;
  17. import org.slf4j.LoggerFactory;
  18. import org.springframework.stereotype.Service;
  19. import java.lang.reflect.Field;
  20. import java.math.BigDecimal;
  21. import java.util.*;
  22. import java.util.stream.Collectors;
  23. @Service
  24. public class CalcFundRankService {
  25. private final static Logger log = LoggerFactory.getLogger(CalcFundRankService.class);
  26. private final FundIndicatorMapper fundIndicatorMapper;
  27. private final RcCompetitionApplyMapper competitionApplyMapper;
  28. private final FundRatingRuleMapper fundRatingRuleMapper;
  29. private final FundInfoMapper fundInfoMapper;
  30. private final RcCompetitionResultMapper rcCompetitionResultMapper;
  31. public CalcFundRankService(FundIndicatorMapper fundIndicatorMapper, RcCompetitionApplyMapper competitionApplyMapper,
  32. FundRatingRuleMapper fundRatingRuleMapper, FundInfoMapper fundInfoMapper,
  33. RcCompetitionResultMapper rcCompetitionResultMapper) {
  34. this.fundIndicatorMapper = fundIndicatorMapper;
  35. this.competitionApplyMapper = competitionApplyMapper;
  36. this.fundRatingRuleMapper = fundRatingRuleMapper;
  37. this.fundInfoMapper = fundInfoMapper;
  38. this.rcCompetitionResultMapper = rcCompetitionResultMapper;
  39. }
  40. public void calFundRank(CalcFundRankEventDO calcFundRankEventDO) {
  41. Integer competitionId = calcFundRankEventDO.getCompetitionId();
  42. String period = calcFundRankEventDO.getPeriod();
  43. List<FundIndicatorDO> fundIndicatorList = fundIndicatorMapper.queryByPeriod(competitionId, period);
  44. if (CollUtil.isEmpty(fundIndicatorList)) {
  45. return;
  46. }
  47. // 基金策略
  48. List<RcCompetitionApplyDO> competitionApplyDOList = competitionApplyMapper.queryByCompetitionId(competitionId);
  49. Map<String, Integer> fundIdStrategyMap = competitionApplyDOList.stream().collect(Collectors.toMap(RcCompetitionApplyDO::getFundId, RcCompetitionApplyDO::getStrategy));
  50. // 排名规则
  51. List<FundRatingRuleDO> ratingRuleDOList = fundRatingRuleMapper.queryByCompetitionId(competitionId);
  52. Map<Integer, List<FundRatingRuleDO>> strategyRatingRuleMap = ratingRuleDOList.stream().collect(Collectors.groupingBy(FundRatingRuleDO::getStrategyId));
  53. // 基金按照策略进行分组
  54. Map<Integer, List<FundIndicatorDO>> strategyIndicatorMap = fundIndicatorList.stream().collect(Collectors.groupingBy(k -> fundIdStrategyMap.get(k.getFundId())));
  55. List<FundScoreRankDTO> fundScoreRankDTOList = CollUtil.newArrayList();
  56. for (Map.Entry<Integer, List<FundIndicatorDO>> strategyIndicatorEntry : strategyIndicatorMap.entrySet()) {
  57. try {
  58. List<FundIndicatorDO> indicatorDOList = strategyIndicatorEntry.getValue();
  59. Integer strategyId = strategyIndicatorEntry.getKey();
  60. List<FundRatingRuleDO> ruleDOList = strategyRatingRuleMap.get(strategyId);
  61. // 计算综合得分和排名
  62. List<FundScoreRankDTO> scoreRankDTOList = calculateScoreAndRank(indicatorDOList, ruleDOList);
  63. scoreRankDTOList.forEach(e -> e.setStrategyId(strategyId));
  64. fundScoreRankDTOList.addAll(scoreRankDTOList);
  65. } catch (Exception e) {
  66. log.error("计算综合得分和排名出错 -> 堆栈信息:{}", ExceptionUtil.stacktraceToString(e));
  67. }
  68. }
  69. if (CollUtil.isEmpty(fundScoreRankDTOList)) {
  70. log.info("基金综合得分和排名为空,无需绘制榜单结果表");
  71. return;
  72. }
  73. // 绘制榜单结果表
  74. try {
  75. saveCompetitionResult(competitionId, period, fundIndicatorList, fundScoreRankDTOList, competitionApplyDOList, calcFundRankEventDO.getFundIdNavMap());
  76. } catch (Exception e) {
  77. log.error("绘制榜单结果表出错 -> 堆栈信息:{}", ExceptionUtil.stacktraceToString(e));
  78. }
  79. }
  80. private void saveCompetitionResult(Integer competitionId, String period, List<FundIndicatorDO> fundIndicatorList, List<FundScoreRankDTO> fundScoreRankDTOList,
  81. List<RcCompetitionApplyDO> competitionApplyDOList, Map<String, List<DateValue>> fundIdNavMap) {
  82. // 基金信息和机构信息
  83. List<FundAndCompanyInfoDO> fundCompanyInfoDOList = fundInfoMapper.queryFundAndTrustByFundId(competitionId);
  84. Map<String, FundAndCompanyInfoDO> fundIdInfoMap = fundCompanyInfoDOList.stream().collect(Collectors.toMap(FundAndCompanyInfoDO::getFundId, v -> v));
  85. // 基金的期初期末净值信息
  86. Map<String, FundInitAndEndNavDTO> fundIdInitAndEndNavMap = getFundInitAndEndNav(fundIdNavMap);
  87. // 榜单数据
  88. Map<String, FundScoreRankDTO> fundIdScoreRankMap = fundScoreRankDTOList.stream().collect(Collectors.toMap(FundScoreRankDTO::getFundId, v -> v));
  89. Map<String, FundIndicatorDO> fundIdIndicatorMap = fundIndicatorList.stream().collect(Collectors.toMap(FundIndicatorDO::getFundId, v -> v));
  90. List<RcCompetitionResultDO> competitionResultDOList = convertToCompetitionResultDO(competitionId, period, fundIdIndicatorMap, fundIdInfoMap, fundIdInitAndEndNavMap, fundIdScoreRankMap);
  91. if (CollUtil.isNotEmpty(competitionResultDOList)) {
  92. List<List<RcCompetitionResultDO>> partition = ListUtil.partition(competitionResultDOList, 500);
  93. partition.forEach(rcCompetitionResultMapper::batchInsertOrUpdate);
  94. }
  95. }
  96. private List<FundScoreRankDTO> calculateScoreAndRank(List<FundIndicatorDO> indicatorDOList, List<FundRatingRuleDO> ruleDOList) {
  97. List<FundScoreRankDTO> scoreRankDTOList = CollUtil.newArrayList();
  98. if (CollUtil.isEmpty(indicatorDOList) || CollUtil.isEmpty(ruleDOList)) {
  99. return scoreRankDTOList;
  100. }
  101. // 1.按照规则计算综合得分
  102. Map<String, BigDecimal> fundIdScoreMap = MapUtil.newHashMap(indicatorDOList.size());
  103. List<Map<String, String>> indicatorMapList = indicatorDOList.stream().map(this::convertToMap).toList();
  104. for (FundRatingRuleDO ratingRuleDo : ruleDOList) {
  105. String indicatorId = ratingRuleDo.getIndicatorId();
  106. BigDecimal weight = ratingRuleDo.getWeight();
  107. Integer ratingType = ratingRuleDo.getRatingType();
  108. // 参与评分的指标总数
  109. int total = (int) indicatorMapList.stream().map(e -> e.get(indicatorId)).filter(StrUtil::isNotBlank).count();
  110. if (total == 0) {
  111. continue;
  112. }
  113. for (Map<String, String> indicatorMap : indicatorMapList) {
  114. String fundId = indicatorMap.get("fundId");
  115. String indicatorValue = indicatorMap.get(indicatorId);
  116. if (StrUtil.isBlank(indicatorValue)) {
  117. continue;
  118. }
  119. // 计算单个指标排名(从小到大 或 从大到小 排名)
  120. int rank;
  121. if (ratingType == 1) {
  122. rank = (int) indicatorMapList.stream().map(e -> e.get(indicatorId))
  123. .filter(StrUtil::isNotBlank).map(Double::valueOf).filter(e -> e.compareTo(Double.valueOf(indicatorValue)) > 0).count() + 1;
  124. } else {
  125. rank = (int) indicatorMapList.stream().map(e -> e.get(indicatorId))
  126. .filter(StrUtil::isNotBlank).map(Double::valueOf).filter(e -> e.compareTo(Double.valueOf(indicatorValue)) < 0).count() + 1;
  127. }
  128. // 排名得分 = [100-100*(绝对排名-1)/(总数-1)] * 权重
  129. double indicatorRank = total == 1 ? 100 : 100 - (100.0 * (rank - 1) / (total - 1));
  130. BigDecimal score = BigDecimalUtils.multiply(BigDecimal.valueOf(indicatorRank), weight);
  131. if (fundIdScoreMap.containsKey(fundId)) {
  132. score = BigDecimalUtils.add(fundIdScoreMap.get(fundId), score);
  133. }
  134. fundIdScoreMap.put(fundId, score);
  135. }
  136. }
  137. // 2.计算排名
  138. List<Map.Entry<String, BigDecimal>> list = new ArrayList<>(fundIdScoreMap.entrySet());
  139. // 按照BigDecimal值降序排序
  140. list.sort((o1, o2) -> o2.getValue().compareTo(o1.getValue()));
  141. int rank = 1;
  142. for (Map.Entry<String, BigDecimal> entry : list) {
  143. FundScoreRankDTO dto = new FundScoreRankDTO(entry.getKey(), null, entry.getValue(), rank++);
  144. scoreRankDTOList.add(dto);
  145. }
  146. return scoreRankDTOList;
  147. }
  148. private Map<String, FundInitAndEndNavDTO> getFundInitAndEndNav(Map<String, List<DateValue>> fundIdNavMap) {
  149. Map<String, FundInitAndEndNavDTO> fundIdInitAndEndNavMap = MapUtil.newHashMap();
  150. for (Map.Entry<String, List<DateValue>> fundIdNavEntry : fundIdNavMap.entrySet()) {
  151. String fundId = fundIdNavEntry.getKey();
  152. List<DateValue> dateValueList = fundIdNavEntry.getValue();
  153. if (CollUtil.isEmpty(dateValueList) || dateValueList.size() == 1) {
  154. continue;
  155. }
  156. FundInitAndEndNavDTO initAndEndNavDTO = new FundInitAndEndNavDTO();
  157. initAndEndNavDTO.setFundId(fundId);
  158. DateValue maxNav = dateValueList.stream().max(Comparator.comparing(DateValue::getDate)).orElse(null);
  159. DateValue minNav = dateValueList.stream().min(Comparator.comparing(DateValue::getDate)).orElse(null);
  160. if (minNav != null) {
  161. initAndEndNavDTO.setStartDate(minNav.getDate());
  162. initAndEndNavDTO.setStartCumulativeNavWithdrawal(minNav.getCumulativeNavWithdrawal());
  163. }
  164. if (maxNav != null) {
  165. initAndEndNavDTO.setEndDate(maxNav.getDate());
  166. initAndEndNavDTO.setEndCumulativeNavWithdrawal(maxNav.getCumulativeNavWithdrawal());
  167. }
  168. fundIdInitAndEndNavMap.put(fundId, initAndEndNavDTO);
  169. }
  170. return fundIdInitAndEndNavMap;
  171. }
  172. private List<RcCompetitionResultDO> convertToCompetitionResultDO(Integer competitionId, String period, Map<String, FundIndicatorDO> fundIdIndicatorMap, Map<String, FundAndCompanyInfoDO> fundIdInfoMap,
  173. Map<String, FundInitAndEndNavDTO> fundIdInitAndEndNavMap, Map<String, FundScoreRankDTO> fundIdScoreRankMap) {
  174. List<RcCompetitionResultDO> competitionResultDOList = CollUtil.newArrayList();
  175. for (Map.Entry<String, FundAndCompanyInfoDO> fundCompanyInfoEntry : fundIdInfoMap.entrySet()) {
  176. String fundId = fundCompanyInfoEntry.getKey();
  177. RcCompetitionResultDO resultDO = new RcCompetitionResultDO();
  178. resultDO.setCompetitionId(competitionId);
  179. resultDO.setPriod(period);
  180. FundAndCompanyInfoDO fundAndCompanyInfoDO = fundCompanyInfoEntry.getValue();
  181. resultDO.setFundId(fundId);
  182. resultDO.setStrategy(fundAndCompanyInfoDO.getStrategyId());
  183. resultDO.setFundRegisterNumber(fundAndCompanyInfoDO.getRegisterNumber());
  184. resultDO.setFundName(fundAndCompanyInfoDO.getFundName());
  185. resultDO.setFundShortName(fundAndCompanyInfoDO.getFundShortName());
  186. resultDO.setCompanyId(fundAndCompanyInfoDO.getCompanyId());
  187. resultDO.setCompanyName(fundAndCompanyInfoDO.getCompanyName());
  188. resultDO.setCompanyShortName(fundAndCompanyInfoDO.getCompanyShortName());
  189. resultDO.setCompanyRegisterNumber(fundAndCompanyInfoDO.getCompanyRegisterNumber());
  190. resultDO.setCompanyScale(fundAndCompanyInfoDO.getCompanyScale());
  191. resultDO.setIsOpenAccount(fundAndCompanyInfoDO.getIsOpenAccount());
  192. resultDO.setIsOpenAccount2(fundAndCompanyInfoDO.getIsOpenAccount2());
  193. FundScoreRankDTO fundScoreRankDTO = fundIdScoreRankMap.get(fundId);
  194. if (fundScoreRankDTO != null) {
  195. resultDO.setRank(fundScoreRankDTO.getRank());
  196. resultDO.setScore(fundScoreRankDTO.getScore());
  197. } else {
  198. resultDO.setRank(999999);
  199. resultDO.setScore(null);
  200. }
  201. FundInitAndEndNavDTO initAndEndNavDTO = fundIdInitAndEndNavMap.get(fundId);
  202. if (initAndEndNavDTO != null) {
  203. resultDO.setPrePriceDate(StrUtil.isNotBlank(initAndEndNavDTO.getStartDate()) ? DateUtil.parse(initAndEndNavDTO.getStartDate(), DateConst.YYYY_MM_DD) : null);
  204. resultDO.setPreCumulativeNavWithdrawal(initAndEndNavDTO.getStartCumulativeNavWithdrawal());
  205. resultDO.setPriceDate(StrUtil.isNotBlank(initAndEndNavDTO.getEndDate()) ? DateUtil.parse(initAndEndNavDTO.getEndDate(), DateConst.YYYY_MM_DD) : null);
  206. resultDO.setCumulativeNavWithdrawal(initAndEndNavDTO.getEndCumulativeNavWithdrawal());
  207. }
  208. FundIndicatorDO fundIndicatorDO = fundIdIndicatorMap.get(fundId);
  209. if (fundIndicatorDO != null) {
  210. resultDO.setRet(fundIndicatorDO.getRet());
  211. resultDO.setRetA(fundIndicatorDO.getRetA());
  212. resultDO.setMaxdrawdown(fundIndicatorDO.getMaxdrawdown());
  213. resultDO.setCalmarratio(fundIndicatorDO.getCalmarratio());
  214. resultDO.setSortinoratio(fundIndicatorDO.getSortinoratio());
  215. resultDO.setSharperatio(fundIndicatorDO.getSharperatio());
  216. resultDO.setAlpha(fundIndicatorDO.getAlpha());
  217. resultDO.setWinrate(fundIndicatorDO.getWinrate());
  218. resultDO.setInfoRatio(fundIndicatorDO.getInfoRatio());
  219. resultDO.setExcessWinrate(fundIndicatorDO.getExcessWinrate());
  220. resultDO.setExcessRet(fundIndicatorDO.getExcessGeometry());
  221. resultDO.setExcessMaxdown(fundIndicatorDO.getExcessMaxdrawdown());
  222. resultDO.setExcessCalmarratio(fundIndicatorDO.getExcessCalmarratio());
  223. resultDO.setExcessSharperatio(fundIndicatorDO.getExcessSharperatio());
  224. resultDO.setExcessStddev(fundIndicatorDO.getExcessStddev());
  225. resultDO.setProductScale(fundIndicatorDO.getProductScale());
  226. } else {
  227. resultDO.setRank(999999);
  228. resultDO.setScore(null);
  229. }
  230. resultDO.setIsvalid(1);
  231. resultDO.setCreateTime(new Date());
  232. resultDO.setCreatorId(999999);
  233. resultDO.setUpdaterId(999999);
  234. resultDO.setUpdateTime(new Date());
  235. competitionResultDOList.add(resultDO);
  236. }
  237. return competitionResultDOList;
  238. }
  239. public Map<String, String> convertToMap(FundIndicatorDO indicatorDO) {
  240. Map<String, String> indicatorMap = MapUtil.newHashMap();
  241. Field[] fields = FundIndicatorDO.class.getDeclaredFields();
  242. for (Field field : fields) {
  243. field.setAccessible(true);
  244. Object value = null;
  245. try {
  246. value = field.get(indicatorDO);
  247. } catch (Exception e) {
  248. indicatorMap.put(field.getName(), null);
  249. }
  250. indicatorMap.put(field.getName(), value != null ? value.toString() : null);
  251. }
  252. return indicatorMap;
  253. }
  254. }