| .gitignore | ●●●●● patch | view | raw | blame | history | |
| src/main/java/com/nq/enums/EUserAssets.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/com/nq/pojo/UserAssets.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/com/nq/pojo/reponse/RUserAssets.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/com/nq/service/IUserPositionService.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/com/nq/service/impl/UserAssetsServices.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/com/nq/service/impl/UserPositionServiceImpl.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/com/nq/service/impl/UserServiceImpl.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/com/nq/utils/UserPointUtil.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/com/nq/utils/task/stock/StockTask.java | ●●●●● patch | view | raw | blame | history |
.gitignore
@@ -1,12 +1,92 @@ ### JetBrains template # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff .idea/ .idea/**/workspace.xml .idea/**/tasks.xml .idea/**/usage.statistics.xml .idea/**/dictionaries .idea/**/shelf # Generated files .idea/**/contentModel.xml # Sensitive or high-churn files .idea/**/dataSources/ .idea/**/dataSources.ids .idea/**/dataSources.local.xml .idea/**/sqlDataSources.xml .idea/**/dynamic.xml .idea/**/uiDesigner.xml .idea/**/dbnavigator.xml # Gradle .idea/**/gradle.xml .idea/**/libraries # Gradle and Maven with auto-import # When using Gradle or Maven with auto-import, you should exclude module files, # since they will be recreated, and may cause churn. Uncomment if using # auto-import. # .idea/artifacts # .idea/compiler.xml # .idea/jarRepositories.xml # .idea/modules.xml # .idea/*.iml # .idea/modules # *.iml # *.ipr # CMake cmake-build-*/ # Mongo Explorer plugin .idea/**/mongoSettings.xml # File-based project format *.iws # IntelliJ out/ target/ *.iml .flattened-pom.xml # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Cursive Clojure plugin .idea/replstate.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties # Editor-based Rest Client .idea/httpRequests # Android studio 3.1+ serialized cache file .idea/caches/build_file_checksums.ser ### Java template # Compiled class file *.class # Log file *.log # BlueJ files *.ctxt # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.jar *.war *.ear # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* src/main/java/com/nq/enums/EUserAssets.java
@@ -12,6 +12,7 @@ CLOSE_POSITION_RETURN_SECURITY_DEPOSIT("CLOSE_POSITION_RETURN_SECURITY","平仓返回保证金"), CLOSE_POSITION("CLOSE_POSITION","平仓"), CONSTRAINT_CLOSE_POSITION("CONSTRAINT_CLOSE_POSITION","强制平仓"), BUY("BUY","购买"), WITHDRAW("WITHDRAW","提现"), src/main/java/com/nq/pojo/UserAssets.java
@@ -47,4 +47,7 @@ // 冻结金额 private BigDecimal freezeMoney; // 累计盈亏 0 正 1 负 private Integer isZf; } src/main/java/com/nq/pojo/reponse/RUserAssets.java
@@ -52,4 +52,7 @@ private String symbol; private String symbolCode; //累计盈亏 0 正 1 负 private Integer isZf; } src/main/java/com/nq/service/IUserPositionService.java
@@ -83,4 +83,6 @@ ServerResponse buyDz(String stockCode, String password, Integer num, HttpServletRequest request) throws Exception; ServerResponse buyStockDzList(HttpServletRequest request); void stockConstraint(List<UserPosition> userPositions); } src/main/java/com/nq/service/impl/UserAssetsServices.java
@@ -60,7 +60,7 @@ UserAssets userAssets = userAssetsMapper.selectById(id); // 0 入款 1是扣钱 2 是充值 3 是提币 BigDecimal bigAmt = new BigDecimal(amt); if(type.equals("0") || type.equals("1 ")){ if(type.equals("0") || type.equals("1")){ if(type.equals("1")){ bigAmt = bigAmt.negate(); } @@ -112,13 +112,13 @@ userAssets.setFreezeMoney(userAssets.getFreezeMoney().add(amount.negate())); }else if(Objects.equals(eUserAssets.getCode(), EUserAssets.CLOSE_POSITION.getCode())){ userAssets.setAvailableBalance(userAssets.getAvailableBalance().add(amount)); userAssets.setCumulativeProfitAndLoss(userAssets.getCumulativeProfitAndLoss().add(amount.abs())); }else if(Objects.equals(eUserAssets.getCode(), EUserAssets.CLOSE_POSITION_RETURN_SECURITY_DEPOSIT.getCode())){ extracted(amount, userAssets); }else if(Objects.equals(eUserAssets.getCode(), EUserAssets.CLOSE_POSITION_RETURN_SECURITY_DEPOSIT.getCode())){ userAssets.setAvailableBalance(userAssets.getAvailableBalance().add(amount)); userAssets.setFreezeMoney(userAssets.getFreezeMoney().subtract(amount)); }else if(Objects.equals(eUserAssets.getCode(), EUserAssets.CALCULATE_PROFIT_AND_LOSS.getCode())){ userAssets.setCumulativeProfitAndLoss(userAssets.getProfitAndLoss().add(amount.abs())); }else if(Objects.equals(eUserAssets.getCode(), EUserAssets.TRANSFER.getCode())){ extracted(amount, userAssets); }else if(Objects.equals(eUserAssets.getCode(), EUserAssets.TRANSFER.getCode())){ userAssets.setAvailableBalance(userAssets.getAvailableBalance().add(amount)); eUserAssets.setDesc(desc); }else if(Objects.equals(eUserAssets.getCode(), EUserAssets.TOP_UP.getCode())){ @@ -128,6 +128,15 @@ }else if(Objects.equals(eUserAssets.getCode(), EUserAssets.HANDLING_CHARGE.getCode())){ userAssets.setHandlingCharge(userAssets.getHandlingCharge().add(amount.abs())); userAssets.setAvailableBalance(userAssets.getAvailableBalance().add(amount.negate())); extracted(amount.negate(),userAssets); }else if(Objects.equals(eUserAssets.getCode(), EUserAssets.CONSTRAINT_CLOSE_POSITION.getCode())){ userAssets.setFreezeMoney(userAssets.getFreezeMoney().subtract(amount)); if(userAssets.getIsZf() == 0){ userAssets.setCumulativeProfitAndLoss(userAssets.getCumulativeProfitAndLoss().subtract(amount)); }else{ userAssets.setCumulativeProfitAndLoss(userAssets.getCumulativeProfitAndLoss().add(amount)); } extracted(userAssets); } String after = userAssets.getAvailableBalance().toString(); MoneyLog moneyLog = new MoneyLog(); @@ -143,4 +152,34 @@ moneyLogMapper.insert(moneyLog); return userAssetsMapper.updateById(userAssets)>1; } //只要涉及到cumulativeProfitAndLoss变动重新设置状态 private static void extracted(UserAssets userAssets) { if(userAssets.getCumulativeProfitAndLoss().compareTo(BigDecimal.ZERO) >= 0){ userAssets.setIsZf(0); }else{ userAssets.setIsZf(1); userAssets.setCumulativeProfitAndLoss(userAssets.getCumulativeProfitAndLoss().abs()); } } private static void extracted(BigDecimal amount, UserAssets userAssets) { if(userAssets.getIsZf() == 1){ userAssets.setCumulativeProfitAndLoss(userAssets.getCumulativeProfitAndLoss().negate()); } if(userAssets.getIsZf() == 0){ if(amount.compareTo(BigDecimal.ZERO) >= 0){ userAssets.setCumulativeProfitAndLoss(userAssets.getCumulativeProfitAndLoss().add(amount.abs())); }else{ userAssets.setCumulativeProfitAndLoss(userAssets.getCumulativeProfitAndLoss().subtract(amount.abs())); } }else{ if(amount.compareTo(BigDecimal.ZERO) >= 0){ userAssets.setCumulativeProfitAndLoss(userAssets.getCumulativeProfitAndLoss().add(amount.abs())); }else{ userAssets.setCumulativeProfitAndLoss(userAssets.getCumulativeProfitAndLoss().subtract(amount.abs())); } } extracted(userAssets); } } src/main/java/com/nq/service/impl/UserPositionServiceImpl.java
@@ -119,7 +119,7 @@ SiteProduct siteProduct = iSiteProductService.getProductSetting(); User user = this.iUserService.getCurrentRefreshUser(request); if (siteProduct.getRealNameDisplay() && (StringUtils.isBlank(user.getRealName()) || StringUtils.isBlank(user.getIdCard()))) { if (siteProduct.getRealNameDisplay() && user.getIsActive() != 2) { return ServerResponse.createByErrorMsg("订单失败,请先实名认证", request); } // 手续费率 @@ -170,7 +170,7 @@ return ServerResponse.createByErrorMsg("报价0,请稍后再试", request); } BigDecimal buyAmt = nowPrice.multiply(new BigDecimal(buyNum)); BigDecimal buyAmt = nowPrice.multiply(new BigDecimal(buyNum)).divide(new BigDecimal(lever)); BigDecimal orderFree = siteSettingBuyFee.multiply(buyAmt); UserAssets userAssets = iUserAssetsServices.assetsByTypeAndUserId(stock.getStockType(), user.getId()); @@ -315,7 +315,9 @@ @Transactional public ServerResponse sell(String positionSn, int doType, HttpServletRequest request) { UserPosition userPosition = this.userPositionMapper.findPositionBySn(positionSn); BigDecimal siitteBuyFee = iSiteSettingService.getSiteSetting().getBuyFee(); // 手续费率 BigDecimal siitteBuyFee = new BigDecimal(iStockConfigServices.queryByKey(EConfigKey.BUY_HANDLING_CHARGE.getCode()).getCValue()) ; Boolean b = tradingHourService.timeCheck(userPosition.getStockCode()); if (!b) { return ServerResponse.createByErrorMsg("订单失败,不在交易时间之内", request); @@ -358,6 +360,7 @@ PositionProfitVO profitVO = UserPointUtil.getPositionProfitVO(userPosition, priceServices.getNowPrice(userPosition.getStockCode())); userAssetsServices.availablebalanceChange(stock.getStockType(), userPosition.getUserId(), EUserAssets.CLOSE_POSITION, profitVO.getAllProfitAndLose(), "", ""); @@ -1599,6 +1602,137 @@ } @Override @Transactional public void stockConstraint(List<UserPosition> list) { SiteSetting siteSetting = iSiteSettingService.getSiteSetting(); BigDecimal siteBuyFee = siteSetting.getBuyFee(); for (UserPosition position : list) { //平仓检查 Result result = getResult(position); if (result == null) continue; //利润为0不需要进行强制平仓 if(result.signum == 0){ continue; } boolean liquidation = false; liquidation = isLiquidation(position, result.signum, result.profit, liquidation); if(liquidation){ extracted(position, result.nowPrice, result.stock); } } } private Result getResult(UserPosition position) { // 检查订单是否存在 if (position == null) { log.info("订单不存在,订单id: {}", position.getId()); return null; } // 检查用户是否存在 User user = this.userMapper.selectById(position.getUserId()); if (user == null) { log.info("用户不存在,订单id: {}", position.getId()); return null; } // 检查是否在交易时间内 if (!tradingHourService.timeCheck(position.getStockCode())) { log.info("不在交易时间之内,订单id: {}", position.getId()); return null; } //判断订单 if (1 == position.getIsLock().intValue()) { log.info("订单已终止,订单id: {}", position.getId()); return null; } // 检查股票是否垫停 Stock stock = stockMapper.selectOne(new QueryWrapper<Stock>().eq("stock_code", position.getStockCode())); if (!priceServices.isLimitDownSell(stock.getStockCode())) { log.info("股票垫停,无法平仓,订单id: {}", position.getId()); return null; } //最新报价 BigDecimal nowPrice = priceServices.getNowPrice(position.getStockCode()); if (nowPrice.compareTo(BigDecimal.ZERO) <= 0) { log.info("报价0,平仓失败,订单id: {}", position.getId()); return null; } //判断订单是否已到强制平仓价格 BigDecimal purchaseAmount = position.getBuyOrderPrice().multiply(new BigDecimal(position.getOrderNum()));// 买入价总额 BigDecimal nowPriceAmount = nowPrice.multiply(new BigDecimal(position.getOrderNum())); // 现价总额 BigDecimal profit = nowPriceAmount.subtract(purchaseAmount);//利润 int signum = profit.signum(); // -1, 0, 1,分别表示 负数、零、正数 Result result = new Result(stock, nowPrice, profit, signum); return result; } private static class Result { public final Stock stock;//股票 public final BigDecimal nowPrice;//现价 public final BigDecimal profit;//利润 public final int signum;// -1, 0, 1,分别表示 负数、零、正数 public Result(Stock stock, BigDecimal nowPrice, BigDecimal profit, int signum) { this.stock = stock; this.nowPrice = nowPrice; this.profit = profit; this.signum = signum; } } //判断平仓 private static boolean isLiquidation(UserPosition position, int signum, BigDecimal profit, boolean liquidation) { if(position.getOrderDirection().equals("买涨")){ //如果买涨 signum 为-1则表示亏损 if(signum == -1){ //判断亏损金额是否达到保证金金额 BigDecimal negate = profit.negate(); if (negate.compareTo(position.getOrderTotalPrice()) >= 0){ //强制平仓 liquidation = true; } } }else{ //买跌 signum if(signum == 1){ //判断亏损金额是否达到保证金金额 if (profit.compareTo(position.getOrderTotalPrice()) >= 0){ //强制平仓 liquidation = true; } } } return liquidation; } //平仓 private void extracted(UserPosition position, BigDecimal nowPrice, Stock stock) { // 更新订单信息 position.setSellOrderId(GeneratePosition.getPositionId()); position.setSellOrderPrice(nowPrice); position.setSellOrderTime(new Date()); userPositionMapper.updateById(position); // 计算手续费等 BigDecimal handlingFee = BigDecimal.ZERO; //更新用户资产 userAssetsServices.availablebalanceChange(stock.getStockType(), position.getUserId(), EUserAssets.CONSTRAINT_CLOSE_POSITION, position.getOrderTotalPrice(), "", ""); log.info("强制平仓成功,订单id: {}", position.getId()); } } src/main/java/com/nq/service/impl/UserServiceImpl.java
@@ -505,7 +505,7 @@ rUserAssets.setCumulativeProfitAndLoss(hProfitAndLose.toString()); rUserAssets.setHandlingCharge(hMoney.toString()); rUserAssets.setProfitAndLoss(profitAndLose.toString()); rUserAssets.setIsZf(userAssets.getIsZf()); BigDecimal rate = rateServices.currencyRate( EStockType.getEStockTypeByCode(userAssets.getAccectType()),EStockType.US); src/main/java/com/nq/utils/UserPointUtil.java
@@ -72,7 +72,7 @@ if ("买跌".equals(position.getOrderDirection())) { profitAndLose = profitAndLose.negate(); } allProfitAndLose = profitAndLose.subtract(position.getOrderFee()).subtract(position.getOrderSpread()).subtract(position.getOrderStayFee()).subtract(position.getSpreadRatePrice()); allProfitAndLose = profitAndLose.subtract(position.getOrderSpread()).subtract(position.getOrderStayFee()).subtract(position.getSpreadRatePrice()); } else { BigDecimal subPrice = nowPrice.subtract(position.getBuyOrderPrice()); profitAndLose = subPrice.multiply(new BigDecimal(position.getOrderNum().intValue())); @@ -80,7 +80,7 @@ profitAndLose = profitAndLose.negate(); } //总盈亏= 浮动盈亏 – 手续费 – 印花税 – 留仓费 – 点差费 allProfitAndLose = profitAndLose.subtract(position.getOrderFee()).subtract(position.getOrderSpread()).subtract(position.getOrderStayFee()).subtract(position.getSpreadRatePrice()); allProfitAndLose = profitAndLose.subtract(position.getOrderSpread()).subtract(position.getOrderStayFee()).subtract(position.getSpreadRatePrice()); } PositionProfitVO positionProfitVO = new PositionProfitVO(); positionProfitVO.setProfitAndLose(profitAndLose); src/main/java/com/nq/utils/task/stock/StockTask.java
@@ -1,15 +1,20 @@ package com.nq.utils.task.stock; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.google.gson.Gson; import com.nq.dao.*; import com.nq.enums.EStockType; import com.nq.pojo.*; import com.nq.service.IMandatoryLiquidationService; import com.nq.service.IStockService; import com.nq.service.IUserPositionService; import com.nq.utils.http.HttpClientRequest; import com.nq.utils.redis.RedisKeyUtil; import com.nq.utils.redis.RedisShardedPoolUtils; import com.nq.utils.stock.BuyAndSellUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,6 +26,8 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @Component @@ -29,6 +36,15 @@ IStockService stockService; @Autowired StockMapper stockMapper; @Autowired IUserPositionService userPositionService; @Autowired UserPositionMapper userPositionMapper; private final Lock stockConstraintLock = new ReentrantLock(); @Autowired @@ -146,4 +162,28 @@ log.error("同步出错", e); } } /** * 强制平仓 */ @Scheduled(cron = "0/1 * * * * ?") public void stockConstraint(){ if (stockConstraintLock.tryLock()) { log.info("强制平仓任务:--------->开始"); try { List<UserPosition> userPositions = userPositionMapper.selectList(new LambdaQueryWrapper<UserPosition>().isNull(UserPosition::getSellOrderId)); if(CollectionUtils.isNotEmpty(userPositions)){ userPositionService.stockConstraint(userPositions); } } catch (Exception e) { e.printStackTrace(); log.error("强制平仓任务错误:" + e.getMessage()); } finally { stockConstraintLock.unlock(); log.info("强制平仓任务:--------->结束"); } } else { log.info("强制平仓任务--------->上次任务还未执行完成,本次任务忽略"); } } }