From 39135376545d5523f13960d922ac89071ea3e864 Mon Sep 17 00:00:00 2001
From: zj <1772600164@qq.com>
Date: Wed, 24 Apr 2024 17:57:46 +0800
Subject: [PATCH] 强制平仓

---
 src/main/java/com/nq/pojo/reponse/RUserAssets.java             |    3 
 .gitignore                                                     |   90 ++++++++++++++
 src/main/java/com/nq/service/impl/UserAssetsServices.java      |   49 +++++++
 src/main/java/com/nq/pojo/UserAssets.java                      |    3 
 src/main/java/com/nq/utils/task/stock/StockTask.java           |   40 ++++++
 src/main/java/com/nq/enums/EUserAssets.java                    |    1 
 src/main/java/com/nq/service/impl/UserPositionServiceImpl.java |  140 ++++++++++++++++++++++
 src/main/java/com/nq/service/IUserPositionService.java         |    2 
 src/main/java/com/nq/utils/UserPointUtil.java                  |    4 
 src/main/java/com/nq/service/impl/UserServiceImpl.java         |    2 
 10 files changed, 318 insertions(+), 16 deletions(-)

diff --git a/.gitignore b/.gitignore
index 32858aa..ab690ec 100644
--- a/.gitignore
+++ b/.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*
diff --git a/src/main/java/com/nq/enums/EUserAssets.java b/src/main/java/com/nq/enums/EUserAssets.java
index 7384a33..4036884 100644
--- a/src/main/java/com/nq/enums/EUserAssets.java
+++ b/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","提现"),
 
diff --git a/src/main/java/com/nq/pojo/UserAssets.java b/src/main/java/com/nq/pojo/UserAssets.java
index 2196a20..2650c0c 100644
--- a/src/main/java/com/nq/pojo/UserAssets.java
+++ b/src/main/java/com/nq/pojo/UserAssets.java
@@ -47,4 +47,7 @@
 
     // 冻结金额
     private BigDecimal freezeMoney;
+
+    // 累计盈亏 0 正 1 负
+    private Integer isZf;
 }
diff --git a/src/main/java/com/nq/pojo/reponse/RUserAssets.java b/src/main/java/com/nq/pojo/reponse/RUserAssets.java
index 777241c..92f2584 100644
--- a/src/main/java/com/nq/pojo/reponse/RUserAssets.java
+++ b/src/main/java/com/nq/pojo/reponse/RUserAssets.java
@@ -52,4 +52,7 @@
     private String symbol;
 
     private String symbolCode;
+
+    //累计盈亏 0 正 1 负
+    private Integer isZf;
 }
diff --git a/src/main/java/com/nq/service/IUserPositionService.java b/src/main/java/com/nq/service/IUserPositionService.java
index 22d1f77..19a31e3 100644
--- a/src/main/java/com/nq/service/IUserPositionService.java
+++ b/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);
 }
diff --git a/src/main/java/com/nq/service/impl/UserAssetsServices.java b/src/main/java/com/nq/service/impl/UserAssetsServices.java
index f4048b4..29f2e2f 100644
--- a/src/main/java/com/nq/service/impl/UserAssetsServices.java
+++ b/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);
+    }
 }
diff --git a/src/main/java/com/nq/service/impl/UserPositionServiceImpl.java b/src/main/java/com/nq/service/impl/UserPositionServiceImpl.java
index 67784f6..6c3f208 100644
--- a/src/main/java/com/nq/service/impl/UserPositionServiceImpl.java
+++ b/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());
+    }
+
 }
 
 
diff --git a/src/main/java/com/nq/service/impl/UserServiceImpl.java b/src/main/java/com/nq/service/impl/UserServiceImpl.java
index 4467d0d..bd33072 100644
--- a/src/main/java/com/nq/service/impl/UserServiceImpl.java
+++ b/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);
 
diff --git a/src/main/java/com/nq/utils/UserPointUtil.java b/src/main/java/com/nq/utils/UserPointUtil.java
index 66376a2..679fcee 100644
--- a/src/main/java/com/nq/utils/UserPointUtil.java
+++ b/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);
diff --git a/src/main/java/com/nq/utils/task/stock/StockTask.java b/src/main/java/com/nq/utils/task/stock/StockTask.java
index 1e0d80b..ed9caf6 100644
--- a/src/main/java/com/nq/utils/task/stock/StockTask.java
+++ b/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("强制平仓任务--------->上次任务还未执行完成,本次任务忽略");
+        }
+    }
 }

--
Gitblit v1.9.3