|
@@ -3,30 +3,35 @@ package com.smppw.analysis.application.service.info;
|
|
|
import cn.hutool.core.bean.BeanUtil;
|
|
|
import cn.hutool.core.collection.CollUtil;
|
|
|
import cn.hutool.core.collection.ListUtil;
|
|
|
+import cn.hutool.core.date.DateUnit;
|
|
|
import cn.hutool.core.date.DateUtil;
|
|
|
import cn.hutool.core.map.MapUtil;
|
|
|
import cn.hutool.core.text.CharSequenceUtil;
|
|
|
+import cn.hutool.core.util.NumberUtil;
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
import com.smppw.analysis.application.dto.info.FundSimilarReq;
|
|
|
+import com.smppw.analysis.application.dto.info.ManualFundManagerParams;
|
|
|
import com.smppw.analysis.application.dto.info.ManualFundNoticeReq;
|
|
|
-import com.smppw.analysis.domain.dataobject.FundArchivesInfoDO;
|
|
|
-import com.smppw.analysis.domain.dataobject.FundFeeDo;
|
|
|
-import com.smppw.analysis.domain.dataobject.FundSimilarDo;
|
|
|
-import com.smppw.analysis.domain.dataobject.ManualFundNoticeInfoDO;
|
|
|
+import com.smppw.analysis.domain.dataobject.*;
|
|
|
import com.smppw.analysis.domain.dto.info.*;
|
|
|
+import com.smppw.analysis.domain.manager.performance.PerformanceFactory;
|
|
|
import com.smppw.analysis.domain.service.BaseIndicatorServiceV2;
|
|
|
import com.smppw.analysis.domain.service.BaseInfoService;
|
|
|
import com.smppw.analysis.domain.service.NavService;
|
|
|
import com.smppw.analysis.infrastructure.utils.StringUtil;
|
|
|
import com.smppw.common.pojo.IStrategy;
|
|
|
import com.smppw.common.pojo.NewSubStrategy;
|
|
|
+import com.smppw.common.pojo.ValueLabelVO;
|
|
|
import com.smppw.common.pojo.dto.DateValue;
|
|
|
+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.calc.IndicatorCalcTimeRangeDto;
|
|
|
-import com.smppw.common.pojo.enums.Frequency;
|
|
|
-import com.smppw.common.pojo.enums.NavType;
|
|
|
-import com.smppw.common.pojo.enums.TimeRange;
|
|
|
-import com.smppw.common.pojo.enums.Visibility;
|
|
|
+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.common.pojo.enums.strategy.Strategy;
|
|
|
+import com.smppw.constants.Consts;
|
|
|
import com.smppw.constants.DateConst;
|
|
|
import com.smppw.core.IndicatorService;
|
|
|
import com.smppw.utils.BigDecimalUtils;
|
|
@@ -38,12 +43,14 @@ import org.springframework.stereotype.Service;
|
|
|
|
|
|
import java.math.RoundingMode;
|
|
|
import java.util.*;
|
|
|
+import java.util.function.Function;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
@Service
|
|
|
public class FundInfoService {
|
|
|
- private static final Map<String, String> ACCRUED_FREQUENCY = MapUtil.newHashMap();
|
|
|
- private static final Map<String, String> ACCRUED_METHOD = MapUtil.newHashMap();
|
|
|
+ private static final Map<String, String> ACCRUED_FREQUENCY = MapUtil.newHashMap(5);
|
|
|
+ private static final Map<String, String> ACCRUED_METHOD = MapUtil.newHashMap(3);
|
|
|
+ private static final Map<Integer, String> EDUCATION_NAME_MAP = MapUtil.newHashMap(8);
|
|
|
|
|
|
static {
|
|
|
ACCRUED_FREQUENCY.put("1", "月");
|
|
@@ -54,15 +61,26 @@ public class FundInfoService {
|
|
|
ACCRUED_METHOD.put("1", "高水位");
|
|
|
ACCRUED_METHOD.put("2", "高水位+赎回时补充");
|
|
|
ACCRUED_METHOD.put("3", "单客户高水位");
|
|
|
+ EDUCATION_NAME_MAP.put(1, "小学");
|
|
|
+ EDUCATION_NAME_MAP.put(2, "中学");
|
|
|
+ EDUCATION_NAME_MAP.put(3, "大专");
|
|
|
+ EDUCATION_NAME_MAP.put(4, "本科");
|
|
|
+ EDUCATION_NAME_MAP.put(5, "硕士");
|
|
|
+ EDUCATION_NAME_MAP.put(6, "博士");
|
|
|
+ EDUCATION_NAME_MAP.put(7, "博士后");
|
|
|
+ EDUCATION_NAME_MAP.put(-1, "其他");
|
|
|
}
|
|
|
|
|
|
private final NavService navService;
|
|
|
+ private final PerformanceFactory factory;
|
|
|
private final BaseInfoService baseInfoService;
|
|
|
private final BaseIndicatorServiceV2 baseIndicatorServiceV2;
|
|
|
|
|
|
public FundInfoService(NavService navService,
|
|
|
+ PerformanceFactory factory,
|
|
|
BaseInfoService baseInfoService,
|
|
|
BaseIndicatorServiceV2 baseIndicatorServiceV2) {
|
|
|
+ this.factory = factory;
|
|
|
this.navService = navService;
|
|
|
this.baseInfoService = baseInfoService;
|
|
|
this.baseIndicatorServiceV2 = baseIndicatorServiceV2;
|
|
@@ -244,4 +262,203 @@ public class FundInfoService {
|
|
|
return fundFeeDo;
|
|
|
}
|
|
|
|
|
|
+ public ManualFundFeeInfoVO getMfFundFee(String fundId) {
|
|
|
+ List<ManualFundFeeDo> dataList = this.baseInfoService.getManualFundFee(fundId);
|
|
|
+ ManualFundFeeInfoVO result = new ManualFundFeeInfoVO();
|
|
|
+ // 数据库 费率 类型与中文映射
|
|
|
+ Map<String, String> typeMapper = MapUtil.builder("10210", "认购金额").put("11210", "申购金额").put("11010", "申购金额")
|
|
|
+ .put("12200", "持有期限").put("12000", "持有期限").build();
|
|
|
+ // 费率类型 与 单位映射
|
|
|
+ Map<String, String> unitMapper = MapUtil.builder("10210", "万元").put("11210", "万元").put("11010", "万元")
|
|
|
+ .put("12200", "日").put("12000", "日").build();
|
|
|
+ // 记录映射函数,返回一个包含最大最小的范围
|
|
|
+ Function<ManualFundFeeDo, ValueLabelVO> function = e -> {
|
|
|
+ ValueLabelVO vo = new ValueLabelVO();
|
|
|
+ vo.setValue(e.getChargeRateDes());
|
|
|
+ String label = typeMapper.get(e.getChargeRateType());
|
|
|
+ // 最小值不为0和null时,设置为起始数据
|
|
|
+ if (e.getStDivStand1() != null && !Double.valueOf(0.0d).equals(e.getStDivStand1())) {
|
|
|
+ label = NumberUtil.roundStr(e.getStDivStand1(), 0) + unitMapper.get(e.getChargeRateType()) + " <= " + label;
|
|
|
+ }
|
|
|
+ // 最大值不为null时设置截止范围数据
|
|
|
+ if (e.getEnDivStand1() != null) {
|
|
|
+ label += " < " + NumberUtil.roundStr(e.getEnDivStand1(), 0) + unitMapper.get(e.getChargeRateType());
|
|
|
+ }
|
|
|
+ vo.setLabel(label);
|
|
|
+ return vo;
|
|
|
+ };
|
|
|
+ // 认购费
|
|
|
+ List<ValueLabelVO> subscriptionFee = this.handleFee(dataList, "10210", function);
|
|
|
+ result.setSubscriptionFee(subscriptionFee);
|
|
|
+ // 申购费,先取场外
|
|
|
+ List<ValueLabelVO> applyFee = this.handleFee(dataList, "11210", function);
|
|
|
+ if (applyFee.isEmpty()) {
|
|
|
+ applyFee = this.handleFee(dataList, "11010", function);
|
|
|
+ }
|
|
|
+ result.setApplyFee(applyFee);
|
|
|
+ // 赎回费,先取场外
|
|
|
+ List<ValueLabelVO> redemptionFee = this.handleFee(dataList, "12200", function);
|
|
|
+ if (redemptionFee.isEmpty()) {
|
|
|
+ redemptionFee = this.handleFee(dataList, "12000", function);
|
|
|
+ }
|
|
|
+ result.setRedemptionFee(redemptionFee);
|
|
|
+ // 管理费、托管费和营销费
|
|
|
+ result.setManagementFeeTrust(this.handleOneFee(dataList, "15000", ManualFundFeeDo::getChargeRateDes));
|
|
|
+ result.setManagementFeeBank(this.handleOneFee(dataList, "16000", ManualFundFeeDo::getChargeRateDes));
|
|
|
+ result.setSaleFee(this.handleOneFee(dataList, "19000", ManualFundFeeDo::getChargeRateDes));
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 费率处理工具,返回一个列表
|
|
|
+ *
|
|
|
+ * @param dataList 费率数据
|
|
|
+ * @param type 过滤的关键字
|
|
|
+ * @param function 转换函数
|
|
|
+ * @param <T> 类型参数
|
|
|
+ * @return /
|
|
|
+ */
|
|
|
+ private <T> List<T> handleFee(List<ManualFundFeeDo> dataList, String type, Function<ManualFundFeeDo, T> function) {
|
|
|
+ return dataList.stream().filter(e -> type.equals(e.getChargeRateType())).map(function).collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 费率处理工具,返回一个对象
|
|
|
+ *
|
|
|
+ * @param dataList 费率数据
|
|
|
+ * @param type 过滤的关键字
|
|
|
+ * @param function 转换函数
|
|
|
+ * @param <T> 类型参数
|
|
|
+ * @return /
|
|
|
+ */
|
|
|
+ private <T> T handleOneFee(List<ManualFundFeeDo> dataList, String type, Function<ManualFundFeeDo, T> function) {
|
|
|
+ return this.handleFee(dataList, type, function).stream().findFirst().orElse(null);
|
|
|
+ }
|
|
|
+
|
|
|
+ public List<FundManagerInfoVo> getHFManagerInfo(String refId) {
|
|
|
+ List<FundManagerInfoVo> resultList = ListUtil.list(true);
|
|
|
+ List<PersonnelInformationDo> managerInfoList = this.baseInfoService.getFundManagerInfo(refId);
|
|
|
+ if (CollUtil.isEmpty(managerInfoList)) {
|
|
|
+ return resultList;
|
|
|
+ }
|
|
|
+ List<String> managerIdList = managerInfoList.stream().map(PersonnelInformationDo::getPersonnelId).collect(Collectors.toList());
|
|
|
+ List<PersonnelWorkExperienceDo> workExperienceList = this.baseInfoService.listPersonnelWorkExperience(managerIdList);
|
|
|
+ //过滤掉任职开始日期和任职结束日期为空的数据
|
|
|
+ if (CollUtil.isNotEmpty(workExperienceList)) {
|
|
|
+ workExperienceList = workExperienceList.stream()
|
|
|
+ .filter(e -> !(StrUtil.isEmpty(e.getStartDate()) && StrUtil.isEmpty(e.getEndDate()))).collect(Collectors.toList());
|
|
|
+ }
|
|
|
+ if (CollUtil.isNotEmpty(workExperienceList)) {
|
|
|
+ Map<String, List<PersonnelWorkExperienceDo>> personnelIdWorkExperienceMap = workExperienceList.stream()
|
|
|
+ .collect(Collectors.groupingBy(PersonnelWorkExperienceDo::getPersonnelId));
|
|
|
+ for (PersonnelInformationDo fundManagerInfo : managerInfoList) {
|
|
|
+ FundManagerInfoVo result = new FundManagerInfoVo();
|
|
|
+ result.setManagerId(fundManagerInfo.getPersonnelId());
|
|
|
+ result.setManagerName(fundManagerInfo.getPersonnelName());
|
|
|
+ result.setAvatar(fundManagerInfo.getAvatar());
|
|
|
+ result.setAvatar2(fundManagerInfo.getAvatar2());
|
|
|
+ result.setEducation(EDUCATION_NAME_MAP.get(fundManagerInfo.getEducation()));
|
|
|
+ result.setProfile(fundManagerInfo.getProfile());
|
|
|
+ List<PersonnelWorkExperienceDo> workExperienceDoList = personnelIdWorkExperienceMap.get(fundManagerInfo.getPersonnelId());
|
|
|
+ if (CollUtil.isNotEmpty(workExperienceDoList)) {
|
|
|
+ String latestPosition = workExperienceDoList.stream().filter(e -> e.getEndDate() == null)
|
|
|
+ .sorted(Comparator.comparing(PersonnelWorkExperienceDo::getStartDate).reversed())
|
|
|
+ .map(PersonnelWorkExperienceDo::getPosition).findFirst().orElse(null);
|
|
|
+ result.setManagerWorkExperienceList(workExperienceDoList);
|
|
|
+ result.setLatestPosition(latestPosition);
|
|
|
+ }
|
|
|
+ resultList.add(result);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return resultList;
|
|
|
+ }
|
|
|
+
|
|
|
+ public List<ManualFundManagerChangeVO> managerChangeList(ManualFundManagerParams params) {
|
|
|
+ String refId = params.getRefId();
|
|
|
+ String benchmarkId = params.getBenchmarkId();
|
|
|
+ List<ManualFundManagerChangeDo> dataList = this.baseInfoService.listFundManagerChangeHistory(refId);
|
|
|
+
|
|
|
+ dataList = dataList.stream().filter(e -> e.getStartDate() != null).collect(Collectors.toList());
|
|
|
+ // 按基金经理+开始任职时间分组每个经理只取一条数据
|
|
|
+ Map<String, ManualFundManagerChangeDo> managerInfoMap = MapUtil.newHashMap();
|
|
|
+ Map<String, List<ManualFundManagerChangeDo>> collect = dataList.stream().collect(Collectors.groupingBy(e -> e.getManagerId() + e.getStartDate(), Collectors.toList()));
|
|
|
+ collect.forEach((k, v) -> {
|
|
|
+ ManualFundManagerChangeDo temp = v.stream().findFirst().orElse(null);
|
|
|
+ managerInfoMap.put(k, temp);
|
|
|
+ });
|
|
|
+
|
|
|
+ List<ManualFundManagerChangeVO> resultList = ListUtil.list(true);
|
|
|
+ List<Indicator> indicatorList = ListUtil.of(Indicator.IntervalReturn, Indicator.AnnualReturn, Indicator.MaxDrawdown,
|
|
|
+ Indicator.SortinoRatio, Indicator.CalmarRatio);
|
|
|
+ managerInfoMap.forEach((k, data) -> {
|
|
|
+ ManualFundManagerChangeVO vo = new ManualFundManagerChangeVO();
|
|
|
+ vo.setManagerId(data.getManagerId());
|
|
|
+ vo.setManagerName(data.getManagerName());
|
|
|
+ // 任职日期和天数计算
|
|
|
+ vo.setEmployDate(data.getStartDate() + "~" + (CharSequenceUtil.isBlank(data.getEndDate()) ? "至今" : data.getEndDate()));
|
|
|
+ Date date = new Date();
|
|
|
+ if (CharSequenceUtil.isNotBlank(data.getEndDate())) {
|
|
|
+ date = DateUtil.parseDate(data.getEndDate());
|
|
|
+ }
|
|
|
+ vo.setEmployDayNum(DateUtil.between(DateUtil.parseDate(data.getStartDate()), date, DateUnit.DAY) + "");
|
|
|
+ // 指标计算
|
|
|
+ Map<String, Map<String, String>> indicatorValue =
|
|
|
+ this.getIndicatorValue(refId, benchmarkId, data.getStartDate(), data.getEndDate(), indicatorList);
|
|
|
+ if (indicatorValue.get(refId) != null) {
|
|
|
+ vo.setFundRet(indicatorValue.get(refId).get(Indicator.IntervalReturn.name()));
|
|
|
+ vo.setAnnualRet(indicatorValue.get(refId).get(Indicator.AnnualReturn.name()));
|
|
|
+ vo.setMaxDrawdown(indicatorValue.get(refId).get(Indicator.MaxDrawdown.name()));
|
|
|
+ vo.setCalmar(indicatorValue.get(refId).get(Indicator.CalmarRatio.name()));
|
|
|
+ vo.setSortino(indicatorValue.get(refId).get(Indicator.SortinoRatio.name()));
|
|
|
+ }
|
|
|
+ resultList.add(vo);
|
|
|
+ });
|
|
|
+ return resultList;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算公募基金和其基准区间内的指标
|
|
|
+ *
|
|
|
+ * @param refId 公募基金
|
|
|
+ * @param benchmarkId 基准
|
|
|
+ * @param startDate 开始日期
|
|
|
+ * @param endDate 结束日期
|
|
|
+ * @param indicatorList 计算指标
|
|
|
+ * @return /
|
|
|
+ */
|
|
|
+ private Map<String, Map<String, String>> getIndicatorValue(String refId, String benchmarkId, String startDate, String endDate, List<Indicator> indicatorList) {
|
|
|
+ DateIntervalDto dateIntervalDto = DateIntervalDto.builder()
|
|
|
+ .id(DateIntervalType.CustomInterval.name()).startDate(startDate).endDate(endDate)
|
|
|
+ .dateIntervalType(DateIntervalType.CustomInterval).frequency(Frequency.Default).build();
|
|
|
+ Map<String, List<DateIntervalDto>> dateIntervalMap = MapUtil.builder(refId, ListUtil.of(dateIntervalDto)).build();
|
|
|
+ Map<String, String> benchmarkIdMap = MapUtil.builder(refId, benchmarkId).build();
|
|
|
+ CalcMultipleSecMultipleTimeRangeIndicatorReq req = CalcMultipleSecMultipleTimeRangeIndicatorReq.builder()
|
|
|
+ .mainSecIdList(ListUtil.of(refId))
|
|
|
+ .secBenchmarkIdMap(benchmarkIdMap)
|
|
|
+ .indexIdList(ListUtil.of(benchmarkId))
|
|
|
+ .raiseType(RaiseType.Both)
|
|
|
+ .strategy(Strategy.All)
|
|
|
+ .visibility(Visibility.Both)
|
|
|
+ .dataFrequency(Frequency.Default)
|
|
|
+ .navType(NavType.CumulativeNav)
|
|
|
+ .secDateIntervalDtoListMap(dateIntervalMap)
|
|
|
+ .indicatorList(indicatorList)
|
|
|
+ .geoExtraindicatorList(ListUtil.empty())
|
|
|
+ .ifAnnualize(true)
|
|
|
+ .riskOfFreeId(Consts.RISK_OF_FREE)
|
|
|
+ .calcIndexRetIndicatorValue(true)
|
|
|
+ .ifConvertPerformanceConsistencyWord(true).build();
|
|
|
+ Map<String, List<IndicatorCalcPropertyDto>> indicatorValueMap = this.baseIndicatorServiceV2.calcMultipleSecMultipleTimeRangeIndicator(req);
|
|
|
+
|
|
|
+ // DateIntervalType.CustomInterval 类型下每个标的只会返回一个维度下的指标计算结果,所以只获取第一条记录的指标计算结果
|
|
|
+ Map<String, Map<String, String>> result = MapUtil.newHashMap();
|
|
|
+ Map<String, String> valueMap = Optional.ofNullable(indicatorValueMap).map(e -> e.get(refId)).orElse(ListUtil.empty()).stream().findFirst()
|
|
|
+ .map(IndicatorCalcPropertyDto::getSecData).map(IndicatorCalcSecDataDto::getIndicatorValueMap).orElse(MapUtil.empty());
|
|
|
+ Map<String, String> valueBenchmarkMap = Optional.ofNullable(indicatorValueMap).map(e -> e.get(refId)).orElse(ListUtil.empty()).stream().findFirst()
|
|
|
+ .map(IndicatorCalcPropertyDto::getIndexData).map(IndicatorCalcIndexDataDto::getIndexIndicatorValueMap).map(e -> e.get(benchmarkId)).orElse(MapUtil.empty());
|
|
|
+ result.put(refId, valueMap);
|
|
|
+ result.put(benchmarkId, valueBenchmarkMap);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
}
|