package com.simuwang.manage.service.competition; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.exceptions.ExceptionUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import com.simuwang.base.common.conts.DateConst; import com.simuwang.base.event.CalcFundRankEventDO; import com.simuwang.base.mapper.*; import com.simuwang.base.pojo.FundInitAndEndNavDTO; import com.simuwang.base.pojo.dos.*; import com.simuwang.base.pojo.dto.FundScoreRankDTO; import com.smppw.common.pojo.dto.DateValue; import com.smppw.utils.BigDecimalUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.lang.reflect.Field; import java.math.BigDecimal; import java.util.*; import java.util.stream.Collectors; @Service public class CalcFundRankService { private final static Logger log = LoggerFactory.getLogger(CalcFundRankService.class); private final FundIndicatorMapper fundIndicatorMapper; private final RcCompetitionApplyMapper competitionApplyMapper; private final FundRatingRuleMapper fundRatingRuleMapper; private final FundInfoMapper fundInfoMapper; private final RcCompetitionResultMapper rcCompetitionResultMapper; public CalcFundRankService(FundIndicatorMapper fundIndicatorMapper, RcCompetitionApplyMapper competitionApplyMapper, FundRatingRuleMapper fundRatingRuleMapper, FundInfoMapper fundInfoMapper, RcCompetitionResultMapper rcCompetitionResultMapper) { this.fundIndicatorMapper = fundIndicatorMapper; this.competitionApplyMapper = competitionApplyMapper; this.fundRatingRuleMapper = fundRatingRuleMapper; this.fundInfoMapper = fundInfoMapper; this.rcCompetitionResultMapper = rcCompetitionResultMapper; } public void calFundRank(CalcFundRankEventDO calcFundRankEventDO) { Integer competitionId = calcFundRankEventDO.getCompetitionId(); String period = calcFundRankEventDO.getPeriod(); List fundIndicatorList = fundIndicatorMapper.queryByPeriod(competitionId, period); if (CollUtil.isEmpty(fundIndicatorList)) { return; } // 基金策略 List competitionApplyDOList = competitionApplyMapper.queryByCompetitionId(competitionId); Map fundIdStrategyMap = competitionApplyDOList.stream().collect(Collectors.toMap(RcCompetitionApplyDO::getFundId, RcCompetitionApplyDO::getStrategy)); // 排名规则 List ratingRuleDOList = fundRatingRuleMapper.queryByCompetitionId(competitionId); Map> strategyRatingRuleMap = ratingRuleDOList.stream().collect(Collectors.groupingBy(FundRatingRuleDO::getStrategyId)); // 基金按照策略进行分组 Map> strategyIndicatorMap = fundIndicatorList.stream().collect(Collectors.groupingBy(k -> fundIdStrategyMap.get(k.getFundId()))); List fundScoreRankDTOList = CollUtil.newArrayList(); for (Map.Entry> strategyIndicatorEntry : strategyIndicatorMap.entrySet()) { try { List indicatorDOList = strategyIndicatorEntry.getValue(); Integer strategyId = strategyIndicatorEntry.getKey(); List ruleDOList = strategyRatingRuleMap.get(strategyId); // 计算综合得分和排名 List scoreRankDTOList = calculateScoreAndRank(indicatorDOList, ruleDOList); scoreRankDTOList.forEach(e -> e.setStrategyId(strategyId)); fundScoreRankDTOList.addAll(scoreRankDTOList); } catch (Exception e) { log.error("计算综合得分和排名出错 -> 堆栈信息:{}", ExceptionUtil.stacktraceToString(e)); } } if (CollUtil.isEmpty(fundScoreRankDTOList)) { log.info("基金综合得分和排名为空,无需绘制榜单结果表"); return; } // 绘制榜单结果表 try { saveCompetitionResult(competitionId, period, fundIndicatorList, fundScoreRankDTOList, competitionApplyDOList, calcFundRankEventDO.getFundIdNavMap()); } catch (Exception e) { log.error("绘制榜单结果表出错 -> 堆栈信息:{}", ExceptionUtil.stacktraceToString(e)); } } private void saveCompetitionResult(Integer competitionId, String period, List fundIndicatorList, List fundScoreRankDTOList, List competitionApplyDOList, Map> fundIdNavMap) { // 基金信息和机构信息 List fundCompanyInfoDOList = fundInfoMapper.queryFundAndTrustByFundId(competitionId); Map fundIdInfoMap = fundCompanyInfoDOList.stream().collect(Collectors.toMap(FundAndCompanyInfoDO::getFundId, v -> v)); // 基金的期初期末净值信息 Map fundIdInitAndEndNavMap = getFundInitAndEndNav(fundIdNavMap); // 榜单数据 Map fundIdScoreRankMap = fundScoreRankDTOList.stream().collect(Collectors.toMap(FundScoreRankDTO::getFundId, v -> v)); Map fundIdIndicatorMap = fundIndicatorList.stream().collect(Collectors.toMap(FundIndicatorDO::getFundId, v -> v)); List competitionResultDOList = convertToCompetitionResultDO(competitionId, period, fundIdIndicatorMap, fundIdInfoMap, fundIdInitAndEndNavMap, fundIdScoreRankMap); if (CollUtil.isNotEmpty(competitionResultDOList)) { List> partition = ListUtil.partition(competitionResultDOList, 500); partition.forEach(rcCompetitionResultMapper::batchInsertOrUpdate); } } private List calculateScoreAndRank(List indicatorDOList, List ruleDOList) { List scoreRankDTOList = CollUtil.newArrayList(); if (CollUtil.isEmpty(indicatorDOList) || CollUtil.isEmpty(ruleDOList)) { return scoreRankDTOList; } // 1.按照规则计算综合得分 Map fundIdScoreMap = MapUtil.newHashMap(indicatorDOList.size()); List> indicatorMapList = indicatorDOList.stream().map(this::convertToMap).toList(); for (FundRatingRuleDO ratingRuleDo : ruleDOList) { String indicatorId = ratingRuleDo.getIndicatorId(); BigDecimal weight = ratingRuleDo.getWeight(); Integer ratingType = ratingRuleDo.getRatingType(); // 参与评分的指标总数 int total = (int) indicatorMapList.stream().map(e -> e.get(indicatorId)).filter(StrUtil::isNotBlank).count(); if (total == 0) { continue; } for (Map indicatorMap : indicatorMapList) { String fundId = indicatorMap.get("fundId"); String indicatorValue = indicatorMap.get(indicatorId); if (StrUtil.isBlank(indicatorValue)) { continue; } // 计算单个指标排名(从小到大 或 从大到小 排名) int rank; if (ratingType == 1) { rank = (int) indicatorMapList.stream().map(e -> e.get(indicatorId)) .filter(StrUtil::isNotBlank).map(Double::valueOf).filter(e -> e.compareTo(Double.valueOf(indicatorValue)) > 0).count() + 1; } else { rank = (int) indicatorMapList.stream().map(e -> e.get(indicatorId)) .filter(StrUtil::isNotBlank).map(Double::valueOf).filter(e -> e.compareTo(Double.valueOf(indicatorValue)) < 0).count() + 1; } // 排名得分 = [100-100*(绝对排名-1)/(总数-1)] * 权重 double indicatorRank = total == 1 ? 100 : 100 - (100.0 * (rank - 1) / (total - 1)); BigDecimal score = BigDecimalUtils.multiply(BigDecimal.valueOf(indicatorRank), weight); if (fundIdScoreMap.containsKey(fundId)) { score = BigDecimalUtils.add(fundIdScoreMap.get(fundId), score); } fundIdScoreMap.put(fundId, score); } } // 2.计算排名 List> list = new ArrayList<>(fundIdScoreMap.entrySet()); // 按照BigDecimal值降序排序 list.sort((o1, o2) -> o2.getValue().compareTo(o1.getValue())); int rank = 1; for (Map.Entry entry : list) { FundScoreRankDTO dto = new FundScoreRankDTO(entry.getKey(), null, entry.getValue(), rank++); scoreRankDTOList.add(dto); } return scoreRankDTOList; } private Map getFundInitAndEndNav(Map> fundIdNavMap) { Map fundIdInitAndEndNavMap = MapUtil.newHashMap(); for (Map.Entry> fundIdNavEntry : fundIdNavMap.entrySet()) { String fundId = fundIdNavEntry.getKey(); List dateValueList = fundIdNavEntry.getValue(); if (CollUtil.isEmpty(dateValueList) || dateValueList.size() == 1) { continue; } FundInitAndEndNavDTO initAndEndNavDTO = new FundInitAndEndNavDTO(); initAndEndNavDTO.setFundId(fundId); DateValue maxNav = dateValueList.stream().max(Comparator.comparing(DateValue::getDate)).orElse(null); DateValue minNav = dateValueList.stream().min(Comparator.comparing(DateValue::getDate)).orElse(null); if (minNav != null) { initAndEndNavDTO.setStartDate(minNav.getDate()); initAndEndNavDTO.setStartCumulativeNavWithdrawal(minNav.getCumulativeNavWithdrawal()); } if (maxNav != null) { initAndEndNavDTO.setEndDate(maxNav.getDate()); initAndEndNavDTO.setEndCumulativeNavWithdrawal(maxNav.getCumulativeNavWithdrawal()); } fundIdInitAndEndNavMap.put(fundId, initAndEndNavDTO); } return fundIdInitAndEndNavMap; } private List convertToCompetitionResultDO(Integer competitionId, String period, Map fundIdIndicatorMap, Map fundIdInfoMap, Map fundIdInitAndEndNavMap, Map fundIdScoreRankMap) { List competitionResultDOList = CollUtil.newArrayList(); for (Map.Entry fundCompanyInfoEntry : fundIdInfoMap.entrySet()) { String fundId = fundCompanyInfoEntry.getKey(); RcCompetitionResultDO resultDO = new RcCompetitionResultDO(); resultDO.setCompetitionId(competitionId); resultDO.setPriod(period); FundAndCompanyInfoDO fundAndCompanyInfoDO = fundCompanyInfoEntry.getValue(); resultDO.setFundId(fundId); resultDO.setStrategy(fundAndCompanyInfoDO.getStrategyId()); resultDO.setFundRegisterNumber(fundAndCompanyInfoDO.getRegisterNumber()); resultDO.setFundName(fundAndCompanyInfoDO.getFundName()); resultDO.setFundShortName(fundAndCompanyInfoDO.getFundShortName()); resultDO.setCompanyId(fundAndCompanyInfoDO.getCompanyId()); resultDO.setCompanyName(fundAndCompanyInfoDO.getCompanyName()); resultDO.setCompanyShortName(fundAndCompanyInfoDO.getCompanyShortName()); resultDO.setCompanyRegisterNumber(fundAndCompanyInfoDO.getCompanyRegisterNumber()); resultDO.setCompanyScale(fundAndCompanyInfoDO.getCompanyScale()); resultDO.setIsOpenAccount(fundAndCompanyInfoDO.getIsOpenAccount()); resultDO.setIsOpenAccount2(fundAndCompanyInfoDO.getIsOpenAccount2()); FundScoreRankDTO fundScoreRankDTO = fundIdScoreRankMap.get(fundId); if (fundScoreRankDTO != null) { resultDO.setRank(fundScoreRankDTO.getRank()); resultDO.setScore(fundScoreRankDTO.getScore()); } else { resultDO.setRank(999999); resultDO.setScore(null); } FundInitAndEndNavDTO initAndEndNavDTO = fundIdInitAndEndNavMap.get(fundId); if (initAndEndNavDTO != null) { resultDO.setPrePriceDate(StrUtil.isNotBlank(initAndEndNavDTO.getStartDate()) ? DateUtil.parse(initAndEndNavDTO.getStartDate(), DateConst.YYYY_MM_DD) : null); resultDO.setPreCumulativeNavWithdrawal(initAndEndNavDTO.getStartCumulativeNavWithdrawal()); resultDO.setPriceDate(StrUtil.isNotBlank(initAndEndNavDTO.getEndDate()) ? DateUtil.parse(initAndEndNavDTO.getEndDate(), DateConst.YYYY_MM_DD) : null); resultDO.setCumulativeNavWithdrawal(initAndEndNavDTO.getEndCumulativeNavWithdrawal()); } FundIndicatorDO fundIndicatorDO = fundIdIndicatorMap.get(fundId); if (fundIndicatorDO != null) { resultDO.setRet(fundIndicatorDO.getRet()); resultDO.setRetA(fundIndicatorDO.getRetA()); resultDO.setMaxdrawdown(fundIndicatorDO.getMaxdrawdown()); resultDO.setCalmarratio(fundIndicatorDO.getCalmarratio()); resultDO.setSortinoratio(fundIndicatorDO.getSortinoratio()); resultDO.setSharperatio(fundIndicatorDO.getSharperatio()); resultDO.setAlpha(fundIndicatorDO.getAlpha()); resultDO.setWinrate(fundIndicatorDO.getWinrate()); resultDO.setInfoRatio(fundIndicatorDO.getInfoRatio()); resultDO.setExcessWinrate(fundIndicatorDO.getExcessWinrate()); resultDO.setExcessRet(fundIndicatorDO.getExcessGeometry()); resultDO.setExcessMaxdown(fundIndicatorDO.getExcessMaxdrawdown()); resultDO.setExcessCalmarratio(fundIndicatorDO.getExcessCalmarratio()); resultDO.setExcessSharperatio(fundIndicatorDO.getExcessSharperatio()); resultDO.setExcessStddev(fundIndicatorDO.getExcessStddev()); resultDO.setProductScale(fundIndicatorDO.getProductScale()); } else { resultDO.setRank(999999); resultDO.setScore(null); } resultDO.setIsvalid(1); resultDO.setCreateTime(new Date()); resultDO.setCreatorId(999999); resultDO.setUpdaterId(999999); resultDO.setUpdateTime(new Date()); competitionResultDOList.add(resultDO); } return competitionResultDOList; } public Map convertToMap(FundIndicatorDO indicatorDO) { Map indicatorMap = MapUtil.newHashMap(); Field[] fields = FundIndicatorDO.class.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); Object value = null; try { value = field.get(indicatorDO); } catch (Exception e) { indicatorMap.put(field.getName(), null); } indicatorMap.put(field.getName(), value != null ? value.toString() : null); } return indicatorMap; } }