ValuationTableParseUtil.java 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206
  1. package com.simuwang.base.common.util;
  2. import cn.hutool.core.collection.CollUtil;
  3. import cn.hutool.core.map.MapUtil;
  4. import cn.hutool.core.util.StrUtil;
  5. import com.simuwang.base.common.conts.AccountingItems;
  6. import com.simuwang.base.common.conts.AccountingNewItems;
  7. import com.simuwang.base.common.conts.HoldingType;
  8. import com.simuwang.base.common.enums.MarketTradIngCodeEnum;
  9. import com.simuwang.base.pojo.dos.FundPositionDetailDO;
  10. import com.simuwang.base.pojo.dos.ValuationTableDO;
  11. import com.simuwang.base.pojo.valuation.AssetsValuationInfo;
  12. import com.simuwang.base.pojo.valuation.CmValuationTableAttribute;
  13. import com.smppw.utils.BigDecimalUtils;
  14. import org.apache.commons.lang3.StringUtils;
  15. import org.slf4j.Logger;
  16. import org.slf4j.LoggerFactory;
  17. import java.math.BigDecimal;
  18. import java.util.*;
  19. import java.util.stream.Collectors;
  20. public final class ValuationTableParseUtil {
  21. private static final Logger log = LoggerFactory.getLogger(ValuationTableParseUtil.class);
  22. /**
  23. * 多头
  24. */
  25. public final static Integer BULL_LONG_SHORT = 1;
  26. /**
  27. * 空头
  28. */
  29. public final static Integer BEAR_LONG_SHORT = 2;
  30. private final static Map<String, Integer> CURRENCY_CODE_MAP = MapUtil.newHashMap();
  31. static {
  32. // 1-人民币,2-美元,3-港元
  33. CURRENCY_CODE_MAP.put("CNY", 1);
  34. CURRENCY_CODE_MAP.put("USD", 2);
  35. CURRENCY_CODE_MAP.put("HKD", 3);
  36. }
  37. /**
  38. * 特殊连接符(用户证券名称的识别剔除)
  39. */
  40. private final static List<String> SPECIAL_CODE = CollUtil.toList("-", "_", ".");
  41. public static List<FundPositionDetailDO> parseDataNew(List<CmValuationTableAttribute> attrList, ValuationTableDO file,
  42. List<String> marketCodeList) {
  43. // 汇总类数据map
  44. Map<String, Integer> tailCode = new HashMap<>();
  45. // 区分"科目代码"列为明细类和汇总类数据
  46. boolean isFooterRow;
  47. // 返回结果(持仓明细对象)
  48. List<FundPositionDetailDO> dataList = new ArrayList<>();
  49. // 识别科目代码层级关系
  50. handleSubjectCodeLevel(attrList);
  51. // 兼容存在重复的数据(兼容updateValuationPositionDetailType任务)
  52. Map<String, String> originalCodePreOriginalCodeMap = attrList.stream().filter(e -> StrUtil.isNotBlank(e.getPreOriginalSubjectCode()))
  53. .collect(Collectors.toMap(CmValuationTableAttribute::getOriginalSubjectCode, CmValuationTableAttribute::getPreOriginalSubjectCode,
  54. (oldValue, newValue) -> newValue));
  55. for (int i = 0; i < attrList.size(); i++) {
  56. CmValuationTableAttribute attr = attrList.get(i);
  57. if (attr.getSubjectCode() == null) {
  58. continue;
  59. }
  60. // 估值表解析到的数据 -> 持仓明细对象
  61. FundPositionDetailDO data = toDetail(attr, file);
  62. // 判断数据为明细类 or 汇总类数据(给字段isFooterRow赋值)
  63. isFooterRow = dealDetailOrSummary(data);
  64. if (data.getSubjectCode() == null) {
  65. continue;
  66. }
  67. // 汇总类数据("基金投资合计","实收资本"等字段)
  68. if (isFooterRow) {
  69. handleSummaryData(data, tailCode);
  70. }
  71. // 持仓明细类数据
  72. if (!isFooterRow) {
  73. // 从科目代码识别出明细证券代码securitiesCode
  74. String securitiesCode = dealSecuritiesCode(attr.getSubjectCode(), attr.getPreOriginalSubjectCode(), attr.getLevel());
  75. data.setSecuritiesCode(securitiesCode);
  76. // 处理证券名称(剔除上一科目名称)
  77. String preOriginalSubjectCode = originalCodePreOriginalCodeMap.get(attr.getOriginalSubjectCode());
  78. String preSecuritiesName = attrList.stream().filter(e -> e.getOriginalSubjectCode().equals(preOriginalSubjectCode))
  79. .findFirst().map(CmValuationTableAttribute::getSubjectName).orElse(null);
  80. String securitiesName = handleSecuritiesName(data.getLevel(), data.getSecuritiesName(), preSecuritiesName);
  81. data.setSecuritiesName(securitiesName);
  82. // data.subjectCode字段保存估值表中的原始科目代码
  83. data.setSubjectCode(attr.getOriginalSubjectCode());
  84. // 资产类型,1资产类,2负债类,3共同类,4所有者权益类,6损益类(根据科目代码编码规则可得)
  85. data.setSubjectType(Integer.valueOf(data.getSubjectCode().substring(0, 1)));
  86. }
  87. data.setIsvalid(1);
  88. data.setCreateTime(new Date());
  89. dataList.add(data);
  90. }
  91. // 推断持仓明细类型
  92. dealAssetTypeNew(dataList, originalCodePreOriginalCodeMap, marketCodeList);
  93. return dataList;
  94. }
  95. /**
  96. * 初步处理证券名称(剔除上一科目名称)
  97. * 如:场外_已上市_开放式_货币_成本-平安财富宝货币A
  98. * 准确的证券名称应该为:平安财富宝货币A
  99. *
  100. * @param level 持仓明细层级
  101. * @param originalSecuritiesName 当前明细的证券名称
  102. * @param preSecuritiesName 上一级的证券名称
  103. * @return 处理后的证券名称
  104. */
  105. private static String handleSecuritiesName(Integer level, String originalSecuritiesName, String preSecuritiesName) {
  106. if (StrUtil.isBlank(preSecuritiesName) || StrUtil.isBlank(originalSecuritiesName) || !originalSecuritiesName.contains(preSecuritiesName) || level == null || level < 4) {
  107. return originalSecuritiesName;
  108. }
  109. String securitiesName = originalSecuritiesName.replace(preSecuritiesName, "");
  110. for (String specialCode : SPECIAL_CODE) {
  111. if (securitiesName.startsWith(specialCode)) {
  112. securitiesName = securitiesName.substring(1);
  113. }
  114. }
  115. return securitiesName;
  116. }
  117. /**
  118. * 获取明细证券代码
  119. *
  120. * @param subjectCode 估值表科目代码(去掉".","_")
  121. * @param preOriginalSubjectCode 上一级科目代码
  122. * @param level 层级
  123. * @return 明细证券代码
  124. */
  125. private static String dealSecuritiesCode(String subjectCode, String preOriginalSubjectCode, Integer level) {
  126. String securitiesCode = null;
  127. // 一级科目的证券代码 等于 估值表原始科目代码
  128. if (level == null) {
  129. return null;
  130. }
  131. if (level == 1) {
  132. securitiesCode = subjectCode;
  133. return securitiesCode;
  134. }
  135. // 二级或三级科目的证券代码为空
  136. if (level == 2 || level == 3) {
  137. return null;
  138. }
  139. if (StrUtil.isNotBlank(preOriginalSubjectCode) && level >= 4) {
  140. String preSubjectCode = preOriginalSubjectCode.replaceAll("[\\.\\s_-]", "");
  141. securitiesCode = subjectCode.substring(preSubjectCode.length());
  142. }
  143. return securitiesCode;
  144. }
  145. private static void handleSummaryData(FundPositionDetailDO data, Map<String, Integer> tailCode) {
  146. data.setSecType(100);
  147. String subjectName = data.getSecuritiesName();
  148. if (StringUtils.isNotEmpty(subjectName)) {
  149. if (subjectName.startsWith("-") || Character.isDigit(subjectName.toCharArray()[0])) {
  150. //如果科目名称是数字
  151. data.setSecuritiesAmount(BigDecimalUtils.toBigDecimal((subjectName.replace("%", "").replace(",", ""))));
  152. data.setSecuritiesName(data.getSubjectCode());
  153. } else {
  154. data.setSecuritiesName(data.getSubjectCode() + subjectName);
  155. }
  156. } else {
  157. data.setSecuritiesName(data.getSubjectCode());
  158. }
  159. String subjectCode = getTypeNo(data.getSubjectCode());
  160. if (subjectCode == null) {
  161. subjectCode = "1000";
  162. } else {
  163. if (tailCode.containsKey(subjectCode)) {
  164. int num = tailCode.get(subjectCode);
  165. tailCode.put(subjectCode, num + 1);
  166. subjectCode = subjectCode + ":" + num;
  167. } else {
  168. tailCode.put(subjectCode, 1);
  169. }
  170. }
  171. data.setSecuritiesCode(subjectCode);
  172. data.setSubjectCode(subjectCode);
  173. data.setLevel(0);
  174. }
  175. private static boolean dealDetailOrSummary(FundPositionDetailDO data) {
  176. boolean isFooterRow = false;
  177. if (!Character.isDigit(data.getSubjectCode().toCharArray()[0])) {
  178. isFooterRow = true;
  179. }
  180. if (!isFooterRow && ValuationDataUtils.hasChinese(data.getSubjectCode(), false)) {
  181. String code = data.getSubjectCode();
  182. if (data.getSecuritiesName() != null) {
  183. code = code.replace(data.getSecuritiesName(), "");
  184. }
  185. if (ValuationDataUtils.hasChinese(code, false)) {
  186. int endIndex = getSubjectCodeEndIndex(data.getSubjectCode());
  187. String subCode = code.substring(0, endIndex);
  188. if (subCode.length() < 4 && data.getSecuritiesName() == null) {
  189. isFooterRow = true;
  190. } else {
  191. data.setSubjectCode(subCode);
  192. if (data.getSecuritiesName() == null) {
  193. data.setSecuritiesName(code.substring(endIndex));
  194. }
  195. }
  196. } else {
  197. data.setSubjectCode(code);
  198. }
  199. }
  200. return isFooterRow;
  201. }
  202. /**
  203. * 识别科目代码的层级
  204. *
  205. * @param valuationDataList 估值表解析到的数据
  206. */
  207. private static void handleSubjectCodeLevel(List<CmValuationTableAttribute> valuationDataList) {
  208. // 先过滤出一级科目(一级科目长度一定是4位)
  209. List<CmValuationTableAttribute> levelOneCodeList = valuationDataList.stream()
  210. .filter(e -> StrUtil.isNotBlank(e.getOriginalSubjectCode()) && e.getOriginalSubjectCode().length() == 4
  211. && StrUtil.isNumeric(e.getOriginalSubjectCode()))
  212. .sorted(Comparator.comparing(CmValuationTableAttribute::getOriginalSubjectCode)).collect(Collectors.toList());
  213. if (CollUtil.isEmpty(levelOneCodeList)) {
  214. return;
  215. }
  216. levelOneCodeList.forEach(levelOneCodeData -> {
  217. String parentCode = levelOneCodeData.getOriginalSubjectCode();
  218. levelOneCodeData.setLevel(1);
  219. List<CmValuationTableAttribute> subSubjectList = valuationDataList.stream()
  220. .filter(e -> StrUtil.isNotBlank(e.getOriginalSubjectCode()) && e.getOriginalSubjectCode().startsWith(parentCode) && e.getLevel() == null)
  221. .sorted(Comparator.comparing(e -> e.getOriginalSubjectCode().length())).collect(Collectors.toList());
  222. handSubSubjectLevel(subSubjectList, parentCode, 1);
  223. });
  224. }
  225. /**
  226. * 递归找出子类科目的层级
  227. *
  228. * @param subjectList 子类科目数据
  229. * @param parentLevel 父类层级
  230. */
  231. private static void handSubSubjectLevel(List<CmValuationTableAttribute> subjectList, String parentCode, Integer parentLevel) {
  232. if (CollUtil.isEmpty(subjectList)) {
  233. return;
  234. }
  235. subjectList.forEach(e -> e.setPreOriginalSubjectCode(parentCode));
  236. for (CmValuationTableAttribute parentData : subjectList) {
  237. if (parentData.getLevel() != null) {
  238. continue;
  239. }
  240. String subParentCode = parentData.getOriginalSubjectCode();
  241. parentData.setLevel(parentLevel + 1);
  242. parentData.setPreOriginalSubjectCode(parentCode);
  243. List<CmValuationTableAttribute> subSubjectList = subjectList.stream()
  244. .filter(e -> StrUtil.isNotBlank(e.getOriginalSubjectCode()) && e.getOriginalSubjectCode().startsWith(subParentCode)
  245. && !e.getOriginalSubjectCode().equals(subParentCode) && e.getLevel() == null)
  246. .sorted(Comparator.comparing(e -> e.getOriginalSubjectCode().length())).collect(Collectors.toList());
  247. handSubSubjectLevel(subSubjectList, subParentCode, parentLevel + 1);
  248. }
  249. }
  250. private static FundPositionDetailDO toDetail(CmValuationTableAttribute attr, ValuationTableDO userValuationTableDo) {
  251. FundPositionDetailDO detail = new FundPositionDetailDO();
  252. detail.setFundId(userValuationTableDo.getFundId());
  253. detail.setValuationDate(userValuationTableDo.getValuationDate());
  254. detail.setCurrency(getCurrencyCode(attr.getCurrency()));
  255. detail.setExchangeRate(attr.getExchangeRate());
  256. detail.setSecuritiesName(attr.getSubjectName());
  257. detail.setSecuritiesAmount(attr.getSecuritiesAmount());
  258. detail.setMarketValue(attr.getMarketValue());
  259. // [权重](市值占净值比)如果不能从估值表中读取时,市值占净值比=市值/资产净值
  260. BigDecimal marketValueRatio = attr.getMarketValueRatio();
  261. if (marketValueRatio == null || marketValueRatio.compareTo(BigDecimal.ZERO) == 0) {
  262. BigDecimal netAssetMarketValue = userValuationTableDo.getNetAssetsValue();
  263. if (attr.getMarketValue() != null && netAssetMarketValue != null && netAssetMarketValue.compareTo(BigDecimal.ZERO) != 0) {
  264. marketValueRatio = BigDecimalUtils.divide(attr.getMarketValue(), netAssetMarketValue);
  265. }
  266. }
  267. detail.setMarketValueRatio(marketValueRatio);
  268. detail.setNetCost(attr.getNetCost());
  269. detail.setNetCostRatio(attr.getNetCostRatio());
  270. BigDecimal increment = attr.getIncrement();
  271. detail.setIncrement(increment);
  272. // [估值增值]如果不能从估值表中读取时,估值增值=市值-成本
  273. if (increment != null && increment.compareTo(BigDecimal.ZERO) == 0) {
  274. increment = BigDecimalUtils.subtract(attr.getMarketValue(), attr.getNetCost());
  275. }
  276. detail.setIncrement(increment);
  277. detail.setHaltInfo(attr.getHaltInfo());
  278. detail.setNature(1);// 默认为多
  279. detail.setSubjectCode(attr.getSubjectCode());
  280. detail.setLevel(attr.getLevel());
  281. detail.setMarketPrice(attr.getMarketPrice());
  282. detail.setUnitCost(attr.getUnitCost());
  283. return detail;
  284. }
  285. private static Integer getCurrencyCode(String currency) {
  286. if (StrUtil.isBlank(currency)) {
  287. return null;
  288. }
  289. for (Map.Entry<String, Integer> entry : CURRENCY_CODE_MAP.entrySet()) {
  290. if (entry.getKey().equals(currency)) {
  291. return entry.getValue();
  292. }
  293. }
  294. return null;
  295. }
  296. private static String getTypeNo(String name) {
  297. if (name.contains("合计") && name.contains("证券")) {
  298. return "100";
  299. }
  300. if (name.startsWith("其中")) {
  301. if (name.contains("股票")) {
  302. return "101";
  303. }
  304. if (name.contains("债券")) {
  305. return "102";
  306. }
  307. if (name.contains("基金")) {
  308. return "103";
  309. }
  310. if (name.contains("权证")) {
  311. return "104";
  312. }
  313. if (name.contains("衍生")) {
  314. return "105";
  315. }
  316. return null;
  317. }
  318. if (name.contains("头寸")) {
  319. return "106";
  320. }
  321. boolean isPaidInCapital = isPaidInCapital(name);
  322. if (isPaidInCapital) {
  323. return "107";
  324. }
  325. if (name.contains("损益平准金")) {
  326. if (name.contains("未")) {
  327. return "109";
  328. }
  329. return "108";
  330. }
  331. if (name.contains("合计") && name.contains("资产")) {
  332. return "110";
  333. }
  334. if (name.contains("合计") && name.contains("负债")) {
  335. return "111";
  336. }
  337. if (name.contains("资产") && name.contains("净值")) {
  338. return "112";
  339. }
  340. if (name.contains("单位") && name.contains("净值")) {
  341. String trimName = name.trim();
  342. if (trimName.length() == 5) {
  343. // 去掉末尾的符号
  344. trimName = trimName.substring(0, 4);
  345. }
  346. if ("单位净值".equals(trimName) || name.contains("今日") || name.contains("基金")) {
  347. return "201";
  348. }
  349. if (name.contains("累计")) {
  350. return "202";
  351. }
  352. if (name.contains("期初")) {
  353. return "203";
  354. }
  355. if (name.contains("昨日")) {
  356. return "204";
  357. }
  358. if (name.contains("年初")) {
  359. return "205";
  360. }
  361. if (name.contains("期初")) {
  362. return "206";
  363. }
  364. if (name.contains("周初")) {
  365. return "207";
  366. }
  367. if (name.contains("月初")) {
  368. return "208";
  369. }
  370. return null;
  371. }
  372. if (name.contains("可分配") && (name.contains("收益") || name.contains("利润"))) {
  373. if (name.contains("单位")) {
  374. return "212";
  375. }
  376. return "213";
  377. }
  378. if (name.contains("现金类占")) {
  379. return "210";
  380. }
  381. if (name.contains("累计派现")) {
  382. return "211";
  383. }
  384. return null;
  385. }
  386. private static boolean isPaidInCapital(String name) {
  387. return name.contains("实收") && name.contains("资本")
  388. || name.contains("实收") && name.contains("信托");
  389. }
  390. /**
  391. * 识别证券类型,多头空头,交易场所
  392. *
  393. * @param dataList 估值表数据对象
  394. * @param originalCodePreOriginalCodeMap 当前科目代码 -上一级科目代码映射关系
  395. * @param marketCodeList 交易场所code列表
  396. */
  397. public static void dealAssetTypeNew(List<FundPositionDetailDO> dataList, Map<String, String> originalCodePreOriginalCodeMap, List<String> marketCodeList) {
  398. if (CollUtil.isEmpty(dataList)) {
  399. return;
  400. }
  401. Map<String, String> securitiesCodeNameMap = MapUtil.newHashMap();
  402. // 查询交易场所code
  403. for (int i = 0; i < dataList.size(); i++) {
  404. FundPositionDetailDO fundPositionDetail = dataList.get(i);
  405. // 汇总类数据("基金投资合计","实收资本"等字段)不需要处理
  406. if (fundPositionDetail.getSecType() != null && fundPositionDetail.getSecType() == 100) {
  407. continue;
  408. }
  409. // 获取二级、三级、四级科目名称
  410. List<String> securitiesNameList = CollUtil.toList("", "", "");
  411. getPreSecuritiesName(fundPositionDetail, dataList, originalCodePreOriginalCodeMap, securitiesNameList);
  412. String secondSecuritiesName = securitiesNameList.get(0);
  413. String thirdSecuritiesName = securitiesNameList.get(1);
  414. String fourSecuritiesName = fundPositionDetail.getLevel() != null && fundPositionDetail.getLevel() > 4 ? securitiesNameList.get(2) : "";
  415. // 标识证券类型
  416. addStockTypeNew(fundPositionDetail, secondSecuritiesName, thirdSecuritiesName, fourSecuritiesName);
  417. // 识别空头多头(识别过程中已经默认为 1-多头)
  418. addLongShort(fundPositionDetail);
  419. // 空头类或正回购的市值入库时应该为负数
  420. if (fundPositionDetail.getNature() == 2 && fundPositionDetail.getMarketValue() != null
  421. && fundPositionDetail.getMarketValue().compareTo(BigDecimal.ZERO) > 0
  422. || (fundPositionDetail.getSubType() != null && fundPositionDetail.getSubType().equals(HoldingType.BondBackRepo.getId()))) {
  423. fundPositionDetail.setMarketValue(BigDecimalUtils.multiply(fundPositionDetail.getMarketValue(), BigDecimalUtils.toBigDecimal("-1")));
  424. }
  425. // 识别交易场所
  426. addMarketIdNew(fundPositionDetail, secondSecuritiesName + thirdSecuritiesName + fourSecuritiesName);
  427. // 识别证券代码(过滤掉交易场所code)
  428. filterSecuritiesCode(fundPositionDetail, marketCodeList);
  429. // 用于后面识别估值增值和应计利息子类
  430. if (fundPositionDetail.getLevel() != null && fundPositionDetail.getLevel() >= 4) {
  431. securitiesCodeNameMap.put(fundPositionDetail.getSubjectCode(), securitiesNameList.get(1));
  432. }
  433. }
  434. // 划分估值增值和应计利息明细数据的sub_type
  435. for (int i = 0; i < dataList.size(); i++) {
  436. FundPositionDetailDO info = dataList.get(i);
  437. Integer secType = info.getSecType();
  438. if (secType == null || secType == 100 || (info.getLevel() != null && info.getLevel() == 0)) {
  439. continue;
  440. }
  441. // 二级,三级期货,期权,其他衍生品的子类 -> sub_type=20
  442. if (info.getLevel() > 1 && info.getLevel() < 4) {
  443. if ((secType == HoldingType.Derivatives.getId() || secType == HoldingType.Future.getId() || secType == HoldingType.Option.getId())) {
  444. info.setSubType(HoldingType.CostCash.getId());
  445. }
  446. }
  447. // 四级以上的明细,如果成本或数量为空或为0
  448. if (info.getLevel() >= 4) {
  449. if (((info.getNetCost() == null || info.getNetCost().compareTo(BigDecimal.ZERO) == 0.0)
  450. || (info.getSecuritiesAmount() == null || info.getSecuritiesAmount().compareTo(BigDecimal.ZERO) == 0.0))) {
  451. String thirdSecuritiesName = securitiesCodeNameMap.get(info.getSubjectCode());
  452. // 正逆回购(目前存在应计利息,不存在估值增值)
  453. if (info.getSecType() == HoldingType.BondBack.getId()) {
  454. if (StrUtil.isNotBlank(thirdSecuritiesName) && thirdSecuritiesName.contains("利息")) {
  455. info.setSubType(HoldingType.Interest.getId());
  456. }
  457. continue;
  458. }
  459. // 其他衍生品不做这个限制
  460. if (info.getSecType() == HoldingType.Derivatives.getId()) {
  461. continue;
  462. }
  463. // 三级科目名称包含"利息" -> sub_type=22
  464. if (StrUtil.isNotBlank(thirdSecuritiesName) && thirdSecuritiesName.contains("利息")) {
  465. info.setSubType(HoldingType.Interest.getId());
  466. continue;
  467. }
  468. info.setSubType(HoldingType.CostCash.getId());
  469. }
  470. }
  471. }
  472. }
  473. /**
  474. * 识别证券代码
  475. *
  476. * @param fundPositionDetail 持仓明细数据
  477. * @param marketCodeList 交易场所code列表
  478. */
  479. public static void filterSecuritiesCode(FundPositionDetailDO fundPositionDetail, List<String> marketCodeList) {
  480. if (fundPositionDetail.getLevel() == null || fundPositionDetail.getLevel() < 4) {
  481. return;
  482. }
  483. // 识别证券代码
  484. String securitiesCode = getSecuritiesCode(fundPositionDetail.getSecuritiesCode(), marketCodeList);
  485. fundPositionDetail.setSecuritiesCode(securitiesCode);
  486. }
  487. /**
  488. * 识别证券代码(去掉交易场所code)
  489. *
  490. * @param originalSecuritiesCode 原始证券代码
  491. * @param marketCodeList 交易场所code列表
  492. * @return 去掉交易场所code的证券代码
  493. */
  494. private static String getSecuritiesCode(String originalSecuritiesCode, List<String> marketCodeList) {
  495. String securitiesCode = originalSecuritiesCode;
  496. if (StrUtil.isBlank(originalSecuritiesCode)) {
  497. return securitiesCode;
  498. }
  499. for (String marketCode : marketCodeList) {
  500. if (originalSecuritiesCode.endsWith(marketCode)) {
  501. int length = originalSecuritiesCode.length();
  502. if (length > marketCode.length()) {
  503. securitiesCode = originalSecuritiesCode.substring(0, length - marketCode.length());
  504. break;
  505. }
  506. }
  507. }
  508. return securitiesCode;
  509. }
  510. /**
  511. * 标识证券类型
  512. *
  513. * @param info 持仓明细对象
  514. * @param secondSecuritiesName 二级证券名称
  515. * @param thirdSecuritiesName 三级证券名称
  516. * @param fourSecuritiesName 四级证券名称
  517. */
  518. public static void addStockTypeNew(FundPositionDetailDO info, String secondSecuritiesName, String thirdSecuritiesName, String fourSecuritiesName) {
  519. // 无证券名称或市值的数据不识别证券类型
  520. if (info.getSecuritiesName() == null || info.getMarketValue() == null) {
  521. return;
  522. }
  523. Integer level = info.getLevel();
  524. String subjectCode = info.getSubjectCode();
  525. // 一.现金类
  526. if (startWith(AccountingNewItems.CASH, subjectCode)) {
  527. info.setSecType(HoldingType.Cash.getId());
  528. info.setSubType(HoldingType.Cash.getId());
  529. return;
  530. }
  531. // 二.股票类
  532. if (startWith(AccountingNewItems.EQUITY_LIST, subjectCode)) {
  533. // 1."1102"开头
  534. if (subjectCode.startsWith("1102") || level < 4) {
  535. info.setSecType(HoldingType.Stock.getId());
  536. Integer subType = dealStockSubType(secondSecuritiesName, thirdSecuritiesName);
  537. info.setSubType(subType);
  538. return;
  539. }
  540. // 2."2101"开头 并且 二级或三级或四级科目有"存托凭证"or"股票"的 四级及以上科目
  541. boolean is2101StockType = subjectCode.startsWith("2101") && (secondSecuritiesName.contains("存托凭证") || secondSecuritiesName.contains("股票")
  542. || thirdSecuritiesName.contains("存托凭证") || thirdSecuritiesName.contains("股票")
  543. || fourSecuritiesName.contains("存托凭证") || fourSecuritiesName.contains("股票"));
  544. if (is2101StockType) {
  545. info.setSecType(HoldingType.Stock.getId());
  546. Integer subType = dealStockSubType(secondSecuritiesName, thirdSecuritiesName);
  547. info.setSubType(subType);
  548. return;
  549. }
  550. }
  551. // 3."1101"开头并且层级 >=4 并且二级科目或三级或四级科目含有"股票"
  552. boolean is1101StockType = subjectCode.startsWith("1101") && level >= 4 &&
  553. (secondSecuritiesName.contains("股票") || thirdSecuritiesName.contains("股票") || fourSecuritiesName.contains("股票"));
  554. if (is1101StockType) {
  555. info.setSecType(HoldingType.Stock.getId());
  556. Integer subType = dealStockSubType(secondSecuritiesName, thirdSecuritiesName);
  557. info.setSubType(subType);
  558. return;
  559. }
  560. // 三.债券类
  561. if (startWith(AccountingNewItems.BOND_LIST, subjectCode)) {
  562. // 1."1103","1104"开头
  563. if (subjectCode.startsWith("1103") || subjectCode.startsWith("1104") || level < 4) {
  564. info.setSecType(HoldingType.Bond.getId());
  565. info.setSubType(HoldingType.Bond.getId());
  566. return;
  567. }
  568. // 2.子类"正逆回购" -> 证券名称以"GC"开头 或 证券名称包含"-0"
  569. if (info.getSecuritiesName().startsWith("GC") || info.getSecuritiesName().contains("-0")) {
  570. info.setSecType(HoldingType.Bond.getId());
  571. info.setSubType(HoldingType.BondBack.getId());
  572. return;
  573. }
  574. }
  575. // 3."2101"开头并且层级 >=4 并且二级或三级或四级科目含有"债券"
  576. boolean is2101BondType = subjectCode.startsWith("2101") && level >= 4 &&
  577. (secondSecuritiesName.contains("债券") || thirdSecuritiesName.contains("债券") || fourSecuritiesName.contains("债券"));
  578. if (is2101BondType) {
  579. info.setSecType(HoldingType.Bond.getId());
  580. info.setSubType(HoldingType.Bond.getId());
  581. return;
  582. }
  583. // 4."1101"开头并且层级 >=4 并且二级或三级或四级科目含有"债券"或"债"
  584. boolean is1101BondType = subjectCode.startsWith("1101") && level >= 4 &&
  585. (secondSecuritiesName.contains("债券") || secondSecuritiesName.contains("债")
  586. || thirdSecuritiesName.contains("债券") || thirdSecuritiesName.contains("债")
  587. || fourSecuritiesName.contains("债券") || fourSecuritiesName.contains("债"));
  588. if (is1101BondType) {
  589. info.setSecType(HoldingType.Bond.getId());
  590. info.setSubType(HoldingType.Bond.getId());
  591. return;
  592. }
  593. // 5.1108开头并且层级 >=4 并且二级或三级或四级科目带有“债”
  594. boolean is1108BondType = subjectCode.startsWith("1108") && level >= 4
  595. && (secondSecuritiesName.contains("债") || thirdSecuritiesName.contains("债") || fourSecuritiesName.contains("债"));
  596. if (is1108BondType) {
  597. info.setSecType(HoldingType.Bond.getId());
  598. info.setSubType(HoldingType.Bond.getId());
  599. return;
  600. }
  601. // 正逆回购类
  602. if (startWith(AccountingNewItems.BOND_BACK_LIST, subjectCode)) {
  603. info.setSecType(HoldingType.BondBack.getId());
  604. // 2202-正回购
  605. if (subjectCode.startsWith("2202")) {
  606. info.setSubType(HoldingType.BondBackRepo.getId());
  607. }
  608. // 1202-逆回购
  609. if (subjectCode.startsWith("1202")) {
  610. info.setSubType(HoldingType.BondBackReverseRepo.getId());
  611. }
  612. return;
  613. }
  614. // 四.基金类
  615. if (startWith(AccountingNewItems.FUND_LIST, subjectCode)) {
  616. // "1108"开头并且非“债券类型”才归为基金(先识别债券再识别基金)
  617. // 1."1105","1107","1109","1110"开头
  618. info.setSecType(HoldingType.Fund.getId());
  619. info.setSubType(HoldingType.Fund.getId());
  620. return;
  621. }
  622. // 2."1101"开头并且层级<4 或 "1101"开头并且二级或三级或四级科目含有"基金"或"理财"
  623. boolean is1101FundType = subjectCode.startsWith("1101") && level < 4 || (subjectCode.startsWith("1101") &&
  624. (secondSecuritiesName.contains("基金") || secondSecuritiesName.contains("理财")
  625. || thirdSecuritiesName.contains("基金") || thirdSecuritiesName.contains("理财")
  626. || fourSecuritiesName.contains("基金") || fourSecuritiesName.contains("理财")));
  627. if (is1101FundType) {
  628. info.setSecType(HoldingType.Fund.getId());
  629. info.setSubType(HoldingType.Fund.getId());
  630. return;
  631. }
  632. // 3.2101开头并且层级>=4,二级或三级科目名称包含“ETF”
  633. boolean is2101FundType = subjectCode.startsWith("2101") && level >= 4
  634. && (secondSecuritiesName.contains("ETF") || thirdSecuritiesName.contains("ETF") || fourSecuritiesName.contains("ETF"));
  635. if (is2101FundType) {
  636. info.setSecType(HoldingType.Fund.getId());
  637. info.setSubType(HoldingType.Fund.getId());
  638. return;
  639. }
  640. // 五.期货、期权类
  641. if (startWith(AccountingNewItems.DERIVATIVES, subjectCode)) {
  642. // 其他衍生品
  643. boolean isDerivatives = secondSecuritiesName.contains("收益互换") || thirdSecuritiesName.contains("收益互换") || fourSecuritiesName.contains("收益互换");
  644. boolean isOptionType = secondSecuritiesName.contains("期权") || thirdSecuritiesName.contains("期权") || fourSecuritiesName.contains("期权");
  645. if (level < 4) {
  646. // 层级小于4,并且二级或三级科目包含“期权” -> 期权
  647. if (isOptionType) {
  648. info.setSecType(HoldingType.Option.getId());
  649. info.setSubType(HoldingType.Option.getId());
  650. return;
  651. }
  652. // 层级小于4,并且非期权非其他衍生品 -> 期货
  653. if (!isDerivatives) {
  654. info.setSecType(HoldingType.Future.getId());
  655. info.setSubType(HoldingType.Future.getId());
  656. return;
  657. }
  658. // 层级小于4,并且二级或三级科目包含“收益互换” -> 其他衍生品
  659. info.setSecType(HoldingType.Derivatives.getId());
  660. info.setSubType(HoldingType.Derivatives.getId());
  661. return;
  662. }
  663. // 标识子类类型
  664. String securitiesName = info.getSecuritiesName();
  665. if (isOptionType) {
  666. info.setSecType(HoldingType.Option.getId());
  667. // 期权通用判断
  668. boolean isCmdtOption = securitiesName.contains("购") || securitiesName.contains("沽") || securitiesName.contains("C") || securitiesName.contains("P");
  669. // ETF期权
  670. boolean isETFOption = (securitiesName.contains("ETF") || securitiesName.contains("科创50") || securitiesName.contains("科创板50"))
  671. && isCmdtOption;
  672. // 股指期权
  673. boolean isIndexOption = (securitiesName.contains("上证50") || securitiesName.contains("沪深300") || securitiesName.contains("中证1000"))
  674. && isCmdtOption;
  675. if (isETFOption) {
  676. info.setSubType(HoldingType.ETFOption.getId());
  677. return;
  678. }
  679. if (isIndexOption) {
  680. info.setSubType(HoldingType.IndexOption.getId());
  681. return;
  682. }
  683. if (isCmdtOption) {
  684. info.setSubType(HoldingType.CmdtOption.getId());
  685. return;
  686. }
  687. // 归为其他期权
  688. info.setSubType(HoldingType.Other.getId());
  689. return;
  690. }
  691. // 非期权和非其他衍生品 -> 期货类
  692. if (!isDerivatives) {
  693. String stockCode = info.getSecuritiesCode();
  694. info.setSecuritiesCode(stockCode);
  695. info.setSecType(HoldingType.Future.getId());
  696. if (startWith(AccountingNewItems.INDEX_FUTRUE, stockCode)) {
  697. // 股指期货
  698. info.setSubType(HoldingType.IndexFuture.getId());
  699. return;
  700. }
  701. if (startWith(AccountingItems.BOND_FUTRUE, stockCode) || info.getSecuritiesName().contains("国债")) {
  702. if (stockCode.startsWith("TA")) {
  703. // 国债期货(T),商品期货(TA)存在冲突,先识别商品期货,再识别国债期货
  704. info.setSubType(HoldingType.CmdtFuture.getId());
  705. return;
  706. }
  707. // 国债期货
  708. info.setSubType(HoldingType.BondFuture.getId());
  709. return;
  710. }
  711. if (startWith(AccountingItems.CMDT_FUTRUE, stockCode)) {
  712. // 商品期货
  713. info.setSubType(HoldingType.CmdtFuture.getId());
  714. return;
  715. }
  716. // 归为其他期货
  717. info.setSubType(HoldingType.Other.getId());
  718. return;
  719. }
  720. // 其他衍生品
  721. info.setSecType(HoldingType.Derivatives.getId());
  722. // 二三四级科目是否包含"冲抵" -> 冲抵现金
  723. if (secondSecuritiesName.contains("冲抵") || thirdSecuritiesName.contains("冲抵") || fourSecuritiesName.contains("冲抵")) {
  724. info.setSubType(HoldingType.CostCash.getId());
  725. } else {
  726. info.setSubType(HoldingType.Derivatives.getId());
  727. }
  728. return;
  729. }
  730. // 以3199开头 -> 其他衍生品
  731. if (subjectCode.startsWith("3199")) {
  732. info.setSecType(HoldingType.Derivatives.getId());
  733. info.setSubType(HoldingType.Derivatives.getId());
  734. return;
  735. }
  736. // 七.其他类
  737. if (startWith(AccountingNewItems.OTHER, subjectCode)) {
  738. info.setSecType(HoldingType.Other.getId());
  739. info.setSubType(HoldingType.Other.getId());
  740. return;
  741. }
  742. // 其他资产类
  743. if (startWith(AccountingNewItems.OTHER_ASSET, subjectCode)) {
  744. info.setSecType(HoldingType.Other.getId());
  745. info.setSubType(HoldingType.OTHER_ASSET.getId());
  746. return;
  747. }
  748. // 其他负债类
  749. if (startWith(AccountingNewItems.OTHER_DEBT, subjectCode)) {
  750. info.setSecType(HoldingType.Other.getId());
  751. info.setSubType(HoldingType.OTHER_DEBT.getId());
  752. return;
  753. }
  754. // 八.兜底策略
  755. if (info.getSecType() == null && StrUtil.isNotBlank(secondSecuritiesName) && StrUtil.isNotBlank(thirdSecuritiesName)) {
  756. if (secondSecuritiesName.contains("资产") || thirdSecuritiesName.contains("资产")) {
  757. info.setSecType(HoldingType.Cash.getId());
  758. info.setSubType(HoldingType.Cash.getId());
  759. return;
  760. }
  761. if (secondSecuritiesName.contains("股票") || thirdSecuritiesName.contains("股")) {
  762. info.setSecType(HoldingType.Stock.getId());
  763. info.setSubType(HoldingType.Stock.getId());
  764. return;
  765. }
  766. if (secondSecuritiesName.contains("基金") || thirdSecuritiesName.contains("基金")) {
  767. info.setSecType(HoldingType.Other.getId());
  768. info.setSubType(HoldingType.Other.getId());
  769. return;
  770. }
  771. if ((secondSecuritiesName.contains("理财") || thirdSecuritiesName.contains("理财"))
  772. && (info.getSubjectCode().startsWith("1101.08") || info.getSubjectCode().startsWith("1101.09"))) {
  773. info.setSecType(HoldingType.Other.getId());
  774. info.setSubType(HoldingType.Other.getId());
  775. return;
  776. }
  777. if (secondSecuritiesName.contains("债") || thirdSecuritiesName.contains("债")) {
  778. if (!secondSecuritiesName.contains("负债") && !thirdSecuritiesName.contains("负债")) {
  779. info.setSecType(HoldingType.Bond.getId());
  780. info.setSubType(HoldingType.Bond.getId());
  781. } else {
  782. info.setSecType(HoldingType.Other.getId());
  783. info.setSubType(HoldingType.Other.getId());
  784. }
  785. }
  786. }
  787. }
  788. /**
  789. * 股票类子类识别:沪港通,深港通,港股通
  790. *
  791. * @param secondSecuritiesName 二级证券名称
  792. * @param thirdSecuritiesName 三级证券名称
  793. * @return 股票子类
  794. */
  795. private static Integer dealStockSubType(String secondSecuritiesName, String thirdSecuritiesName) {
  796. if (secondSecuritiesName.contains("沪港通") || thirdSecuritiesName.contains("沪港通")) {
  797. return HoldingType.StockSHH.getId();
  798. }
  799. if ((secondSecuritiesName.contains("深港通") || thirdSecuritiesName.contains("深港通"))) {
  800. return HoldingType.StockSZH.getId();
  801. }
  802. if ((secondSecuritiesName.contains("港股通") || thirdSecuritiesName.contains("港股通"))) {
  803. return HoldingType.StockHGT.getId();
  804. }
  805. return HoldingType.Stock.getId();
  806. }
  807. /**
  808. * 多头、空头识别
  809. *
  810. * @param fundPositionDetail 持仓明细对象
  811. */
  812. public static void addLongShort(FundPositionDetailDO fundPositionDetail) {
  813. Integer secType = fundPositionDetail.getSecType();
  814. if (secType == null) {
  815. return;
  816. }
  817. String subjectCode = fundPositionDetail.getSubjectCode();
  818. // 股票类:归属于1102,1101下的股票为多头;归属于2101下的股票为空头
  819. if (secType == HoldingType.Stock.getId()) {
  820. if (subjectCode.startsWith("1102") || subjectCode.startsWith("1101")) {
  821. fundPositionDetail.setNature(BULL_LONG_SHORT);
  822. return;
  823. }
  824. if (subjectCode.startsWith("2101")) {
  825. fundPositionDetail.setNature(BEAR_LONG_SHORT);
  826. return;
  827. }
  828. }
  829. // 债券类:归属于1101,1103,1104下的债券为多头;归属于2101下的债券为空头
  830. if (secType == HoldingType.Bond.getId()) {
  831. if (subjectCode.startsWith("110") || subjectCode.startsWith("1102") || subjectCode.startsWith("1104")) {
  832. fundPositionDetail.setNature(BULL_LONG_SHORT);
  833. return;
  834. }
  835. if (subjectCode.startsWith("2101")) {
  836. fundPositionDetail.setNature(BEAR_LONG_SHORT);
  837. return;
  838. }
  839. }
  840. // 期货,期权,衍生品类
  841. if (secType == HoldingType.Future.getId() || secType == HoldingType.Option.getId() || secType == HoldingType.Derivatives.getId()) {
  842. BigDecimal netCost = fundPositionDetail.getNetCost();
  843. Integer longShort = inferFutureOptionLongShort(netCost, fundPositionDetail.getLevel(), fundPositionDetail.getMarketValue());
  844. fundPositionDetail.setNature(longShort);
  845. }
  846. // 正回购 -> 空头
  847. if (secType == HoldingType.Bond.getId() && fundPositionDetail.getSubjectCode().startsWith("2202")) {
  848. fundPositionDetail.setNature(BEAR_LONG_SHORT);
  849. }
  850. // 2101下的基金 -> 空头
  851. if (secType == HoldingType.Fund.getId() && fundPositionDetail.getSubjectCode().startsWith("2101")) {
  852. fundPositionDetail.setNature(BEAR_LONG_SHORT);
  853. }
  854. // 其他资产 -> 多头, 其他负债 -> 空头
  855. if (secType == HoldingType.Other.getId() && fundPositionDetail.getSubType() != null && fundPositionDetail.getSubType().equals(HoldingType.OTHER_DEBT.getId())) {
  856. fundPositionDetail.setNature(BEAR_LONG_SHORT);
  857. }
  858. }
  859. /**
  860. * 获取持仓明细的二、三级、四级证券名称
  861. *
  862. * @param fundPositionDetail 持仓明细
  863. * @param dataList 估值表持仓明细数据
  864. * @param originalCodePreOriginalCodeMap 科目代码-上一级科目代码映射关系
  865. * @param securitiesNameList
  866. * @return 持仓明细的二、三级、四级证券名称
  867. */
  868. private static void getPreSecuritiesName(FundPositionDetailDO fundPositionDetail, List<FundPositionDetailDO> dataList,
  869. Map<String, String> originalCodePreOriginalCodeMap, List<String> securitiesNameList) {
  870. Integer level = fundPositionDetail.getLevel();
  871. String preOriginalCode = originalCodePreOriginalCodeMap.get(fundPositionDetail.getSubjectCode());
  872. if (StrUtil.isBlank(preOriginalCode) || level == null || level == 1) {
  873. return;
  874. }
  875. if (level == 2) {
  876. securitiesNameList.set(0, fundPositionDetail.getSecuritiesName());
  877. }
  878. if (level == 3) {
  879. String secondSecuritiesName = dataList.stream().filter(e -> e.getSubjectCode().equals(preOriginalCode))
  880. .findFirst().map(FundPositionDetailDO::getSecuritiesName).orElse("");
  881. securitiesNameList.set(0, secondSecuritiesName);
  882. securitiesNameList.set(1, fundPositionDetail.getSecuritiesName());
  883. }
  884. if (level == 4) {
  885. securitiesNameList.set(2, fundPositionDetail.getSecuritiesName());
  886. }
  887. if (level >= 4) {
  888. FundPositionDetailDO preFundPositionDetail = dataList.stream().filter(e -> e.getSubjectCode().equals(preOriginalCode))
  889. .findFirst().orElse(null);
  890. if (preFundPositionDetail != null) {
  891. getPreSecuritiesName(preFundPositionDetail, dataList, originalCodePreOriginalCodeMap, securitiesNameList);
  892. }
  893. }
  894. }
  895. /**
  896. * 识别交易场所
  897. * MarketTradIngCodeEnum
  898. *
  899. * @param info 持仓明细对象数据
  900. * @param marketName 二级、三级证券名称
  901. */
  902. public static void addMarketIdNew(FundPositionDetailDO info, String marketName) {
  903. if (info.getSecType() == null) {
  904. return;
  905. }
  906. // 股票交易场所
  907. if (info.getSecType() == HoldingType.Stock.getId()) {
  908. addStockMarketId(info, marketName);
  909. }
  910. // 债券交易场所
  911. if (info.getSecType() == HoldingType.Bond.getId() || info.getSecType() == HoldingType.BondBack.getId()) {
  912. addBondMarketId(info, marketName);
  913. }
  914. // 期货、期权交易场所
  915. if (info.getSecType() == HoldingType.Future.getId() || info.getSecType() == HoldingType.Option.getId()) {
  916. addFutureOptionMarketId(info, marketName);
  917. }
  918. }
  919. private static void addBondMarketId(FundPositionDetailDO info, String marketName) {
  920. if (marketName.contains("上交") || marketName.contains("上海")) {
  921. // 二级或三级科目包含"上交"或"上海" -> 上交所
  922. info.setMarketId(MarketTradIngCodeEnum.SSE.getMarketId());
  923. return;
  924. }
  925. if (marketName.contains("深交") || marketName.contains("深圳")) {
  926. // 二级或三级科目包含"深交"或"深圳" -> 深交所
  927. info.setMarketId(MarketTradIngCodeEnum.SZSE.getMarketId());
  928. return;
  929. }
  930. if (marketName.contains("北交") || marketName.contains("北京")) {
  931. // 二级或三级科目包含“北交”或“北京”-> 北交所
  932. info.setMarketId(MarketTradIngCodeEnum.NEEQ.getMarketId());
  933. return;
  934. }
  935. if (marketName.contains("银行间")) {
  936. // 二级或三级科目包含“银行间”-> 银行间
  937. info.setMarketId(MarketTradIngCodeEnum.IBM.getMarketId());
  938. return;
  939. }
  940. // 兜底策略:交易场所为场外(OTC)
  941. if (info.getMarketId() == null) {
  942. info.setMarketId(MarketTradIngCodeEnum.OTC.getMarketId());
  943. }
  944. }
  945. /**
  946. * 识别期货、期权交易场所
  947. *
  948. * @param info 持仓明细对象数据
  949. * @param marketName 二级、三级证券名称
  950. */
  951. private static void addFutureOptionMarketId(FundPositionDetailDO info, String marketName) {
  952. if (marketName.contains("上交") || (marketName.contains("上海")
  953. && !marketName.contains("上海能源") && !marketName.contains("上海商品") && !marketName.contains("上海期货"))) {
  954. // 二级或三级科目包含"上交"或"上海"并且不包含"上海能源"和"上海商品"和"上海期货" -> 上交所
  955. info.setMarketId(MarketTradIngCodeEnum.SSE.getMarketId());
  956. return;
  957. }
  958. if (marketName.contains("深交") || marketName.contains("深圳")) {
  959. // 二级或三级科目包含"深交"或"深圳" -> 深交所
  960. info.setMarketId(MarketTradIngCodeEnum.SZSE.getMarketId());
  961. return;
  962. }
  963. if (marketName.contains("中金所") && info.getSecType() != null && info.getSecType().equals(HoldingType.Future.getId())) {
  964. // 二级或三级科目包含"中金所" -> 中国金融期货交易所(期货-20,期权-51)
  965. info.setMarketId(MarketTradIngCodeEnum.CFFEX_FUTURE.getMarketId());
  966. return;
  967. }
  968. if (marketName.contains("中金所") && info.getSecType() != null && info.getSecType().equals(HoldingType.Option.getId())) {
  969. // 二级或三级科目包含"中金所" -> 中国金融期货交易所(期货-20,期权-51)
  970. info.setMarketId(MarketTradIngCodeEnum.CFFEX_OPTION.getMarketId());
  971. return;
  972. }
  973. if (marketName.contains("广州商品") || marketName.contains("广期所") || marketName.contains("广州期货")) {
  974. // 二级或三级科目包含"广州商品"或"广期所"或"广州期货”-> 广州期货交易所
  975. info.setMarketId(MarketTradIngCodeEnum.GZFE.getMarketId());
  976. return;
  977. }
  978. if (marketName.contains("郑州商品") || marketName.contains("郑商所") || marketName.contains("郑州期货")) {
  979. // 二级或三级科目包含"郑州商品"或"郑商所"或"郑州期货”-> 郑州期货交易所
  980. info.setMarketId(MarketTradIngCodeEnum.CZC.getMarketId());
  981. return;
  982. }
  983. if (marketName.contains("大连商品") || marketName.contains("大商所") || marketName.contains("大连期货")) {
  984. // 二级或三级科目包含"大连商品"或"大商所"或"大连期货”-> 大连期货交易所
  985. info.setMarketId(MarketTradIngCodeEnum.DCE.getMarketId());
  986. return;
  987. }
  988. if (marketName.contains("上海能源")) {
  989. // 二级或三级科目包含"上海能源" -> 上海国际能源交易中心
  990. info.setMarketId(MarketTradIngCodeEnum.INE.getMarketId());
  991. return;
  992. }
  993. if (marketName.contains("上海商品") || marketName.contains("上期所") || marketName.contains("上海期货")) {
  994. // 二级或三级科目包含"上海商品"或"上期所"或"上海期货”-> 上海期货交易所
  995. info.setMarketId(MarketTradIngCodeEnum.SHFE.getMarketId());
  996. return;
  997. }
  998. // 兜底策略:交易场所为场外(OTC)
  999. if (info.getMarketId() == null) {
  1000. info.setMarketId(MarketTradIngCodeEnum.OTC.getMarketId());
  1001. }
  1002. }
  1003. /**
  1004. * 识别股票交易场所
  1005. *
  1006. * @param info 持仓明细对象数据
  1007. * @param marketName 二级、三级证券名称
  1008. */
  1009. private static void addStockMarketId(FundPositionDetailDO info, String marketName) {
  1010. if (marketName.contains("场外") || marketName.contains("新三板")) {
  1011. // 二级或三级科目包含“场外“或”新三板 -> 新三板
  1012. info.setMarketId(MarketTradIngCodeEnum.NTBC.getMarketId());
  1013. return;
  1014. }
  1015. if (marketName.contains("上交") || marketName.contains("上海")) {
  1016. // 二级或三级科目包含"上交"或"上海" -> 上交所
  1017. info.setMarketId(MarketTradIngCodeEnum.SSE.getMarketId());
  1018. return;
  1019. }
  1020. if (marketName.contains("深交") || marketName.contains("深圳")) {
  1021. // 二级或三级科目包含"深交"或"深圳" -> 深交所
  1022. info.setMarketId(MarketTradIngCodeEnum.SZSE.getMarketId());
  1023. return;
  1024. }
  1025. if (marketName.contains("北交") || marketName.contains("北京")) {
  1026. // 二级或三级科目包含“北交”或“北京”-> 北交所
  1027. info.setMarketId(MarketTradIngCodeEnum.NEEQ.getMarketId());
  1028. return;
  1029. }
  1030. if (marketName.contains("港") || marketName.contains("港股通")) {
  1031. // 二级或三级科目包含"港"或"港股通" -> 港交所
  1032. info.setMarketId(MarketTradIngCodeEnum.HKEX.getMarketId());
  1033. if (info.getSecuritiesCode() != null && info.getSecuritiesCode().startsWith("H")) {
  1034. info.setSecuritiesCode(info.getSecuritiesCode().replace("H", ""));
  1035. }
  1036. }
  1037. if (marketName.contains("沪港通")) {
  1038. if (info.getSecuritiesCode() != null) {
  1039. if (info.getSecuritiesCode().startsWith("6")) {
  1040. info.setMarketId(MarketTradIngCodeEnum.SSE.getMarketId());
  1041. } else if (info.getSecuritiesCode().startsWith("H")) {
  1042. info.setSecuritiesCode(info.getSecuritiesCode().replace("H", ""));
  1043. }
  1044. }
  1045. } else if (marketName.contains("深港通")) {
  1046. if (info.getSecuritiesCode() != null) {
  1047. if (!info.getSecuritiesCode().startsWith("H")) {
  1048. info.setMarketId(MarketTradIngCodeEnum.SZSE.getMarketId());
  1049. } else {
  1050. info.setSecuritiesCode(info.getSecuritiesCode().replace("H", ""));
  1051. }
  1052. }
  1053. }
  1054. // 兜底策略:交易场所为场外(OTC)
  1055. if (info.getMarketId() == null) {
  1056. info.setMarketId(MarketTradIngCodeEnum.OTC.getMarketId());
  1057. }
  1058. }
  1059. /**
  1060. * 期货及衍生品空头、多头识别逻辑
  1061. *
  1062. * @param netCost 成本
  1063. * @param level 层级
  1064. * @param marketValue 市值
  1065. * @return 1-多头 或 2-空头
  1066. */
  1067. private static Integer inferFutureOptionLongShort(BigDecimal netCost, int level, BigDecimal marketValue) {
  1068. //1.期货及衍生品的一级,二级,三级科目的多头空头识别逻辑:通过市值正负确认,正则为多头,反之空头
  1069. Integer inferByMarketValue = marketValue != null && marketValue.compareTo(BigDecimal.ZERO) > 0 ? BULL_LONG_SHORT : BEAR_LONG_SHORT;
  1070. if (level <= 3) {
  1071. return inferByMarketValue;
  1072. }
  1073. //成本为空时,识别为多头(在持仓分析的时候,会过滤掉成本为空的数据,因此无意义的,但为了防止空指针,默认为多头)
  1074. if (netCost == null) {
  1075. return BULL_LONG_SHORT;
  1076. }
  1077. //2.期货及衍生品的四级以上科目:1-如果成本小于0为空头;2-等于0时,市值为正则为多头,反之空头;3-成本大于0为多头
  1078. if (netCost.compareTo(BigDecimal.ZERO) < 0) {
  1079. return BEAR_LONG_SHORT;
  1080. }
  1081. if (netCost.compareTo(BigDecimal.ZERO) == 0) {
  1082. return inferByMarketValue;
  1083. }
  1084. return BULL_LONG_SHORT;
  1085. }
  1086. private static boolean startWith(Collection<String> codeSet, String subCode) {
  1087. for (String code : codeSet) {
  1088. if (subCode.startsWith(code)) {
  1089. return true;
  1090. }
  1091. }
  1092. return false;
  1093. }
  1094. public static int getSubjectCodeEndIndex(String str) {
  1095. char[] cArr = str.toCharArray();
  1096. if (!Character.isDigit(cArr[0])) {
  1097. return 0;
  1098. }
  1099. int startIndex = 0;
  1100. for (int i = 0; i < cArr.length; i++) {
  1101. int index = cArr[i];
  1102. if ((index >= 48 && index <= 57) || (index >= 65 && index <= 90) || (index >= 97 && index <= 122)) {
  1103. continue;
  1104. }
  1105. startIndex = i;
  1106. break;
  1107. }
  1108. return startIndex;
  1109. }
  1110. public static List<CmValuationTableAttribute> getAttrList(List<AssetsValuationInfo> dataList) {
  1111. List<CmValuationTableAttribute> attrList = new ArrayList<>(dataList.size());
  1112. for (AssetsValuationInfo info : dataList) {
  1113. CmValuationTableAttribute attr = new CmValuationTableAttribute();
  1114. attr.setUserid(info.getUserId());
  1115. attr.setSubjectCode(info.getSubjectCode());
  1116. attr.setOriginalSubjectCode(info.getOriginalSubjectCode());
  1117. attr.setSubjectName(info.getSubjectName());
  1118. attr.setSecuritiesAmount(BigDecimalUtils.toBigDecimal(info.getSecuritiesAmount()));
  1119. attr.setUnitCost(BigDecimalUtils.toBigDecimal(info.getUnitCost()));
  1120. attr.setNetCost(BigDecimalUtils.toBigDecimal(info.getNetCost()));
  1121. attr.setNetCostRatio(BigDecimalUtils.toBigDecimal(info.getNetCostRatio()));
  1122. attr.setMarketValue(BigDecimalUtils.toBigDecimal(info.getMarketValue()));
  1123. attr.setMarketValueRatio(BigDecimalUtils.toBigDecimal(info.getMarketValueRatio()));
  1124. attr.setIncrement(BigDecimalUtils.toBigDecimal(info.getIncrement()));
  1125. attr.setHaltInfo(info.getHaltInfo());
  1126. attr.setRightsInterestsInfo(info.getRightsInterestsInfo());
  1127. attr.setMarketPrice(BigDecimalUtils.toBigDecimal(info.getMarketPrice()));
  1128. attr.setCurrency(info.getCurrency());
  1129. if (info.getExchangeRate() != null) {
  1130. attr.setExchangeRate(BigDecimalUtils.toBigDecimal(info.getExchangeRate()));
  1131. }
  1132. attr.setOCurrencyCost(BigDecimalUtils.toBigDecimal(info.getOriCurrencyCost()));
  1133. attr.setOCurrencyMarketValue(BigDecimalUtils.toBigDecimal(info.getOriCurrencyMarketValue()));
  1134. attr.setOCurrencyValueAdded(BigDecimalUtils.toBigDecimal(info.getOriCurrencyValueAdded()));
  1135. attrList.add(attr);
  1136. }
  1137. return attrList;
  1138. }
  1139. }