123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381 |
- 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<String, Object> calcIndicators(IndicatorParams params) {
- return this.calcIndicators(params, ListUtil.toList(Indicator.INDICATOR_TYPE_ARRAY), ListUtil.toList(Indicator.RISK_TABLE_EXCESS_INDICATOR_ARRAY));
- }
- public Map<String, Object> calcIndicators(IndicatorParams params, List<Indicator> indicators, List<Indicator> geoIndicators) {
- try {
- // 1.参数处理
- this.checkParams(params);
- List<String> refIds = params.getFundId();
- List<String> secIds = this.getSecIdsByParams(params);
- // 差集求指数id集合,包括基�
- List<String> 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<String, List<IndicatorCalcPropertyDto>> indicator = this.baseIndicatorServiceV2.calcMultipleSecMultipleTimeRangeIndicator(req);
- // 3.返回结果处理
- Map<String, Object> valuesMap = MapUtil.newHashMap(true);
- Map<String, Object> 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<String, String> indicatorValues = Optional.ofNullable(dto).map(IndicatorCalcPropertyDto::getSecData).map(IndicatorCalcSecDataDto::getIndicatorValueMap).orElse(MapUtil.empty());
- // 标的几何指标
- Map<String, String> geoIndicatorValues = Optional.ofNullable(dto).map(IndicatorCalcPropertyDto::getSecData).map(IndicatorCalcSecDataDto::getExtraGeoIndicatorValueMap).orElse(MapUtil.empty());
- // 指数指标,包括基�
- Map<String, Map<String, String>> indexIndicatorValuesMap = Optional.ofNullable(dto).map(IndicatorCalcPropertyDto::getIndexData).map(IndicatorCalcIndexDataDto::getIndexIndicatorValueMap).orElse(MapUtil.empty());
- // 指数几何指标
- Map<String, Map<String, String>> indexGeoIndicatorValuesMap = Optional.ofNullable(dto).map(IndicatorCalcPropertyDto::getIndexData).map(IndicatorCalcIndexDataDto::getIndexGeoExtraIndicatorValueMap).orElse(MapUtil.empty());
- // 标的和指数分别处理最大回撤指标,并合并指标结果到一个map�
- Map<String, String> values = this.handleMaxDrawdown(indicatorValues, refId, null, false);
- Map<String, String> geoValues = this.handleMaxDrawdown(geoIndicatorValues, refId, null, true);
- Map<String, String> unionValues = MapUtil.<String, String>builder().putAll(values).putAll(geoValues).build();
- valuesMap.put(refId, unionValues);
- for (String indexId : indexIds) {
- if (indexValuesMap.containsKey(indexId)) {
- continue;
- }
- Map<String, String> indexValues = this.handleMaxDrawdown(indexIndicatorValuesMap.get(indexId), indexId, null, false);
- Map<String, String> indexGeoValues = this.handleMaxDrawdown(indexGeoIndicatorValuesMap.get(indexId), indexId, params.getBenchmarkId(), true);
- Map<String, String> unionIndexValues = MapUtil.<String, String>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<String, Object> trend(TrendParams params) {
- List<TrendType> trendTypes = ListUtil.toList(TrendType.Ret, TrendType.Nav, TrendType.ExtraNav, TrendType.OrigNav, TrendType.ExtraRetAri, TrendType.NetValueChange, TrendType.ExtraRetGeo, TrendType.DrawdownTrend);
- List<TrendType> indexTrendTypes = ListUtil.toList(TrendType.Ret, TrendType.OrigNav);
- return this.handleTrends(params, trendTypes, indexTrendTypes);
- }
- public Map<String, Object> handleTrends(TrendParams params, List<TrendType> trendTypes, List<TrendType> indexTrendTypes) {
- // 1、参数处理
- this.checkParams(params);
- List<String> refIds = params.getFundId();
- List<String> secIds = this.getSecIdsByParams(params);
- List<String> indexIds = CollectionUtil.subtractToList(secIds, refIds);
- // 时段参数构建
- DateIntervalDto dateInterval = this.buildDateIntervalByParams(params, DateIntervalType.CustomInterval, null);
- Map<String, String> benchmarkIdMap = MapUtil.newHashMap(true);
- for (String refId : refIds) {
- if (StrUtil.isBlank(params.getBenchmarkId())) {
- continue;
- }
- benchmarkIdMap.put(refId, params.getBenchmarkId());
- }
- // 计算走势图
- Map<String, List<IndicatorCalcPropertyDto>> trendMap = this.baseIndicatorServiceV2.getMultipleSecTrend(refIds, benchmarkIdMap,
- indexIds, dateInterval, params.getFrequency(), null, null, params.getRaiseType(), this.getOriginStrategy(params),
- Visibility.Both, params.getNavType(), trendTypes);
- // 结果处理,支持多标的
- // 处理走势图
- Map<String, List<Map<String, Object>>> dataListMap = MapUtil.newHashMap(true);
- Map<String, List<Map<String, Object>>> indexDataListMap = MapUtil.newHashMap(true);
- Map<String, Map<String, Object>> trendListMap = MapUtil.newHashMap(true);
- Map<String, Map<String, Object>> 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<String> tempDateList = Optional.ofNullable(dto).map(IndicatorCalcPropertyDto::getDateList).orElse(ListUtil.empty());
- // 基金走势序列
- Map<TrendType, List<Double>> tempTrendTypeListMap = Optional.ofNullable(dto).map(IndicatorCalcPropertyDto::getSecData).map(IndicatorCalcSecDataDto::getTrendValueMap).orElse(MapUtil.empty());
- trendListMap.put(refId, MapUtil.<String, Object>builder().put("dates", tempDateList).put("trend", tempTrendTypeListMap).build());
- dataListMap.put(refId, this.buildDateValue(tempDateList, tempTrendTypeListMap, trendTypes));
- // 指数净值和收益序列
- Map<String, Map<TrendType, List<Double>>> 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.<String, Object>builder().put("dates", tempDateList).put("trend", tempIndexTrendTypeListMap.get(indexId)).build());
- indexDataListMap.put(indexId, this.buildDateValue(tempDateList, tempIndexTrendTypeListMap.get(indexId), indexTrendTypes));
- }
- }
- // Map<String, List<Map<String, Object>>> valuesMap = this.multiSecMergeDateValue(refIds, trendMap, dataListMap, trendTypes, multiSec);
- Map<String, List<Map<String, Object>>> dataset = MapUtil.<String, List<Map<String, Object>>>builder().putAll(dataListMap).build();
- // Map<String, String> productNameMapping = this.secId2NameService.query(secIds);
- // Map<String, Object> extInfos = MapUtil.<String, Object>builder().put("endDate", params.getEndDate()).put("startDate", params.getStartDate()).build();
- // if (masterId != null || !multiSec) {
- // // 求基金和基准的年化收益率
- // String fundId = refIds.get(0);
- // Map<String, Object> map = trendListMap.get(fundId);
- // Map<String, Object> indexMap = Optional.ofNullable(indexTrendListMap.get(params.getBenchmarkId())).orElse(MapUtil.newHashMap());
- // List<String> dateList = MapUtil.get(map, "dates", List.class);
- // Map<TrendType, List<Double>> trendTypeListMap = MapUtil.get(map, "trend", Map.class);
- // Map<TrendType, List<Double>> indexTrendTypeListMap = Optional.ofNullable(MapUtil.get(indexMap, "trend", Map.class)).orElse(MapUtil.newHashMap());
- // List<DateValue> dateValues = this.buildDateValue(dateList, trendTypeListMap.get(TrendType.Nav));
- // List<DateValue> 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<String, Object> extInfos1 = MapUtil.<String, Object>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 <P> 类型参数
- * @return /
- */
- private <P extends BaseParams> CalcMultipleSecMultipleTimeRangeIndicatorReq buildCalcReq(P params, List<Indicator> indicators, List<Indicator> geoIndicators, Map<String, List<DateIntervalDto>> dateIntervalMap) {
- List<String> refIds = params.getFundId();
- List<String> secIds = this.getSecIdsByParams(params);
- // 差集求指数id集合,包括基准
- List<String> indexIds = CollectionUtil.subtractToList(secIds, refIds);
- Map<String, String> 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 <P> /
- * @return /
- */
- protected <P extends BaseParams> CalcMultipleSecMultipleTimeRangeIndicatorReq buildCalcReq(P params, List<Indicator> indicatorList, List<Indicator> geoIndicatorList,
- DateIntervalType dateIntervalType, Frequency frequency) {
- List<String> refIds = params.getFundId();
- DateIntervalDto dateInterval = this.buildDateIntervalByParams(params, dateIntervalType, frequency);
- Map<String, List<DateIntervalDto>> 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 <P> 参数类型
- * @return /
- */
- protected <P extends BaseParams> 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 <P extends BaseParams> IStrategy getOriginStrategy(P params) {
- return StrategyHandleUtils.getStrategy(params.getStrategy());
- }
- /**
- * 公共参数检验,部分参数为空时设置默认值.
- *
- * @param params 请求参数
- * @param <P> 类型参数
- */
- protected <P extends BaseParams> 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 <P> 类型
- * @return /
- */
- protected <P extends BaseParams> List<String> getSecIdsByParams(P params) {
- List<String> 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<String, String> handleMaxDrawdown(Map<String, String> indicatorValues, String secId, String benchmarkId, boolean geo) {
- Map<String, String> 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<Map<String, Object>> buildDateValue(List<String> dateList, Map<TrendType, List<Double>> trendTypeListMap, List<TrendType> trendTypes) {
- List<Map<String, Object>> dataList = ListUtil.list(true);
- for (int i = 0; i < dateList.size(); i++) {
- Map<String, Object> temp = MapUtil.<String, Object>builder("date", dateList.get(i)).build();
- for (TrendType trendType : trendTypes) {
- List<Double> 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;
- }
- }
|