package com.simuwang.base.common.util; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import com.simuwang.base.common.conts.AccountingItems; import com.simuwang.base.common.conts.AccountingNewItems; import com.simuwang.base.common.conts.HoldingType; import com.simuwang.base.common.enums.MarketTradIngCodeEnum; import com.simuwang.base.pojo.dos.FundPositionDetailDO; import com.simuwang.base.pojo.dos.ValuationTableDO; import com.simuwang.base.pojo.valuation.AssetsValuationInfo; import com.simuwang.base.pojo.valuation.CmValuationTableAttribute; import com.smppw.utils.BigDecimalUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.math.BigDecimal; import java.util.*; import java.util.stream.Collectors; public final class ValuationTableParseUtil { private static final Logger log = LoggerFactory.getLogger(ValuationTableParseUtil.class); /** * 多头 */ public final static Integer BULL_LONG_SHORT = 1; /** * 空头 */ public final static Integer BEAR_LONG_SHORT = 2; private final static Map CURRENCY_CODE_MAP = MapUtil.newHashMap(); static { // 1-人民币,2-美元,3-港元 CURRENCY_CODE_MAP.put("CNY", 1); CURRENCY_CODE_MAP.put("USD", 2); CURRENCY_CODE_MAP.put("HKD", 3); } /** * 特殊连接符(用户证券名称的识别剔除) */ private final static List SPECIAL_CODE = CollUtil.toList("-", "_", "."); public static List parseDataNew(List attrList, ValuationTableDO file, List marketCodeList) { // 汇总类数据map Map tailCode = new HashMap<>(); // 区分"科目代码"列为明细类和汇总类数据 boolean isFooterRow; // 返回结果(持仓明细对象) List dataList = new ArrayList<>(); // 识别科目代码层级关系 handleSubjectCodeLevel(attrList); // 兼容存在重复的数据(兼容updateValuationPositionDetailType任务) Map originalCodePreOriginalCodeMap = attrList.stream().filter(e -> StrUtil.isNotBlank(e.getPreOriginalSubjectCode())) .collect(Collectors.toMap(CmValuationTableAttribute::getOriginalSubjectCode, CmValuationTableAttribute::getPreOriginalSubjectCode, (oldValue, newValue) -> newValue)); for (int i = 0; i < attrList.size(); i++) { CmValuationTableAttribute attr = attrList.get(i); if (attr.getSubjectCode() == null) { continue; } // 估值表解析到的数据 -> 持仓明细对象 FundPositionDetailDO data = toDetail(attr, file); // 判断数据为明细类 or 汇总类数据(给字段isFooterRow赋值) isFooterRow = dealDetailOrSummary(data); if (data.getSubjectCode() == null) { continue; } // 汇总类数据("基金投资合计","实收资本"等字段) if (isFooterRow) { handleSummaryData(data, tailCode); } // 持仓明细类数据 if (!isFooterRow) { // 从科目代码识别出明细证券代码securitiesCode String securitiesCode = dealSecuritiesCode(attr.getSubjectCode(), attr.getPreOriginalSubjectCode(), attr.getLevel()); data.setSecuritiesCode(securitiesCode); // 处理证券名称(剔除上一科目名称) String preOriginalSubjectCode = originalCodePreOriginalCodeMap.get(attr.getOriginalSubjectCode()); String preSecuritiesName = attrList.stream().filter(e -> e.getOriginalSubjectCode().equals(preOriginalSubjectCode)) .findFirst().map(CmValuationTableAttribute::getSubjectName).orElse(null); String securitiesName = handleSecuritiesName(data.getLevel(), data.getSecuritiesName(), preSecuritiesName); data.setSecuritiesName(securitiesName); // data.subjectCode字段保存估值表中的原始科目代码 data.setSubjectCode(attr.getOriginalSubjectCode()); // 资产类型,1资产类,2负债类,3共同类,4所有者权益类,6损益类(根据科目代码编码规则可得) data.setSubjectType(Integer.valueOf(data.getSubjectCode().substring(0, 1))); } data.setIsvalid(1); data.setCreateTime(new Date()); dataList.add(data); } // 推断持仓明细类型 dealAssetTypeNew(dataList, originalCodePreOriginalCodeMap, marketCodeList); return dataList; } /** * 初步处理证券名称(剔除上一科目名称) * 如:场外_已上市_开放式_货币_成本-平安财富宝货币A * 准确的证券名称应该为:平安财富宝货币A * * @param level 持仓明细层级 * @param originalSecuritiesName 当前明细的证券名称 * @param preSecuritiesName 上一级的证券名称 * @return 处理后的证券名称 */ private static String handleSecuritiesName(Integer level, String originalSecuritiesName, String preSecuritiesName) { if (StrUtil.isBlank(preSecuritiesName) || StrUtil.isBlank(originalSecuritiesName) || !originalSecuritiesName.contains(preSecuritiesName) || level == null || level < 4) { return originalSecuritiesName; } String securitiesName = originalSecuritiesName.replace(preSecuritiesName, ""); for (String specialCode : SPECIAL_CODE) { if (securitiesName.startsWith(specialCode)) { securitiesName = securitiesName.substring(1); } } return securitiesName; } /** * 获取明细证券代码 * * @param subjectCode 估值表科目代码(去掉".","_") * @param preOriginalSubjectCode 上一级科目代码 * @param level 层级 * @return 明细证券代码 */ private static String dealSecuritiesCode(String subjectCode, String preOriginalSubjectCode, Integer level) { String securitiesCode = null; // 一级科目的证券代码 等于 估值表原始科目代码 if (level == null) { return null; } if (level == 1) { securitiesCode = subjectCode; return securitiesCode; } // 二级或三级科目的证券代码为空 if (level == 2 || level == 3) { return null; } if (StrUtil.isNotBlank(preOriginalSubjectCode) && level >= 4) { String preSubjectCode = preOriginalSubjectCode.replaceAll("[\\.\\s_-]", ""); securitiesCode = subjectCode.substring(preSubjectCode.length()); } return securitiesCode; } private static void handleSummaryData(FundPositionDetailDO data, Map tailCode) { data.setSecType(100); String subjectName = data.getSecuritiesName(); if (StringUtils.isNotEmpty(subjectName)) { if (subjectName.startsWith("-") || Character.isDigit(subjectName.toCharArray()[0])) { //如果科目名称是数字 data.setSecuritiesAmount(BigDecimalUtils.toBigDecimal((subjectName.replace("%", "").replace(",", "")))); data.setSecuritiesName(data.getSubjectCode()); } else { data.setSecuritiesName(data.getSubjectCode() + subjectName); } } else { data.setSecuritiesName(data.getSubjectCode()); } String subjectCode = getTypeNo(data.getSubjectCode()); if (subjectCode == null) { subjectCode = "1000"; } else { if (tailCode.containsKey(subjectCode)) { int num = tailCode.get(subjectCode); tailCode.put(subjectCode, num + 1); subjectCode = subjectCode + ":" + num; } else { tailCode.put(subjectCode, 1); } } data.setSecuritiesCode(subjectCode); data.setSubjectCode(subjectCode); data.setLevel(0); } private static boolean dealDetailOrSummary(FundPositionDetailDO data) { boolean isFooterRow = false; if (!Character.isDigit(data.getSubjectCode().toCharArray()[0])) { isFooterRow = true; } if (!isFooterRow && ValuationDataUtils.hasChinese(data.getSubjectCode(), false)) { String code = data.getSubjectCode(); if (data.getSecuritiesName() != null) { code = code.replace(data.getSecuritiesName(), ""); } if (ValuationDataUtils.hasChinese(code, false)) { int endIndex = getSubjectCodeEndIndex(data.getSubjectCode()); String subCode = code.substring(0, endIndex); if (subCode.length() < 4 && data.getSecuritiesName() == null) { isFooterRow = true; } else { data.setSubjectCode(subCode); if (data.getSecuritiesName() == null) { data.setSecuritiesName(code.substring(endIndex)); } } } else { data.setSubjectCode(code); } } return isFooterRow; } /** * 识别科目代码的层级 * * @param valuationDataList 估值表解析到的数据 */ private static void handleSubjectCodeLevel(List valuationDataList) { // 先过滤出一级科目(一级科目长度一定是4位) List levelOneCodeList = valuationDataList.stream() .filter(e -> StrUtil.isNotBlank(e.getOriginalSubjectCode()) && e.getOriginalSubjectCode().length() == 4 && StrUtil.isNumeric(e.getOriginalSubjectCode())) .sorted(Comparator.comparing(CmValuationTableAttribute::getOriginalSubjectCode)).collect(Collectors.toList()); if (CollUtil.isEmpty(levelOneCodeList)) { return; } levelOneCodeList.forEach(levelOneCodeData -> { String parentCode = levelOneCodeData.getOriginalSubjectCode(); levelOneCodeData.setLevel(1); List subSubjectList = valuationDataList.stream() .filter(e -> StrUtil.isNotBlank(e.getOriginalSubjectCode()) && e.getOriginalSubjectCode().startsWith(parentCode) && e.getLevel() == null) .sorted(Comparator.comparing(e -> e.getOriginalSubjectCode().length())).collect(Collectors.toList()); handSubSubjectLevel(subSubjectList, parentCode, 1); }); } /** * 递归找出子类科目的层级 * * @param subjectList 子类科目数据 * @param parentLevel 父类层级 */ private static void handSubSubjectLevel(List subjectList, String parentCode, Integer parentLevel) { if (CollUtil.isEmpty(subjectList)) { return; } subjectList.forEach(e -> e.setPreOriginalSubjectCode(parentCode)); for (CmValuationTableAttribute parentData : subjectList) { if (parentData.getLevel() != null) { continue; } String subParentCode = parentData.getOriginalSubjectCode(); parentData.setLevel(parentLevel + 1); parentData.setPreOriginalSubjectCode(parentCode); List subSubjectList = subjectList.stream() .filter(e -> StrUtil.isNotBlank(e.getOriginalSubjectCode()) && e.getOriginalSubjectCode().startsWith(subParentCode) && !e.getOriginalSubjectCode().equals(subParentCode) && e.getLevel() == null) .sorted(Comparator.comparing(e -> e.getOriginalSubjectCode().length())).collect(Collectors.toList()); handSubSubjectLevel(subSubjectList, subParentCode, parentLevel + 1); } } private static FundPositionDetailDO toDetail(CmValuationTableAttribute attr, ValuationTableDO userValuationTableDo) { FundPositionDetailDO detail = new FundPositionDetailDO(); detail.setFundId(userValuationTableDo.getFundId()); detail.setValuationDate(userValuationTableDo.getValuationDate()); detail.setCurrency(getCurrencyCode(attr.getCurrency())); detail.setExchangeRate(attr.getExchangeRate()); detail.setSecuritiesName(attr.getSubjectName()); detail.setSecuritiesAmount(attr.getSecuritiesAmount()); detail.setMarketValue(attr.getMarketValue()); // [权重](市值占净值比)如果不能从估值表中读取时,市值占净值比=市值/资产净值 BigDecimal marketValueRatio = attr.getMarketValueRatio(); if (marketValueRatio == null || marketValueRatio.compareTo(BigDecimal.ZERO) == 0) { BigDecimal netAssetMarketValue = userValuationTableDo.getNetAssetsValue(); if (attr.getMarketValue() != null && netAssetMarketValue != null && netAssetMarketValue.compareTo(BigDecimal.ZERO) != 0) { marketValueRatio = BigDecimalUtils.divide(attr.getMarketValue(), netAssetMarketValue); } } detail.setMarketValueRatio(marketValueRatio); detail.setNetCost(attr.getNetCost()); detail.setNetCostRatio(attr.getNetCostRatio()); BigDecimal increment = attr.getIncrement(); detail.setIncrement(increment); // [估值增值]如果不能从估值表中读取时,估值增值=市值-成本 if (increment != null && increment.compareTo(BigDecimal.ZERO) == 0) { increment = BigDecimalUtils.subtract(attr.getMarketValue(), attr.getNetCost()); } detail.setIncrement(increment); detail.setHaltInfo(attr.getHaltInfo()); detail.setNature(1);// 默认为多 detail.setSubjectCode(attr.getSubjectCode()); detail.setLevel(attr.getLevel()); detail.setMarketPrice(attr.getMarketPrice()); detail.setUnitCost(attr.getUnitCost()); return detail; } private static Integer getCurrencyCode(String currency) { if (StrUtil.isBlank(currency)) { return null; } for (Map.Entry entry : CURRENCY_CODE_MAP.entrySet()) { if (entry.getKey().equals(currency)) { return entry.getValue(); } } return null; } private static String getTypeNo(String name) { if (name.contains("合计") && name.contains("证券")) { return "100"; } if (name.startsWith("其中")) { if (name.contains("股票")) { return "101"; } if (name.contains("债券")) { return "102"; } if (name.contains("基金")) { return "103"; } if (name.contains("权证")) { return "104"; } if (name.contains("衍生")) { return "105"; } return null; } if (name.contains("头寸")) { return "106"; } boolean isPaidInCapital = isPaidInCapital(name); if (isPaidInCapital) { return "107"; } if (name.contains("损益平准金")) { if (name.contains("未")) { return "109"; } return "108"; } if (name.contains("合计") && name.contains("资产")) { return "110"; } if (name.contains("合计") && name.contains("负债")) { return "111"; } if (name.contains("资产") && name.contains("净值")) { return "112"; } if (name.contains("单位") && name.contains("净值")) { String trimName = name.trim(); if (trimName.length() == 5) { // 去掉末尾的符号 trimName = trimName.substring(0, 4); } if ("单位净值".equals(trimName) || name.contains("今日") || name.contains("基金")) { return "201"; } if (name.contains("累计")) { return "202"; } if (name.contains("期初")) { return "203"; } if (name.contains("昨日")) { return "204"; } if (name.contains("年初")) { return "205"; } if (name.contains("期初")) { return "206"; } if (name.contains("周初")) { return "207"; } if (name.contains("月初")) { return "208"; } return null; } if (name.contains("可分配") && (name.contains("收益") || name.contains("利润"))) { if (name.contains("单位")) { return "212"; } return "213"; } if (name.contains("现金类占")) { return "210"; } if (name.contains("累计派现")) { return "211"; } return null; } private static boolean isPaidInCapital(String name) { return name.contains("实收") && name.contains("资本") || name.contains("实收") && name.contains("信托"); } /** * 识别证券类型,多头空头,交易场所 * * @param dataList 估值表数据对象 * @param originalCodePreOriginalCodeMap 当前科目代码 -上一级科目代码映射关系 * @param marketCodeList 交易场所code列表 */ public static void dealAssetTypeNew(List dataList, Map originalCodePreOriginalCodeMap, List marketCodeList) { if (CollUtil.isEmpty(dataList)) { return; } Map securitiesCodeNameMap = MapUtil.newHashMap(); // 查询交易场所code for (int i = 0; i < dataList.size(); i++) { FundPositionDetailDO fundPositionDetail = dataList.get(i); // 汇总类数据("基金投资合计","实收资本"等字段)不需要处理 if (fundPositionDetail.getSecType() != null && fundPositionDetail.getSecType() == 100) { continue; } // 获取二级、三级、四级科目名称 List securitiesNameList = CollUtil.toList("", "", ""); getPreSecuritiesName(fundPositionDetail, dataList, originalCodePreOriginalCodeMap, securitiesNameList); String secondSecuritiesName = securitiesNameList.get(0); String thirdSecuritiesName = securitiesNameList.get(1); String fourSecuritiesName = fundPositionDetail.getLevel() != null && fundPositionDetail.getLevel() > 4 ? securitiesNameList.get(2) : ""; // 标识证券类型 addStockTypeNew(fundPositionDetail, secondSecuritiesName, thirdSecuritiesName, fourSecuritiesName); // 识别空头多头(识别过程中已经默认为 1-多头) addLongShort(fundPositionDetail); // 空头类或正回购的市值入库时应该为负数 if (fundPositionDetail.getNature() == 2 && fundPositionDetail.getMarketValue() != null && fundPositionDetail.getMarketValue().compareTo(BigDecimal.ZERO) > 0 || (fundPositionDetail.getSubType() != null && fundPositionDetail.getSubType().equals(HoldingType.BondBackRepo.getId()))) { fundPositionDetail.setMarketValue(BigDecimalUtils.multiply(fundPositionDetail.getMarketValue(), BigDecimalUtils.toBigDecimal("-1"))); } // 识别交易场所 addMarketIdNew(fundPositionDetail, secondSecuritiesName + thirdSecuritiesName + fourSecuritiesName); // 识别证券代码(过滤掉交易场所code) filterSecuritiesCode(fundPositionDetail, marketCodeList); // 用于后面识别估值增值和应计利息子类 if (fundPositionDetail.getLevel() != null && fundPositionDetail.getLevel() >= 4) { securitiesCodeNameMap.put(fundPositionDetail.getSubjectCode(), securitiesNameList.get(1)); } } // 划分估值增值和应计利息明细数据的sub_type for (int i = 0; i < dataList.size(); i++) { FundPositionDetailDO info = dataList.get(i); Integer secType = info.getSecType(); if (secType == null || secType == 100 || (info.getLevel() != null && info.getLevel() == 0)) { continue; } // 二级,三级期货,期权,其他衍生品的子类 -> sub_type=20 if (info.getLevel() > 1 && info.getLevel() < 4) { if ((secType == HoldingType.Derivatives.getId() || secType == HoldingType.Future.getId() || secType == HoldingType.Option.getId())) { info.setSubType(HoldingType.CostCash.getId()); } } // 四级以上的明细,如果成本或数量为空或为0 if (info.getLevel() >= 4) { if (((info.getNetCost() == null || info.getNetCost().compareTo(BigDecimal.ZERO) == 0.0) || (info.getSecuritiesAmount() == null || info.getSecuritiesAmount().compareTo(BigDecimal.ZERO) == 0.0))) { String thirdSecuritiesName = securitiesCodeNameMap.get(info.getSubjectCode()); // 正逆回购(目前存在应计利息,不存在估值增值) if (info.getSecType() == HoldingType.BondBack.getId()) { if (StrUtil.isNotBlank(thirdSecuritiesName) && thirdSecuritiesName.contains("利息")) { info.setSubType(HoldingType.Interest.getId()); } continue; } // 其他衍生品不做这个限制 if (info.getSecType() == HoldingType.Derivatives.getId()) { continue; } // 三级科目名称包含"利息" -> sub_type=22 if (StrUtil.isNotBlank(thirdSecuritiesName) && thirdSecuritiesName.contains("利息")) { info.setSubType(HoldingType.Interest.getId()); continue; } info.setSubType(HoldingType.CostCash.getId()); } } } } /** * 识别证券代码 * * @param fundPositionDetail 持仓明细数据 * @param marketCodeList 交易场所code列表 */ public static void filterSecuritiesCode(FundPositionDetailDO fundPositionDetail, List marketCodeList) { if (fundPositionDetail.getLevel() == null || fundPositionDetail.getLevel() < 4) { return; } // 识别证券代码 String securitiesCode = getSecuritiesCode(fundPositionDetail.getSecuritiesCode(), marketCodeList); fundPositionDetail.setSecuritiesCode(securitiesCode); } /** * 识别证券代码(去掉交易场所code) * * @param originalSecuritiesCode 原始证券代码 * @param marketCodeList 交易场所code列表 * @return 去掉交易场所code的证券代码 */ private static String getSecuritiesCode(String originalSecuritiesCode, List marketCodeList) { String securitiesCode = originalSecuritiesCode; if (StrUtil.isBlank(originalSecuritiesCode)) { return securitiesCode; } for (String marketCode : marketCodeList) { if (originalSecuritiesCode.endsWith(marketCode)) { int length = originalSecuritiesCode.length(); if (length > marketCode.length()) { securitiesCode = originalSecuritiesCode.substring(0, length - marketCode.length()); break; } } } return securitiesCode; } /** * 标识证券类型 * * @param info 持仓明细对象 * @param secondSecuritiesName 二级证券名称 * @param thirdSecuritiesName 三级证券名称 * @param fourSecuritiesName 四级证券名称 */ public static void addStockTypeNew(FundPositionDetailDO info, String secondSecuritiesName, String thirdSecuritiesName, String fourSecuritiesName) { // 无证券名称或市值的数据不识别证券类型 if (info.getSecuritiesName() == null || info.getMarketValue() == null) { return; } Integer level = info.getLevel(); String subjectCode = info.getSubjectCode(); // 一.现金类 if (startWith(AccountingNewItems.CASH, subjectCode)) { info.setSecType(HoldingType.Cash.getId()); info.setSubType(HoldingType.Cash.getId()); return; } // 二.股票类 if (startWith(AccountingNewItems.EQUITY_LIST, subjectCode)) { // 1."1102"开头 if (subjectCode.startsWith("1102") || level < 4) { info.setSecType(HoldingType.Stock.getId()); Integer subType = dealStockSubType(secondSecuritiesName, thirdSecuritiesName); info.setSubType(subType); return; } // 2."2101"开头 并且 二级或三级或四级科目有"存托凭证"or"股票"的 四级及以上科目 boolean is2101StockType = subjectCode.startsWith("2101") && (secondSecuritiesName.contains("存托凭证") || secondSecuritiesName.contains("股票") || thirdSecuritiesName.contains("存托凭证") || thirdSecuritiesName.contains("股票") || fourSecuritiesName.contains("存托凭证") || fourSecuritiesName.contains("股票")); if (is2101StockType) { info.setSecType(HoldingType.Stock.getId()); Integer subType = dealStockSubType(secondSecuritiesName, thirdSecuritiesName); info.setSubType(subType); return; } } // 3."1101"开头并且层级 >=4 并且二级科目或三级或四级科目含有"股票" boolean is1101StockType = subjectCode.startsWith("1101") && level >= 4 && (secondSecuritiesName.contains("股票") || thirdSecuritiesName.contains("股票") || fourSecuritiesName.contains("股票")); if (is1101StockType) { info.setSecType(HoldingType.Stock.getId()); Integer subType = dealStockSubType(secondSecuritiesName, thirdSecuritiesName); info.setSubType(subType); return; } // 三.债券类 if (startWith(AccountingNewItems.BOND_LIST, subjectCode)) { // 1."1103","1104"开头 if (subjectCode.startsWith("1103") || subjectCode.startsWith("1104") || level < 4) { info.setSecType(HoldingType.Bond.getId()); info.setSubType(HoldingType.Bond.getId()); return; } // 2.子类"正逆回购" -> 证券名称以"GC"开头 或 证券名称包含"-0" if (info.getSecuritiesName().startsWith("GC") || info.getSecuritiesName().contains("-0")) { info.setSecType(HoldingType.Bond.getId()); info.setSubType(HoldingType.BondBack.getId()); return; } } // 3."2101"开头并且层级 >=4 并且二级或三级或四级科目含有"债券" boolean is2101BondType = subjectCode.startsWith("2101") && level >= 4 && (secondSecuritiesName.contains("债券") || thirdSecuritiesName.contains("债券") || fourSecuritiesName.contains("债券")); if (is2101BondType) { info.setSecType(HoldingType.Bond.getId()); info.setSubType(HoldingType.Bond.getId()); return; } // 4."1101"开头并且层级 >=4 并且二级或三级或四级科目含有"债券"或"债" boolean is1101BondType = subjectCode.startsWith("1101") && level >= 4 && (secondSecuritiesName.contains("债券") || secondSecuritiesName.contains("债") || thirdSecuritiesName.contains("债券") || thirdSecuritiesName.contains("债") || fourSecuritiesName.contains("债券") || fourSecuritiesName.contains("债")); if (is1101BondType) { info.setSecType(HoldingType.Bond.getId()); info.setSubType(HoldingType.Bond.getId()); return; } // 5.1108开头并且层级 >=4 并且二级或三级或四级科目带有“债” boolean is1108BondType = subjectCode.startsWith("1108") && level >= 4 && (secondSecuritiesName.contains("债") || thirdSecuritiesName.contains("债") || fourSecuritiesName.contains("债")); if (is1108BondType) { info.setSecType(HoldingType.Bond.getId()); info.setSubType(HoldingType.Bond.getId()); return; } // 正逆回购类 if (startWith(AccountingNewItems.BOND_BACK_LIST, subjectCode)) { info.setSecType(HoldingType.BondBack.getId()); // 2202-正回购 if (subjectCode.startsWith("2202")) { info.setSubType(HoldingType.BondBackRepo.getId()); } // 1202-逆回购 if (subjectCode.startsWith("1202")) { info.setSubType(HoldingType.BondBackReverseRepo.getId()); } return; } // 四.基金类 if (startWith(AccountingNewItems.FUND_LIST, subjectCode)) { // "1108"开头并且非“债券类型”才归为基金(先识别债券再识别基金) // 1."1105","1107","1109","1110"开头 info.setSecType(HoldingType.Fund.getId()); info.setSubType(HoldingType.Fund.getId()); return; } // 2."1101"开头并且层级<4 或 "1101"开头并且二级或三级或四级科目含有"基金"或"理财" boolean is1101FundType = subjectCode.startsWith("1101") && level < 4 || (subjectCode.startsWith("1101") && (secondSecuritiesName.contains("基金") || secondSecuritiesName.contains("理财") || thirdSecuritiesName.contains("基金") || thirdSecuritiesName.contains("理财") || fourSecuritiesName.contains("基金") || fourSecuritiesName.contains("理财"))); if (is1101FundType) { info.setSecType(HoldingType.Fund.getId()); info.setSubType(HoldingType.Fund.getId()); return; } // 3.2101开头并且层级>=4,二级或三级科目名称包含“ETF” boolean is2101FundType = subjectCode.startsWith("2101") && level >= 4 && (secondSecuritiesName.contains("ETF") || thirdSecuritiesName.contains("ETF") || fourSecuritiesName.contains("ETF")); if (is2101FundType) { info.setSecType(HoldingType.Fund.getId()); info.setSubType(HoldingType.Fund.getId()); return; } // 五.期货、期权类 if (startWith(AccountingNewItems.DERIVATIVES, subjectCode)) { // 其他衍生品 boolean isDerivatives = secondSecuritiesName.contains("收益互换") || thirdSecuritiesName.contains("收益互换") || fourSecuritiesName.contains("收益互换"); boolean isOptionType = secondSecuritiesName.contains("期权") || thirdSecuritiesName.contains("期权") || fourSecuritiesName.contains("期权"); if (level < 4) { // 层级小于4,并且二级或三级科目包含“期权” -> 期权 if (isOptionType) { info.setSecType(HoldingType.Option.getId()); info.setSubType(HoldingType.Option.getId()); return; } // 层级小于4,并且非期权非其他衍生品 -> 期货 if (!isDerivatives) { info.setSecType(HoldingType.Future.getId()); info.setSubType(HoldingType.Future.getId()); return; } // 层级小于4,并且二级或三级科目包含“收益互换” -> 其他衍生品 info.setSecType(HoldingType.Derivatives.getId()); info.setSubType(HoldingType.Derivatives.getId()); return; } // 标识子类类型 String securitiesName = info.getSecuritiesName(); if (isOptionType) { info.setSecType(HoldingType.Option.getId()); // 期权通用判断 boolean isCmdtOption = securitiesName.contains("购") || securitiesName.contains("沽") || securitiesName.contains("C") || securitiesName.contains("P"); // ETF期权 boolean isETFOption = (securitiesName.contains("ETF") || securitiesName.contains("科创50") || securitiesName.contains("科创板50")) && isCmdtOption; // 股指期权 boolean isIndexOption = (securitiesName.contains("上证50") || securitiesName.contains("沪深300") || securitiesName.contains("中证1000")) && isCmdtOption; if (isETFOption) { info.setSubType(HoldingType.ETFOption.getId()); return; } if (isIndexOption) { info.setSubType(HoldingType.IndexOption.getId()); return; } if (isCmdtOption) { info.setSubType(HoldingType.CmdtOption.getId()); return; } // 归为其他期权 info.setSubType(HoldingType.Other.getId()); return; } // 非期权和非其他衍生品 -> 期货类 if (!isDerivatives) { String stockCode = info.getSecuritiesCode(); info.setSecuritiesCode(stockCode); info.setSecType(HoldingType.Future.getId()); if (startWith(AccountingNewItems.INDEX_FUTRUE, stockCode)) { // 股指期货 info.setSubType(HoldingType.IndexFuture.getId()); return; } if (startWith(AccountingItems.BOND_FUTRUE, stockCode) || info.getSecuritiesName().contains("国债")) { if (stockCode.startsWith("TA")) { // 国债期货(T),商品期货(TA)存在冲突,先识别商品期货,再识别国债期货 info.setSubType(HoldingType.CmdtFuture.getId()); return; } // 国债期货 info.setSubType(HoldingType.BondFuture.getId()); return; } if (startWith(AccountingItems.CMDT_FUTRUE, stockCode)) { // 商品期货 info.setSubType(HoldingType.CmdtFuture.getId()); return; } // 归为其他期货 info.setSubType(HoldingType.Other.getId()); return; } // 其他衍生品 info.setSecType(HoldingType.Derivatives.getId()); // 二三四级科目是否包含"冲抵" -> 冲抵现金 if (secondSecuritiesName.contains("冲抵") || thirdSecuritiesName.contains("冲抵") || fourSecuritiesName.contains("冲抵")) { info.setSubType(HoldingType.CostCash.getId()); } else { info.setSubType(HoldingType.Derivatives.getId()); } return; } // 以3199开头 -> 其他衍生品 if (subjectCode.startsWith("3199")) { info.setSecType(HoldingType.Derivatives.getId()); info.setSubType(HoldingType.Derivatives.getId()); return; } // 七.其他类 if (startWith(AccountingNewItems.OTHER, subjectCode)) { info.setSecType(HoldingType.Other.getId()); info.setSubType(HoldingType.Other.getId()); return; } // 其他资产类 if (startWith(AccountingNewItems.OTHER_ASSET, subjectCode)) { info.setSecType(HoldingType.Other.getId()); info.setSubType(HoldingType.OTHER_ASSET.getId()); return; } // 其他负债类 if (startWith(AccountingNewItems.OTHER_DEBT, subjectCode)) { info.setSecType(HoldingType.Other.getId()); info.setSubType(HoldingType.OTHER_DEBT.getId()); return; } // 八.兜底策略 if (info.getSecType() == null && StrUtil.isNotBlank(secondSecuritiesName) && StrUtil.isNotBlank(thirdSecuritiesName)) { if (secondSecuritiesName.contains("资产") || thirdSecuritiesName.contains("资产")) { info.setSecType(HoldingType.Cash.getId()); info.setSubType(HoldingType.Cash.getId()); return; } if (secondSecuritiesName.contains("股票") || thirdSecuritiesName.contains("股")) { info.setSecType(HoldingType.Stock.getId()); info.setSubType(HoldingType.Stock.getId()); return; } if (secondSecuritiesName.contains("基金") || thirdSecuritiesName.contains("基金")) { info.setSecType(HoldingType.Other.getId()); info.setSubType(HoldingType.Other.getId()); return; } if ((secondSecuritiesName.contains("理财") || thirdSecuritiesName.contains("理财")) && (info.getSubjectCode().startsWith("1101.08") || info.getSubjectCode().startsWith("1101.09"))) { info.setSecType(HoldingType.Other.getId()); info.setSubType(HoldingType.Other.getId()); return; } if (secondSecuritiesName.contains("债") || thirdSecuritiesName.contains("债")) { if (!secondSecuritiesName.contains("负债") && !thirdSecuritiesName.contains("负债")) { info.setSecType(HoldingType.Bond.getId()); info.setSubType(HoldingType.Bond.getId()); } else { info.setSecType(HoldingType.Other.getId()); info.setSubType(HoldingType.Other.getId()); } } } } /** * 股票类子类识别:沪港通,深港通,港股通 * * @param secondSecuritiesName 二级证券名称 * @param thirdSecuritiesName 三级证券名称 * @return 股票子类 */ private static Integer dealStockSubType(String secondSecuritiesName, String thirdSecuritiesName) { if (secondSecuritiesName.contains("沪港通") || thirdSecuritiesName.contains("沪港通")) { return HoldingType.StockSHH.getId(); } if ((secondSecuritiesName.contains("深港通") || thirdSecuritiesName.contains("深港通"))) { return HoldingType.StockSZH.getId(); } if ((secondSecuritiesName.contains("港股通") || thirdSecuritiesName.contains("港股通"))) { return HoldingType.StockHGT.getId(); } return HoldingType.Stock.getId(); } /** * 多头、空头识别 * * @param fundPositionDetail 持仓明细对象 */ public static void addLongShort(FundPositionDetailDO fundPositionDetail) { Integer secType = fundPositionDetail.getSecType(); if (secType == null) { return; } String subjectCode = fundPositionDetail.getSubjectCode(); // 股票类:归属于1102,1101下的股票为多头;归属于2101下的股票为空头 if (secType == HoldingType.Stock.getId()) { if (subjectCode.startsWith("1102") || subjectCode.startsWith("1101")) { fundPositionDetail.setNature(BULL_LONG_SHORT); return; } if (subjectCode.startsWith("2101")) { fundPositionDetail.setNature(BEAR_LONG_SHORT); return; } } // 债券类:归属于1101,1103,1104下的债券为多头;归属于2101下的债券为空头 if (secType == HoldingType.Bond.getId()) { if (subjectCode.startsWith("110") || subjectCode.startsWith("1102") || subjectCode.startsWith("1104")) { fundPositionDetail.setNature(BULL_LONG_SHORT); return; } if (subjectCode.startsWith("2101")) { fundPositionDetail.setNature(BEAR_LONG_SHORT); return; } } // 期货,期权,衍生品类 if (secType == HoldingType.Future.getId() || secType == HoldingType.Option.getId() || secType == HoldingType.Derivatives.getId()) { BigDecimal netCost = fundPositionDetail.getNetCost(); Integer longShort = inferFutureOptionLongShort(netCost, fundPositionDetail.getLevel(), fundPositionDetail.getMarketValue()); fundPositionDetail.setNature(longShort); } // 正回购 -> 空头 if (secType == HoldingType.Bond.getId() && fundPositionDetail.getSubjectCode().startsWith("2202")) { fundPositionDetail.setNature(BEAR_LONG_SHORT); } // 2101下的基金 -> 空头 if (secType == HoldingType.Fund.getId() && fundPositionDetail.getSubjectCode().startsWith("2101")) { fundPositionDetail.setNature(BEAR_LONG_SHORT); } // 其他资产 -> 多头, 其他负债 -> 空头 if (secType == HoldingType.Other.getId() && fundPositionDetail.getSubType() != null && fundPositionDetail.getSubType().equals(HoldingType.OTHER_DEBT.getId())) { fundPositionDetail.setNature(BEAR_LONG_SHORT); } } /** * 获取持仓明细的二、三级、四级证券名称 * * @param fundPositionDetail 持仓明细 * @param dataList 估值表持仓明细数据 * @param originalCodePreOriginalCodeMap 科目代码-上一级科目代码映射关系 * @param securitiesNameList * @return 持仓明细的二、三级、四级证券名称 */ private static void getPreSecuritiesName(FundPositionDetailDO fundPositionDetail, List dataList, Map originalCodePreOriginalCodeMap, List securitiesNameList) { Integer level = fundPositionDetail.getLevel(); String preOriginalCode = originalCodePreOriginalCodeMap.get(fundPositionDetail.getSubjectCode()); if (StrUtil.isBlank(preOriginalCode) || level == null || level == 1) { return; } if (level == 2) { securitiesNameList.set(0, fundPositionDetail.getSecuritiesName()); } if (level == 3) { String secondSecuritiesName = dataList.stream().filter(e -> e.getSubjectCode().equals(preOriginalCode)) .findFirst().map(FundPositionDetailDO::getSecuritiesName).orElse(""); securitiesNameList.set(0, secondSecuritiesName); securitiesNameList.set(1, fundPositionDetail.getSecuritiesName()); } if (level == 4) { securitiesNameList.set(2, fundPositionDetail.getSecuritiesName()); } if (level >= 4) { FundPositionDetailDO preFundPositionDetail = dataList.stream().filter(e -> e.getSubjectCode().equals(preOriginalCode)) .findFirst().orElse(null); if (preFundPositionDetail != null) { getPreSecuritiesName(preFundPositionDetail, dataList, originalCodePreOriginalCodeMap, securitiesNameList); } } } /** * 识别交易场所 * MarketTradIngCodeEnum * * @param info 持仓明细对象数据 * @param marketName 二级、三级证券名称 */ public static void addMarketIdNew(FundPositionDetailDO info, String marketName) { if (info.getSecType() == null) { return; } // 股票交易场所 if (info.getSecType() == HoldingType.Stock.getId()) { addStockMarketId(info, marketName); } // 债券交易场所 if (info.getSecType() == HoldingType.Bond.getId() || info.getSecType() == HoldingType.BondBack.getId()) { addBondMarketId(info, marketName); } // 期货、期权交易场所 if (info.getSecType() == HoldingType.Future.getId() || info.getSecType() == HoldingType.Option.getId()) { addFutureOptionMarketId(info, marketName); } } private static void addBondMarketId(FundPositionDetailDO info, String marketName) { if (marketName.contains("上交") || marketName.contains("上海")) { // 二级或三级科目包含"上交"或"上海" -> 上交所 info.setMarketId(MarketTradIngCodeEnum.SSE.getMarketId()); return; } if (marketName.contains("深交") || marketName.contains("深圳")) { // 二级或三级科目包含"深交"或"深圳" -> 深交所 info.setMarketId(MarketTradIngCodeEnum.SZSE.getMarketId()); return; } if (marketName.contains("北交") || marketName.contains("北京")) { // 二级或三级科目包含“北交”或“北京”-> 北交所 info.setMarketId(MarketTradIngCodeEnum.NEEQ.getMarketId()); return; } if (marketName.contains("银行间")) { // 二级或三级科目包含“银行间”-> 银行间 info.setMarketId(MarketTradIngCodeEnum.IBM.getMarketId()); return; } // 兜底策略:交易场所为场外(OTC) if (info.getMarketId() == null) { info.setMarketId(MarketTradIngCodeEnum.OTC.getMarketId()); } } /** * 识别期货、期权交易场所 * * @param info 持仓明细对象数据 * @param marketName 二级、三级证券名称 */ private static void addFutureOptionMarketId(FundPositionDetailDO info, String marketName) { if (marketName.contains("上交") || (marketName.contains("上海") && !marketName.contains("上海能源") && !marketName.contains("上海商品") && !marketName.contains("上海期货"))) { // 二级或三级科目包含"上交"或"上海"并且不包含"上海能源"和"上海商品"和"上海期货" -> 上交所 info.setMarketId(MarketTradIngCodeEnum.SSE.getMarketId()); return; } if (marketName.contains("深交") || marketName.contains("深圳")) { // 二级或三级科目包含"深交"或"深圳" -> 深交所 info.setMarketId(MarketTradIngCodeEnum.SZSE.getMarketId()); return; } if (marketName.contains("中金所") && info.getSecType() != null && info.getSecType().equals(HoldingType.Future.getId())) { // 二级或三级科目包含"中金所" -> 中国金融期货交易所(期货-20,期权-51) info.setMarketId(MarketTradIngCodeEnum.CFFEX_FUTURE.getMarketId()); return; } if (marketName.contains("中金所") && info.getSecType() != null && info.getSecType().equals(HoldingType.Option.getId())) { // 二级或三级科目包含"中金所" -> 中国金融期货交易所(期货-20,期权-51) info.setMarketId(MarketTradIngCodeEnum.CFFEX_OPTION.getMarketId()); return; } if (marketName.contains("广州商品") || marketName.contains("广期所") || marketName.contains("广州期货")) { // 二级或三级科目包含"广州商品"或"广期所"或"广州期货”-> 广州期货交易所 info.setMarketId(MarketTradIngCodeEnum.GZFE.getMarketId()); return; } if (marketName.contains("郑州商品") || marketName.contains("郑商所") || marketName.contains("郑州期货")) { // 二级或三级科目包含"郑州商品"或"郑商所"或"郑州期货”-> 郑州期货交易所 info.setMarketId(MarketTradIngCodeEnum.CZC.getMarketId()); return; } if (marketName.contains("大连商品") || marketName.contains("大商所") || marketName.contains("大连期货")) { // 二级或三级科目包含"大连商品"或"大商所"或"大连期货”-> 大连期货交易所 info.setMarketId(MarketTradIngCodeEnum.DCE.getMarketId()); return; } if (marketName.contains("上海能源")) { // 二级或三级科目包含"上海能源" -> 上海国际能源交易中心 info.setMarketId(MarketTradIngCodeEnum.INE.getMarketId()); return; } if (marketName.contains("上海商品") || marketName.contains("上期所") || marketName.contains("上海期货")) { // 二级或三级科目包含"上海商品"或"上期所"或"上海期货”-> 上海期货交易所 info.setMarketId(MarketTradIngCodeEnum.SHFE.getMarketId()); return; } // 兜底策略:交易场所为场外(OTC) if (info.getMarketId() == null) { info.setMarketId(MarketTradIngCodeEnum.OTC.getMarketId()); } } /** * 识别股票交易场所 * * @param info 持仓明细对象数据 * @param marketName 二级、三级证券名称 */ private static void addStockMarketId(FundPositionDetailDO info, String marketName) { if (marketName.contains("场外") || marketName.contains("新三板")) { // 二级或三级科目包含“场外“或”新三板 -> 新三板 info.setMarketId(MarketTradIngCodeEnum.NTBC.getMarketId()); return; } if (marketName.contains("上交") || marketName.contains("上海")) { // 二级或三级科目包含"上交"或"上海" -> 上交所 info.setMarketId(MarketTradIngCodeEnum.SSE.getMarketId()); return; } if (marketName.contains("深交") || marketName.contains("深圳")) { // 二级或三级科目包含"深交"或"深圳" -> 深交所 info.setMarketId(MarketTradIngCodeEnum.SZSE.getMarketId()); return; } if (marketName.contains("北交") || marketName.contains("北京")) { // 二级或三级科目包含“北交”或“北京”-> 北交所 info.setMarketId(MarketTradIngCodeEnum.NEEQ.getMarketId()); return; } if (marketName.contains("港") || marketName.contains("港股通")) { // 二级或三级科目包含"港"或"港股通" -> 港交所 info.setMarketId(MarketTradIngCodeEnum.HKEX.getMarketId()); if (info.getSecuritiesCode() != null && info.getSecuritiesCode().startsWith("H")) { info.setSecuritiesCode(info.getSecuritiesCode().replace("H", "")); } } if (marketName.contains("沪港通")) { if (info.getSecuritiesCode() != null) { if (info.getSecuritiesCode().startsWith("6")) { info.setMarketId(MarketTradIngCodeEnum.SSE.getMarketId()); } else if (info.getSecuritiesCode().startsWith("H")) { info.setSecuritiesCode(info.getSecuritiesCode().replace("H", "")); } } } else if (marketName.contains("深港通")) { if (info.getSecuritiesCode() != null) { if (!info.getSecuritiesCode().startsWith("H")) { info.setMarketId(MarketTradIngCodeEnum.SZSE.getMarketId()); } else { info.setSecuritiesCode(info.getSecuritiesCode().replace("H", "")); } } } // 兜底策略:交易场所为场外(OTC) if (info.getMarketId() == null) { info.setMarketId(MarketTradIngCodeEnum.OTC.getMarketId()); } } /** * 期货及衍生品空头、多头识别逻辑 * * @param netCost 成本 * @param level 层级 * @param marketValue 市值 * @return 1-多头 或 2-空头 */ private static Integer inferFutureOptionLongShort(BigDecimal netCost, int level, BigDecimal marketValue) { //1.期货及衍生品的一级,二级,三级科目的多头空头识别逻辑:通过市值正负确认,正则为多头,反之空头 Integer inferByMarketValue = marketValue != null && marketValue.compareTo(BigDecimal.ZERO) > 0 ? BULL_LONG_SHORT : BEAR_LONG_SHORT; if (level <= 3) { return inferByMarketValue; } //成本为空时,识别为多头(在持仓分析的时候,会过滤掉成本为空的数据,因此无意义的,但为了防止空指针,默认为多头) if (netCost == null) { return BULL_LONG_SHORT; } //2.期货及衍生品的四级以上科目:1-如果成本小于0为空头;2-等于0时,市值为正则为多头,反之空头;3-成本大于0为多头 if (netCost.compareTo(BigDecimal.ZERO) < 0) { return BEAR_LONG_SHORT; } if (netCost.compareTo(BigDecimal.ZERO) == 0) { return inferByMarketValue; } return BULL_LONG_SHORT; } private static boolean startWith(Collection codeSet, String subCode) { for (String code : codeSet) { if (subCode.startsWith(code)) { return true; } } return false; } public static int getSubjectCodeEndIndex(String str) { char[] cArr = str.toCharArray(); if (!Character.isDigit(cArr[0])) { return 0; } int startIndex = 0; for (int i = 0; i < cArr.length; i++) { int index = cArr[i]; if ((index >= 48 && index <= 57) || (index >= 65 && index <= 90) || (index >= 97 && index <= 122)) { continue; } startIndex = i; break; } return startIndex; } public static List getAttrList(List dataList) { List attrList = new ArrayList<>(dataList.size()); for (AssetsValuationInfo info : dataList) { CmValuationTableAttribute attr = new CmValuationTableAttribute(); attr.setUserid(info.getUserId()); attr.setSubjectCode(info.getSubjectCode()); attr.setOriginalSubjectCode(info.getOriginalSubjectCode()); attr.setSubjectName(info.getSubjectName()); attr.setSecuritiesAmount(BigDecimalUtils.toBigDecimal(info.getSecuritiesAmount())); attr.setUnitCost(BigDecimalUtils.toBigDecimal(info.getUnitCost())); attr.setNetCost(BigDecimalUtils.toBigDecimal(info.getNetCost())); attr.setNetCostRatio(BigDecimalUtils.toBigDecimal(info.getNetCostRatio())); attr.setMarketValue(BigDecimalUtils.toBigDecimal(info.getMarketValue())); attr.setMarketValueRatio(BigDecimalUtils.toBigDecimal(info.getMarketValueRatio())); attr.setIncrement(BigDecimalUtils.toBigDecimal(info.getIncrement())); attr.setHaltInfo(info.getHaltInfo()); attr.setRightsInterestsInfo(info.getRightsInterestsInfo()); attr.setMarketPrice(BigDecimalUtils.toBigDecimal(info.getMarketPrice())); attr.setCurrency(info.getCurrency()); if (info.getExchangeRate() != null) { attr.setExchangeRate(BigDecimalUtils.toBigDecimal(info.getExchangeRate())); } attr.setOCurrencyCost(BigDecimalUtils.toBigDecimal(info.getOriCurrencyCost())); attr.setOCurrencyMarketValue(BigDecimalUtils.toBigDecimal(info.getOriCurrencyMarketValue())); attr.setOCurrencyValueAdded(BigDecimalUtils.toBigDecimal(info.getOriCurrencyValueAdded())); attrList.add(attr); } return attrList; } }