zj
2024-04-24 39135376545d5523f13960d922ac89071ea3e864
强制平仓
10 files modified
334 ■■■■■ changed files
.gitignore 90 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/enums/EUserAssets.java 1 ●●●● patch | view | raw | blame | history
src/main/java/com/nq/pojo/UserAssets.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/pojo/reponse/RUserAssets.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/IUserPositionService.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/UserAssetsServices.java 49 ●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/UserPositionServiceImpl.java 140 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/UserServiceImpl.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/nq/utils/UserPointUtil.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/nq/utils/task/stock/StockTask.java 40 ●●●●● 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("强制平仓任务--------->上次任务还未执行完成,本次任务忽略");
        }
    }
}