package com.smppw.analysis.application.service; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import com.smppw.analysis.application.dto.BaseParams; import com.smppw.analysis.application.dto.IndicatorParams; import com.smppw.analysis.application.dto.TrendParams; import com.smppw.analysis.domain.service.BaseIndicatorServiceV2; import com.smppw.common.exception.APIException; import com.smppw.common.exception.DataException; import com.smppw.common.pojo.IStrategy; import com.smppw.common.pojo.dto.calc.IndicatorCalcIndexDataDto; import com.smppw.common.pojo.dto.calc.IndicatorCalcPropertyDto; import com.smppw.common.pojo.dto.calc.IndicatorCalcSecDataDto; import com.smppw.common.pojo.dto.indicator.CalcMultipleSecMultipleTimeRangeIndicatorReq; import com.smppw.common.pojo.dto.indicator.DateIntervalDto; import com.smppw.common.pojo.enums.*; import com.smppw.constants.Consts; import com.smppw.core.reta.calc.PerformanceConsistency; import com.smppw.utils.StrategyHandleUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.List; import java.util.Map; import java.util.Optional; @Service public class PerformanceService { private final Logger logger = LoggerFactory.getLogger(PerformanceService.class); private final BaseIndicatorServiceV2 baseIndicatorServiceV2; public PerformanceService(BaseIndicatorServiceV2 baseIndicatorServiceV2) { this.baseIndicatorServiceV2 = baseIndicatorServiceV2; } public Map calcIndicators(IndicatorParams params) { return this.calcIndicators(params, ListUtil.toList(Indicator.INDICATOR_TYPE_ARRAY), ListUtil.toList(Indicator.RISK_TABLE_EXCESS_INDICATOR_ARRAY)); } public Map calcIndicators(IndicatorParams params, List indicators, List geoIndicators) { try { // 1.参数处理 this.checkParams(params); List refIds = params.getFundId(); List secIds = this.getSecIdsByParams(params); // 差集求指数id集合,包括基� List indexIds = CollectionUtil.subtractToList(secIds, refIds); String riskOfFreeId = params.getRiskOfFreeId(); Double riskOfFreeValue = params.getRiskOfFreeValue(); if (StrUtil.isBlank(riskOfFreeId)) { riskOfFreeId = Consts.RISK_OF_FREE; } else if (NumberUtil.isNumber(riskOfFreeId)) { riskOfFreeValue = Double.parseDouble(riskOfFreeId); riskOfFreeId = null; } CalcMultipleSecMultipleTimeRangeIndicatorReq req = this.buildCalcReq(params, indicators, geoIndicators, DateIntervalType.CustomInterval, null); req.setRiskOfFreeId(riskOfFreeId); req.setRiskOfFreeValue(riskOfFreeValue); // 2.指标计算 Map> indicator = this.baseIndicatorServiceV2.calcMultipleSecMultipleTimeRangeIndicator(req); // 3.返回结果处理 Map valuesMap = MapUtil.newHashMap(true); Map indexValuesMap = MapUtil.newHashMap(true); for (String refId : refIds) { IndicatorCalcPropertyDto dto = Optional.ofNullable(indicator.get(refId)).filter(e -> !e.isEmpty()).map(e -> e.get(0)).orElse(null); // 标的指标 Map indicatorValues = Optional.ofNullable(dto).map(IndicatorCalcPropertyDto::getSecData).map(IndicatorCalcSecDataDto::getIndicatorValueMap).orElse(MapUtil.empty()); // 标的几何指标 Map geoIndicatorValues = Optional.ofNullable(dto).map(IndicatorCalcPropertyDto::getSecData).map(IndicatorCalcSecDataDto::getExtraGeoIndicatorValueMap).orElse(MapUtil.empty()); // 指数指标,包括基� Map> indexIndicatorValuesMap = Optional.ofNullable(dto).map(IndicatorCalcPropertyDto::getIndexData).map(IndicatorCalcIndexDataDto::getIndexIndicatorValueMap).orElse(MapUtil.empty()); // 指数几何指标 Map> indexGeoIndicatorValuesMap = Optional.ofNullable(dto).map(IndicatorCalcPropertyDto::getIndexData).map(IndicatorCalcIndexDataDto::getIndexGeoExtraIndicatorValueMap).orElse(MapUtil.empty()); // 标的和指数分别处理最大回撤指标,并合并指标结果到一个map� Map values = this.handleMaxDrawdown(indicatorValues, refId, null, false); Map geoValues = this.handleMaxDrawdown(geoIndicatorValues, refId, null, true); Map unionValues = MapUtil.builder().putAll(values).putAll(geoValues).build(); valuesMap.put(refId, unionValues); for (String indexId : indexIds) { if (indexValuesMap.containsKey(indexId)) { continue; } Map indexValues = this.handleMaxDrawdown(indexIndicatorValuesMap.get(indexId), indexId, null, false); Map indexGeoValues = this.handleMaxDrawdown(indexGeoIndicatorValuesMap.get(indexId), indexId, params.getBenchmarkId(), true); Map unionIndexValues = MapUtil.builder().putAll(indexValues).putAll(indexGeoValues).build(); indexValuesMap.put(indexId, unionIndexValues); } } // 返回结果对象构建 return MapUtil.builder(valuesMap).putAll(indexValuesMap).build(); } catch (Exception e) { e.printStackTrace(); logger.error(String.format("基金收益风险指标计算错误:%s", e.getMessage())); } return MapUtil.newHashMap(); } public Map trend(TrendParams params) { List trendTypes = ListUtil.toList(TrendType.Ret, TrendType.Nav, TrendType.ExtraNav, TrendType.OrigNav, TrendType.ExtraRetAri, TrendType.NetValueChange, TrendType.ExtraRetGeo, TrendType.DrawdownTrend); List indexTrendTypes = ListUtil.toList(TrendType.Ret, TrendType.OrigNav); return this.handleTrends(params, trendTypes, indexTrendTypes); } public Map handleTrends(TrendParams params, List trendTypes, List indexTrendTypes) { // 1、参数处理 this.checkParams(params); List refIds = params.getFundId(); List secIds = this.getSecIdsByParams(params); List indexIds = CollectionUtil.subtractToList(secIds, refIds); // 时段参数构建 DateIntervalDto dateInterval = this.buildDateIntervalByParams(params, DateIntervalType.CustomInterval, null); Map benchmarkIdMap = MapUtil.newHashMap(true); for (String refId : refIds) { if (StrUtil.isBlank(params.getBenchmarkId())) { continue; } benchmarkIdMap.put(refId, params.getBenchmarkId()); } // 计算走势图 Map> trendMap = this.baseIndicatorServiceV2.getMultipleSecTrend(refIds, benchmarkIdMap, indexIds, dateInterval, params.getFrequency(), null, null, params.getRaiseType(), this.getOriginStrategy(params), Visibility.Both, params.getNavType(), trendTypes); // 结果处理,支持多标的 // 处理走势图 Map>> dataListMap = MapUtil.newHashMap(true); Map>> indexDataListMap = MapUtil.newHashMap(true); Map> trendListMap = MapUtil.newHashMap(true); Map> indexTrendListMap = MapUtil.newHashMap(true); for (String refId : refIds) { IndicatorCalcPropertyDto dto = Optional.ofNullable(trendMap.get(refId)).filter(e -> !e.isEmpty()).map(e -> e.get(0)).orElse(null); List tempDateList = Optional.ofNullable(dto).map(IndicatorCalcPropertyDto::getDateList).orElse(ListUtil.empty()); // 基金走势序列 Map> tempTrendTypeListMap = Optional.ofNullable(dto).map(IndicatorCalcPropertyDto::getSecData).map(IndicatorCalcSecDataDto::getTrendValueMap).orElse(MapUtil.empty()); trendListMap.put(refId, MapUtil.builder().put("dates", tempDateList).put("trend", tempTrendTypeListMap).build()); dataListMap.put(refId, this.buildDateValue(tempDateList, tempTrendTypeListMap, trendTypes)); // 指数净值和收益序列 Map>> tempIndexTrendTypeListMap = Optional.ofNullable(dto).map(IndicatorCalcPropertyDto::getIndexData).map(IndicatorCalcIndexDataDto::getIndexTrendValueMap).orElse(MapUtil.empty()); for (String indexId : indexIds) { if (indexDataListMap.containsKey(indexId) && !indexId.equals(params.getBenchmarkId())) { continue; } indexTrendListMap.put(indexId, MapUtil.builder().put("dates", tempDateList).put("trend", tempIndexTrendTypeListMap.get(indexId)).build()); indexDataListMap.put(indexId, this.buildDateValue(tempDateList, tempIndexTrendTypeListMap.get(indexId), indexTrendTypes)); } } // Map>> valuesMap = this.multiSecMergeDateValue(refIds, trendMap, dataListMap, trendTypes, multiSec); Map>> dataset = MapUtil.>>builder().putAll(dataListMap).build(); // Map productNameMapping = this.secId2NameService.query(secIds); // Map extInfos = MapUtil.builder().put("endDate", params.getEndDate()).put("startDate", params.getStartDate()).build(); // if (masterId != null || !multiSec) { // // 求基金和基准的年化收益率 // String fundId = refIds.get(0); // Map map = trendListMap.get(fundId); // Map indexMap = Optional.ofNullable(indexTrendListMap.get(params.getBenchmarkId())).orElse(MapUtil.newHashMap()); // List dateList = MapUtil.get(map, "dates", List.class); // Map> trendTypeListMap = MapUtil.get(map, "trend", Map.class); // Map> indexTrendTypeListMap = Optional.ofNullable(MapUtil.get(indexMap, "trend", Map.class)).orElse(MapUtil.newHashMap()); // List dateValues = this.buildDateValue(dateList, trendTypeListMap.get(TrendType.Nav)); // List indexDateValues = this.buildDateValue(dateList, indexTrendTypeListMap.get(TrendType.OrigNav)); // Double annual = IndicatorFactory.getInstance().get(Indicator.AnnualReturn).calc(dateValues); // Double annualBenchmark = IndicatorFactory.getInstance().get(Indicator.AnnualReturn).calc(indexDateValues); // // 求最大回撤和超额最大回撤,业绩走势图不返回 // Double maxDown = this.handleMaxAndMin(trendTypeListMap.get(TrendType.DrawdownTrend), (o1, o2) -> o1 > o2 ? o2 : o1); // Double maxExtraDown = this.handleMaxAndMin(trendTypeListMap.get(TrendType.ExtraDrawdownTrend), (o1, o2) -> o1 > o2 ? o2 : o1); // Map extInfos1 = MapUtil.builder("maxDown", maxDown).put("maxExtraDown", maxExtraDown) // .put("annual", annual).put("annualBenchmark", annualBenchmark).build(); // extInfos.putAll(extInfos1); // dataset.putAll(indexDataListMap); // } return MapUtil.newHashMap(); } /** * 构建指标计算参数 * * @param params 接口请求参数 * @param indicators 要计算的普通指标 * @param geoIndicators geo指标 * @param dateIntervalMap 时段 * @param

类型参数 * @return / */ private

CalcMultipleSecMultipleTimeRangeIndicatorReq buildCalcReq(P params, List indicators, List geoIndicators, Map> dateIntervalMap) { List refIds = params.getFundId(); List secIds = this.getSecIdsByParams(params); // 差集求指数id集合,包括基准 List indexIds = CollectionUtil.subtractToList(secIds, refIds); Map benchmarkIdMap = MapUtil.newHashMap(true); for (String refId : refIds) { if (StrUtil.isBlank(params.getBenchmarkId())) { continue; } benchmarkIdMap.put(refId, params.getBenchmarkId()); } return CalcMultipleSecMultipleTimeRangeIndicatorReq.builder() .mainSecIdList(refIds) .secBenchmarkIdMap(benchmarkIdMap) .indexIdList(indexIds) .raiseType(params.getRaiseType()) .strategy(this.getOriginStrategy(params)) .visibility(Visibility.Both) .dataFrequency(params.getFrequency()) .navType(params.getNavType()) .secDateIntervalDtoListMap(dateIntervalMap) .indicatorList(indicators) .geoExtraindicatorList(geoIndicators) .ifAnnualize(true) .calcIndexRetIndicatorValue(true) .ifConvertPerformanceConsistencyWord(true).build(); } /** * 构建指标计算请求参数 * * @param params / * @param indicatorList 要计算的普通指标列表 * @param geoIndicatorList 几何指标列表 * @param dateIntervalType / * @param frequency / * @param

/ * @return / */ protected

CalcMultipleSecMultipleTimeRangeIndicatorReq buildCalcReq(P params, List indicatorList, List geoIndicatorList, DateIntervalType dateIntervalType, Frequency frequency) { List refIds = params.getFundId(); DateIntervalDto dateInterval = this.buildDateIntervalByParams(params, dateIntervalType, frequency); Map> dateIntervalMap = MapUtil.newHashMap(true); for (String refId : refIds) { dateIntervalMap.put(refId, ListUtil.toList(dateInterval)); } return this.buildCalcReq(params, indicatorList, geoIndicatorList, dateIntervalMap); } /** * 根据请求参数构建 DateIntervalDto 对象 * * @param params 请求参数,需继承 CommonParams * @param dateIntervalType 时段类型 * @param frequency 接口传递的频率,一般为滚动计算时传递的滚动频率 * @param

参数类型 * @return / */ protected

DateIntervalDto buildDateIntervalByParams(P params, DateIntervalType dateIntervalType, Frequency frequency) { if (dateIntervalType == null) { dateIntervalType = DateIntervalType.DefaultInterval; } if (frequency == null) { frequency = params.getFrequency(); } return DateIntervalDto.builder() .id(dateIntervalType.name()) .startDate(params.getStartDate()) .endDate(params.getEndDate()) .timeRange(params.getTimeRange()) .dateIntervalType(dateIntervalType) .frequency(frequency).build(); } /** * 策略枚举,从字符串转换。实现类太多不能自动转换需特殊处理 */ protected

IStrategy getOriginStrategy(P params) { return StrategyHandleUtils.getStrategy(params.getStrategy()); } /** * 公共参数检验,部分参数为空时设置默认值. * * @param params 请求参数 * @param

类型参数 */ protected

void checkParams(P params) { if (params.getFundId() == null || params.getFundId().isEmpty()) { throw new APIException("fundId 参数不能为空"); } if (StrUtil.isBlank(params.getStartDate()) && StrUtil.isBlank(params.getEndDate())) { throw new DataException(null); } if (params.getNavType() == null) { logger.warn(String.format("navType 为null, 设置一个初始值:%s", NavType.CumulativeNav)); params.setNavType(NavType.CumulativeNav); } if (params.getFrequency() == null) { logger.warn(String.format("frequency 为null, 设置一个初始值:%s", Frequency.Daily)); params.setFrequency(Frequency.Daily); } } /** * 从请求参数中获取所有标的并集集合 * * @param params 公共请求参数 * @param

类型 * @return / */ protected

List getSecIdsByParams(P params) { List tempList = ListUtil.list(true); tempList.addAll(params.getFundId()); if (StrUtil.isNotBlank(params.getBenchmarkId())) { tempList.add(params.getBenchmarkId()); } return params.getSecIds() == null ? tempList : CollUtil.addAllIfNotContains(tempList, params.getSecIds()); } /** * 处理最大回撤问题,是否已恢复 * * @param indicatorValues 指标结果 * @param secId 标的id * @param benchmarkId 基准 * @param geo 是否处理geo几何指标 * @return 新map */ protected Map handleMaxDrawdown(Map indicatorValues, String secId, String benchmarkId, boolean geo) { Map result = MapUtil.newHashMap(); String maxDrawdown = indicatorValues.get(Indicator.MaxDrawdown.name()); String consistency = indicatorValues.get(Indicator.PerformanceConsistency.name()); if (StrUtil.isNotBlank(maxDrawdown) && new BigDecimal(maxDrawdown).compareTo(BigDecimal.ZERO) > 0) { if (StrUtil.isBlank(indicatorValues.get(Indicator.MaxDrawdownRecureDate.name()))) { indicatorValues.put(Indicator.MaxDrawdownRecureDate.name(), "最大回撤未修复"); } if (StrUtil.isBlank(indicatorValues.get(Indicator.MaxDrawdownRecureIntervalDays.name()))) { indicatorValues.put(Indicator.MaxDrawdownRecureIntervalDays.name(), "最大回撤未修复"); } if (StrUtil.isBlank(indicatorValues.get(Indicator.MaxDrawdownRecureInterval.name()))) { indicatorValues.put(Indicator.MaxDrawdownRecureInterval.name(), "最大回撤未修复"); } } if (StrUtil.isEmpty(consistency)) { indicatorValues.put(Indicator.PerformanceConsistency.name(), PerformanceConsistency.convert2Word(null)); } else { indicatorValues.put(Indicator.PerformanceConsistency.name(), PerformanceConsistency.convert2Word(Double.parseDouble(consistency))); } if (geo) { indicatorValues.forEach((k, v) -> result.put("geoExcess" + k, secId != null && secId.equals(benchmarkId) ? null : v)); } else { result.putAll(indicatorValues); } return result; } /** * 构建日期值对象 * * @param dateList 日期序列 * @param trendTypeListMap 走势图数据 * @param trendTypes 走势图类型列表 * @return / */ private List> buildDateValue(List dateList, Map> trendTypeListMap, List trendTypes) { List> dataList = ListUtil.list(true); for (int i = 0; i < dateList.size(); i++) { Map temp = MapUtil.builder("date", dateList.get(i)).build(); for (TrendType trendType : trendTypes) { List doubles = trendTypeListMap.get(trendType); Object value = null; try { value = i <= doubles.size() ? doubles.get(i) : null; } catch (Exception ignored) { } temp.put(StrUtil.toCamelCase(trendType.name()), value); } dataList.add(temp); } return dataList; } }