PerformanceService.java 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. package com.smppw.analysis.application.service;
  2. import cn.hutool.core.collection.CollUtil;
  3. import cn.hutool.core.collection.CollectionUtil;
  4. import cn.hutool.core.collection.ListUtil;
  5. import cn.hutool.core.map.MapUtil;
  6. import cn.hutool.core.util.NumberUtil;
  7. import cn.hutool.core.util.StrUtil;
  8. import com.smppw.analysis.application.dto.BaseParams;
  9. import com.smppw.analysis.application.dto.IndicatorParams;
  10. import com.smppw.analysis.application.dto.TrendParams;
  11. import com.smppw.analysis.domain.service.BaseIndicatorServiceV2;
  12. import com.smppw.common.exception.APIException;
  13. import com.smppw.common.exception.DataException;
  14. import com.smppw.common.pojo.IStrategy;
  15. import com.smppw.common.pojo.dto.calc.IndicatorCalcIndexDataDto;
  16. import com.smppw.common.pojo.dto.calc.IndicatorCalcPropertyDto;
  17. import com.smppw.common.pojo.dto.calc.IndicatorCalcSecDataDto;
  18. import com.smppw.common.pojo.dto.indicator.CalcMultipleSecMultipleTimeRangeIndicatorReq;
  19. import com.smppw.common.pojo.dto.indicator.DateIntervalDto;
  20. import com.smppw.common.pojo.enums.*;
  21. import com.smppw.constants.Consts;
  22. import com.smppw.core.reta.calc.PerformanceConsistency;
  23. import com.smppw.utils.StrategyHandleUtils;
  24. import org.slf4j.Logger;
  25. import org.slf4j.LoggerFactory;
  26. import org.springframework.stereotype.Service;
  27. import java.math.BigDecimal;
  28. import java.util.List;
  29. import java.util.Map;
  30. import java.util.Optional;
  31. @Service
  32. public class PerformanceService {
  33. private final Logger logger = LoggerFactory.getLogger(PerformanceService.class);
  34. private final BaseIndicatorServiceV2 baseIndicatorServiceV2;
  35. public PerformanceService(BaseIndicatorServiceV2 baseIndicatorServiceV2) {
  36. this.baseIndicatorServiceV2 = baseIndicatorServiceV2;
  37. }
  38. public Map<String, Object> calcIndicators(IndicatorParams params) {
  39. return this.calcIndicators(params, ListUtil.toList(Indicator.INDICATOR_TYPE_ARRAY), ListUtil.toList(Indicator.RISK_TABLE_EXCESS_INDICATOR_ARRAY));
  40. }
  41. public Map<String, Object> calcIndicators(IndicatorParams params, List<Indicator> indicators, List<Indicator> geoIndicators) {
  42. try {
  43. // 1.参数处理
  44. this.checkParams(params);
  45. List<String> refIds = params.getFundId();
  46. List<String> secIds = this.getSecIdsByParams(params);
  47. // 差集求指数id集合,包括基�
  48. List<String> indexIds = CollectionUtil.subtractToList(secIds, refIds);
  49. String riskOfFreeId = params.getRiskOfFreeId();
  50. Double riskOfFreeValue = params.getRiskOfFreeValue();
  51. if (StrUtil.isBlank(riskOfFreeId)) {
  52. riskOfFreeId = Consts.RISK_OF_FREE;
  53. } else if (NumberUtil.isNumber(riskOfFreeId)) {
  54. riskOfFreeValue = Double.parseDouble(riskOfFreeId);
  55. riskOfFreeId = null;
  56. }
  57. CalcMultipleSecMultipleTimeRangeIndicatorReq req = this.buildCalcReq(params, indicators, geoIndicators, DateIntervalType.CustomInterval, null);
  58. req.setRiskOfFreeId(riskOfFreeId);
  59. req.setRiskOfFreeValue(riskOfFreeValue);
  60. // 2.指标计算
  61. Map<String, List<IndicatorCalcPropertyDto>> indicator = this.baseIndicatorServiceV2.calcMultipleSecMultipleTimeRangeIndicator(req);
  62. // 3.返回结果处理
  63. Map<String, Object> valuesMap = MapUtil.newHashMap(true);
  64. Map<String, Object> indexValuesMap = MapUtil.newHashMap(true);
  65. for (String refId : refIds) {
  66. IndicatorCalcPropertyDto dto = Optional.ofNullable(indicator.get(refId)).filter(e -> !e.isEmpty()).map(e -> e.get(0)).orElse(null);
  67. // 标的指标
  68. Map<String, String> indicatorValues = Optional.ofNullable(dto).map(IndicatorCalcPropertyDto::getSecData).map(IndicatorCalcSecDataDto::getIndicatorValueMap).orElse(MapUtil.empty());
  69. // 标的几何指标
  70. Map<String, String> geoIndicatorValues = Optional.ofNullable(dto).map(IndicatorCalcPropertyDto::getSecData).map(IndicatorCalcSecDataDto::getExtraGeoIndicatorValueMap).orElse(MapUtil.empty());
  71. // 指数指标,包括基�
  72. Map<String, Map<String, String>> indexIndicatorValuesMap = Optional.ofNullable(dto).map(IndicatorCalcPropertyDto::getIndexData).map(IndicatorCalcIndexDataDto::getIndexIndicatorValueMap).orElse(MapUtil.empty());
  73. // 指数几何指标
  74. Map<String, Map<String, String>> indexGeoIndicatorValuesMap = Optional.ofNullable(dto).map(IndicatorCalcPropertyDto::getIndexData).map(IndicatorCalcIndexDataDto::getIndexGeoExtraIndicatorValueMap).orElse(MapUtil.empty());
  75. // 标的和指数分别处理最大回撤指标,并合并指标结果到一个map�
  76. Map<String, String> values = this.handleMaxDrawdown(indicatorValues, refId, null, false);
  77. Map<String, String> geoValues = this.handleMaxDrawdown(geoIndicatorValues, refId, null, true);
  78. Map<String, String> unionValues = MapUtil.<String, String>builder().putAll(values).putAll(geoValues).build();
  79. valuesMap.put(refId, unionValues);
  80. for (String indexId : indexIds) {
  81. if (indexValuesMap.containsKey(indexId)) {
  82. continue;
  83. }
  84. Map<String, String> indexValues = this.handleMaxDrawdown(indexIndicatorValuesMap.get(indexId), indexId, null, false);
  85. Map<String, String> indexGeoValues = this.handleMaxDrawdown(indexGeoIndicatorValuesMap.get(indexId), indexId, params.getBenchmarkId(), true);
  86. Map<String, String> unionIndexValues = MapUtil.<String, String>builder().putAll(indexValues).putAll(indexGeoValues).build();
  87. indexValuesMap.put(indexId, unionIndexValues);
  88. }
  89. }
  90. // 返回结果对象构建
  91. return MapUtil.builder(valuesMap).putAll(indexValuesMap).build();
  92. } catch (Exception e) {
  93. e.printStackTrace();
  94. logger.error(String.format("基金收益风险指标计算错误:%s", e.getMessage()));
  95. }
  96. return MapUtil.newHashMap();
  97. }
  98. public Map<String, Object> trend(TrendParams params) {
  99. List<TrendType> trendTypes = ListUtil.toList(TrendType.Ret, TrendType.Nav, TrendType.ExtraNav, TrendType.OrigNav, TrendType.ExtraRetAri, TrendType.NetValueChange, TrendType.ExtraRetGeo, TrendType.DrawdownTrend);
  100. List<TrendType> indexTrendTypes = ListUtil.toList(TrendType.Ret, TrendType.OrigNav);
  101. return this.handleTrends(params, trendTypes, indexTrendTypes);
  102. }
  103. public Map<String, Object> handleTrends(TrendParams params, List<TrendType> trendTypes, List<TrendType> indexTrendTypes) {
  104. // 1、参数处理
  105. this.checkParams(params);
  106. List<String> refIds = params.getFundId();
  107. List<String> secIds = this.getSecIdsByParams(params);
  108. List<String> indexIds = CollectionUtil.subtractToList(secIds, refIds);
  109. // 时段参数构建
  110. DateIntervalDto dateInterval = this.buildDateIntervalByParams(params, DateIntervalType.CustomInterval, null);
  111. Map<String, String> benchmarkIdMap = MapUtil.newHashMap(true);
  112. for (String refId : refIds) {
  113. if (StrUtil.isBlank(params.getBenchmarkId())) {
  114. continue;
  115. }
  116. benchmarkIdMap.put(refId, params.getBenchmarkId());
  117. }
  118. // 计算走势图
  119. Map<String, List<IndicatorCalcPropertyDto>> trendMap = this.baseIndicatorServiceV2.getMultipleSecTrend(refIds, benchmarkIdMap,
  120. indexIds, dateInterval, params.getFrequency(), null, null, params.getRaiseType(), this.getOriginStrategy(params),
  121. Visibility.Both, params.getNavType(), trendTypes);
  122. // 结果处理,支持多标的
  123. // 处理走势图
  124. Map<String, List<Map<String, Object>>> dataListMap = MapUtil.newHashMap(true);
  125. Map<String, List<Map<String, Object>>> indexDataListMap = MapUtil.newHashMap(true);
  126. Map<String, Map<String, Object>> trendListMap = MapUtil.newHashMap(true);
  127. Map<String, Map<String, Object>> indexTrendListMap = MapUtil.newHashMap(true);
  128. for (String refId : refIds) {
  129. IndicatorCalcPropertyDto dto = Optional.ofNullable(trendMap.get(refId)).filter(e -> !e.isEmpty()).map(e -> e.get(0)).orElse(null);
  130. List<String> tempDateList = Optional.ofNullable(dto).map(IndicatorCalcPropertyDto::getDateList).orElse(ListUtil.empty());
  131. // 基金走势序列
  132. Map<TrendType, List<Double>> tempTrendTypeListMap = Optional.ofNullable(dto).map(IndicatorCalcPropertyDto::getSecData).map(IndicatorCalcSecDataDto::getTrendValueMap).orElse(MapUtil.empty());
  133. trendListMap.put(refId, MapUtil.<String, Object>builder().put("dates", tempDateList).put("trend", tempTrendTypeListMap).build());
  134. dataListMap.put(refId, this.buildDateValue(tempDateList, tempTrendTypeListMap, trendTypes));
  135. // 指数净值和收益序列
  136. Map<String, Map<TrendType, List<Double>>> tempIndexTrendTypeListMap = Optional.ofNullable(dto).map(IndicatorCalcPropertyDto::getIndexData).map(IndicatorCalcIndexDataDto::getIndexTrendValueMap).orElse(MapUtil.empty());
  137. for (String indexId : indexIds) {
  138. if (indexDataListMap.containsKey(indexId) && !indexId.equals(params.getBenchmarkId())) {
  139. continue;
  140. }
  141. indexTrendListMap.put(indexId, MapUtil.<String, Object>builder().put("dates", tempDateList).put("trend", tempIndexTrendTypeListMap.get(indexId)).build());
  142. indexDataListMap.put(indexId, this.buildDateValue(tempDateList, tempIndexTrendTypeListMap.get(indexId), indexTrendTypes));
  143. }
  144. }
  145. // Map<String, List<Map<String, Object>>> valuesMap = this.multiSecMergeDateValue(refIds, trendMap, dataListMap, trendTypes, multiSec);
  146. Map<String, List<Map<String, Object>>> dataset = MapUtil.<String, List<Map<String, Object>>>builder().putAll(dataListMap).build();
  147. // Map<String, String> productNameMapping = this.secId2NameService.query(secIds);
  148. // Map<String, Object> extInfos = MapUtil.<String, Object>builder().put("endDate", params.getEndDate()).put("startDate", params.getStartDate()).build();
  149. // if (masterId != null || !multiSec) {
  150. // // 求基金和基准的年化收益率
  151. // String fundId = refIds.get(0);
  152. // Map<String, Object> map = trendListMap.get(fundId);
  153. // Map<String, Object> indexMap = Optional.ofNullable(indexTrendListMap.get(params.getBenchmarkId())).orElse(MapUtil.newHashMap());
  154. // List<String> dateList = MapUtil.get(map, "dates", List.class);
  155. // Map<TrendType, List<Double>> trendTypeListMap = MapUtil.get(map, "trend", Map.class);
  156. // Map<TrendType, List<Double>> indexTrendTypeListMap = Optional.ofNullable(MapUtil.get(indexMap, "trend", Map.class)).orElse(MapUtil.newHashMap());
  157. // List<DateValue> dateValues = this.buildDateValue(dateList, trendTypeListMap.get(TrendType.Nav));
  158. // List<DateValue> indexDateValues = this.buildDateValue(dateList, indexTrendTypeListMap.get(TrendType.OrigNav));
  159. // Double annual = IndicatorFactory.getInstance().get(Indicator.AnnualReturn).calc(dateValues);
  160. // Double annualBenchmark = IndicatorFactory.getInstance().get(Indicator.AnnualReturn).calc(indexDateValues);
  161. // // 求最大回撤和超额最大回撤,业绩走势图不返回
  162. // Double maxDown = this.handleMaxAndMin(trendTypeListMap.get(TrendType.DrawdownTrend), (o1, o2) -> o1 > o2 ? o2 : o1);
  163. // Double maxExtraDown = this.handleMaxAndMin(trendTypeListMap.get(TrendType.ExtraDrawdownTrend), (o1, o2) -> o1 > o2 ? o2 : o1);
  164. // Map<String, Object> extInfos1 = MapUtil.<String, Object>builder("maxDown", maxDown).put("maxExtraDown", maxExtraDown)
  165. // .put("annual", annual).put("annualBenchmark", annualBenchmark).build();
  166. // extInfos.putAll(extInfos1);
  167. // dataset.putAll(indexDataListMap);
  168. // }
  169. return MapUtil.newHashMap();
  170. }
  171. /**
  172. * 构建指标计算参数
  173. *
  174. * @param params 接口请求参数
  175. * @param indicators 要计算的普通指标
  176. * @param geoIndicators geo指标
  177. * @param dateIntervalMap 时段
  178. * @param <P> 类型参数
  179. * @return /
  180. */
  181. private <P extends BaseParams> CalcMultipleSecMultipleTimeRangeIndicatorReq buildCalcReq(P params, List<Indicator> indicators, List<Indicator> geoIndicators, Map<String, List<DateIntervalDto>> dateIntervalMap) {
  182. List<String> refIds = params.getFundId();
  183. List<String> secIds = this.getSecIdsByParams(params);
  184. // 差集求指数id集合,包括基准
  185. List<String> indexIds = CollectionUtil.subtractToList(secIds, refIds);
  186. Map<String, String> benchmarkIdMap = MapUtil.newHashMap(true);
  187. for (String refId : refIds) {
  188. if (StrUtil.isBlank(params.getBenchmarkId())) {
  189. continue;
  190. }
  191. benchmarkIdMap.put(refId, params.getBenchmarkId());
  192. }
  193. return CalcMultipleSecMultipleTimeRangeIndicatorReq.builder()
  194. .mainSecIdList(refIds)
  195. .secBenchmarkIdMap(benchmarkIdMap)
  196. .indexIdList(indexIds)
  197. .raiseType(params.getRaiseType())
  198. .strategy(this.getOriginStrategy(params))
  199. .visibility(Visibility.Both)
  200. .dataFrequency(params.getFrequency())
  201. .navType(params.getNavType())
  202. .secDateIntervalDtoListMap(dateIntervalMap)
  203. .indicatorList(indicators)
  204. .geoExtraindicatorList(geoIndicators)
  205. .ifAnnualize(true)
  206. .calcIndexRetIndicatorValue(true)
  207. .ifConvertPerformanceConsistencyWord(true).build();
  208. }
  209. /**
  210. * 构建指标计算请求参数
  211. *
  212. * @param params /
  213. * @param indicatorList 要计算的普通指标列表
  214. * @param geoIndicatorList 几何指标列表
  215. * @param dateIntervalType /
  216. * @param frequency /
  217. * @param <P> /
  218. * @return /
  219. */
  220. protected <P extends BaseParams> CalcMultipleSecMultipleTimeRangeIndicatorReq buildCalcReq(P params, List<Indicator> indicatorList, List<Indicator> geoIndicatorList,
  221. DateIntervalType dateIntervalType, Frequency frequency) {
  222. List<String> refIds = params.getFundId();
  223. DateIntervalDto dateInterval = this.buildDateIntervalByParams(params, dateIntervalType, frequency);
  224. Map<String, List<DateIntervalDto>> dateIntervalMap = MapUtil.newHashMap(true);
  225. for (String refId : refIds) {
  226. dateIntervalMap.put(refId, ListUtil.toList(dateInterval));
  227. }
  228. return this.buildCalcReq(params, indicatorList, geoIndicatorList, dateIntervalMap);
  229. }
  230. /**
  231. * 根据请求参数构建 DateIntervalDto 对象
  232. *
  233. * @param params 请求参数,需继承 CommonParams
  234. * @param dateIntervalType 时段类型
  235. * @param frequency 接口传递的频率,一般为滚动计算时传递的滚动频率
  236. * @param <P> 参数类型
  237. * @return /
  238. */
  239. protected <P extends BaseParams> DateIntervalDto buildDateIntervalByParams(P params, DateIntervalType dateIntervalType, Frequency frequency) {
  240. if (dateIntervalType == null) {
  241. dateIntervalType = DateIntervalType.DefaultInterval;
  242. }
  243. if (frequency == null) {
  244. frequency = params.getFrequency();
  245. }
  246. return DateIntervalDto.builder()
  247. .id(dateIntervalType.name())
  248. .startDate(params.getStartDate())
  249. .endDate(params.getEndDate())
  250. .timeRange(params.getTimeRange())
  251. .dateIntervalType(dateIntervalType)
  252. .frequency(frequency).build();
  253. }
  254. /**
  255. * 策略枚举,从字符串转换。实现类太多不能自动转换需特殊处理
  256. */
  257. protected <P extends BaseParams> IStrategy getOriginStrategy(P params) {
  258. return StrategyHandleUtils.getStrategy(params.getStrategy());
  259. }
  260. /**
  261. * 公共参数检验,部分参数为空时设置默认值.
  262. *
  263. * @param params 请求参数
  264. * @param <P> 类型参数
  265. */
  266. protected <P extends BaseParams> void checkParams(P params) {
  267. if (params.getFundId() == null || params.getFundId().isEmpty()) {
  268. throw new APIException("fundId 参数不能为空");
  269. }
  270. if (StrUtil.isBlank(params.getStartDate()) && StrUtil.isBlank(params.getEndDate())) {
  271. throw new DataException(null);
  272. }
  273. if (params.getNavType() == null) {
  274. logger.warn(String.format("navType 为null, 设置一个初始值:%s", NavType.CumulativeNav));
  275. params.setNavType(NavType.CumulativeNav);
  276. }
  277. if (params.getFrequency() == null) {
  278. logger.warn(String.format("frequency 为null, 设置一个初始值:%s", Frequency.Daily));
  279. params.setFrequency(Frequency.Daily);
  280. }
  281. }
  282. /**
  283. * 从请求参数中获取所有标的并集集合
  284. *
  285. * @param params 公共请求参数
  286. * @param <P> 类型
  287. * @return /
  288. */
  289. protected <P extends BaseParams> List<String> getSecIdsByParams(P params) {
  290. List<String> tempList = ListUtil.list(true);
  291. tempList.addAll(params.getFundId());
  292. if (StrUtil.isNotBlank(params.getBenchmarkId())) {
  293. tempList.add(params.getBenchmarkId());
  294. }
  295. return params.getSecIds() == null ? tempList : CollUtil.addAllIfNotContains(tempList, params.getSecIds());
  296. }
  297. /**
  298. * 处理最大回撤问题,是否已恢复
  299. *
  300. * @param indicatorValues 指标结果
  301. * @param secId 标的id
  302. * @param benchmarkId 基准
  303. * @param geo 是否处理geo几何指标
  304. * @return 新map
  305. */
  306. protected Map<String, String> handleMaxDrawdown(Map<String, String> indicatorValues, String secId, String benchmarkId, boolean geo) {
  307. Map<String, String> result = MapUtil.newHashMap();
  308. String maxDrawdown = indicatorValues.get(Indicator.MaxDrawdown.name());
  309. String consistency = indicatorValues.get(Indicator.PerformanceConsistency.name());
  310. if (StrUtil.isNotBlank(maxDrawdown) && new BigDecimal(maxDrawdown).compareTo(BigDecimal.ZERO) > 0) {
  311. if (StrUtil.isBlank(indicatorValues.get(Indicator.MaxDrawdownRecureDate.name()))) {
  312. indicatorValues.put(Indicator.MaxDrawdownRecureDate.name(), "最大回撤未修复");
  313. }
  314. if (StrUtil.isBlank(indicatorValues.get(Indicator.MaxDrawdownRecureIntervalDays.name()))) {
  315. indicatorValues.put(Indicator.MaxDrawdownRecureIntervalDays.name(), "最大回撤未修复");
  316. }
  317. if (StrUtil.isBlank(indicatorValues.get(Indicator.MaxDrawdownRecureInterval.name()))) {
  318. indicatorValues.put(Indicator.MaxDrawdownRecureInterval.name(), "最大回撤未修复");
  319. }
  320. }
  321. if (StrUtil.isEmpty(consistency)) {
  322. indicatorValues.put(Indicator.PerformanceConsistency.name(), PerformanceConsistency.convert2Word(null));
  323. } else {
  324. indicatorValues.put(Indicator.PerformanceConsistency.name(), PerformanceConsistency.convert2Word(Double.parseDouble(consistency)));
  325. }
  326. if (geo) {
  327. indicatorValues.forEach((k, v) -> result.put("geoExcess" + k, secId != null && secId.equals(benchmarkId) ? null : v));
  328. } else {
  329. result.putAll(indicatorValues);
  330. }
  331. return result;
  332. }
  333. /**
  334. * 构建日期值对象
  335. *
  336. * @param dateList 日期序列
  337. * @param trendTypeListMap 走势图数据
  338. * @param trendTypes 走势图类型列表
  339. * @return /
  340. */
  341. private List<Map<String, Object>> buildDateValue(List<String> dateList, Map<TrendType, List<Double>> trendTypeListMap, List<TrendType> trendTypes) {
  342. List<Map<String, Object>> dataList = ListUtil.list(true);
  343. for (int i = 0; i < dateList.size(); i++) {
  344. Map<String, Object> temp = MapUtil.<String, Object>builder("date", dateList.get(i)).build();
  345. for (TrendType trendType : trendTypes) {
  346. List<Double> doubles = trendTypeListMap.get(trendType);
  347. Object value = null;
  348. try {
  349. value = i <= doubles.size() ? doubles.get(i) : null;
  350. } catch (Exception ignored) {
  351. }
  352. temp.put(StrUtil.toCamelCase(trendType.name()), value);
  353. }
  354. dataList.add(temp);
  355. }
  356. return dataList;
  357. }
  358. }