1
zj
17 hours ago effab9619e17b97ac98b035eaa64ba99b27f5dc6
trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderService.java
@@ -63,6 +63,10 @@
import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
@@ -86,6 +90,12 @@
@Transactional
@Slf4j
public class ContractOrderService extends ServiceImpl<ContractOrderMapper, ContractOrder> {
    /**
     * 资金费结算间隔(分钟):从当地日界 0:00 起每 N 分钟为一个结算点。
     * 240 即每 4 小时一档(0、4、8、12、16、20 点)。
     */
    private static final int FUNDING_SETTLEMENT_INTERVAL_MINUTES = 240;
    private Logger logger = LogManager.getLogger(ContractOrderService.class);
@@ -161,6 +171,10 @@
    private ContractApplyOrderService contractApplyOrderService;
    @Autowired
    private SysparaService sysparaService;
    @Autowired
    @Lazy
    private ContractOrderCalculationService contractOrderCalculationService;
    public IPage<ContractOrderDTO> listRecordCur(Page page, ContractOrderQuery query) {
        if (query.getStartTime() != null) {
@@ -373,12 +387,30 @@
            return null;
        }
        /**
         * 收益
         */
        contractOrderCalculationService.refreshMarkPriceProfit(order);
        BigDecimal volume = order.getVolume();
        BigDecimal profit = settle(order, order.getVolume());
        if (volume.compareTo(BigDecimal.ZERO) <= 0) {
            return null;
        }
        ContractOrderProfit cache = getCacheProfit(order.getUuid());
        BigDecimal markPrice = order.getTradeAvgPrice();
        if (cache != null && cache.getCloseAvgPrice() != null
                && cache.getCloseAvgPrice().compareTo(BigDecimal.ZERO) > 0) {
            markPrice = cache.getCloseAvgPrice();
        }
        BigDecimal fundingPnlChunk = calculateAccruedFundingPnl(order, markPrice, new Date());
        String symbol = order.getSymbol();
        Item item = itemService.findBySymbol(symbol);
        BigDecimal closeFee = calculateCloseTradingFee(item, markPrice, volume);
        BigDecimal profit = settle(order, volume).subtract(closeFee);
        order.setFee(defaultZero(order.getFee()).add(closeFee));
        order.setAmountClose(order.getAmountClose().subtract(closeFee));
        order.setFundingFee(defaultZero(order.getFundingFee()).add(fundingPnlChunk));
//        Item item = itemService.findBySymbol(symbol);
//        profit = exchangeRateService.getUsdtByType(profit, item.getType());
        if (ContractOrder.ORDER_FOLLOW == order.getFollow()) { // 跟单订单
@@ -406,11 +438,6 @@
            walletService.updateMoney(order.getSymbol(), partyId, profit, BigDecimal.ZERO,
                    Constants.MONEYLOG_CATEGORY_CONTRACT, Constants.WALLET_USDT, Constants.MONEYLOG_CONTENT_CONTRACT_CLOSE, "平仓,平仓合约数[" + volume + "],订单号[" + order.getOrderNo() + "]");
        }
        order.setState(ContractOrder.STATE_CREATED);
        order.setVolume(BigDecimal.ZERO);
        order.setDeposit(BigDecimal.ZERO);
        order.setCloseTime(DateUtil.currentSeconds());
        order.setCloseTimeTs(DateUtil.currentSeconds());
//        List<Realtime> list = this.dataService.realtime(order.getSymbol());
//        // 平仓时候把当前价格先更新回去
//        if (list.size() != 0) {
@@ -466,6 +493,7 @@
            if (cacheProfit != null) {
                contractOrder.setProfit(cacheProfit.getProfit());
                contractOrder.setCloseAvgPrice(cacheProfit.getCloseAvgPrice());
                contractOrder.setForceClosePrice(cacheProfit.getForceClosePrice());
            } else {
                contractOrder.setProfit(BigDecimal.ZERO);
@@ -630,11 +658,6 @@
     * @param volume 平仓的张数
     */
    public BigDecimal settle(ContractOrder order, BigDecimal volume) {
        /**
         * 平仓比率
         */
//        BigDecimal rate = volume.divide(order.getVolumeOpen(), 10, RoundingMode.HALF_UP);
        ContractOrderProfit cacheProfit = getCacheProfit(order.getUuid());
        BigDecimal originProfit = BigDecimal.ZERO;
        if (cacheProfit != null) {
@@ -642,7 +665,16 @@
            order.setCloseAvgPrice(cacheProfit.getCloseAvgPrice());
        }
        BigDecimal profit = order.getDeposit().add(originProfit);
        BigDecimal currentVolume = defaultZero(order.getVolume());
        if (currentVolume.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
        BigDecimal closeRatio = volume.divide(currentVolume, 10, RoundingMode.HALF_UP);
        BigDecimal currentDeposit = defaultZero(order.getDeposit());
        BigDecimal currentBorrowed = getCurrentBorrowedAmount(order);
        BigDecimal releasedDeposit = currentDeposit.multiply(closeRatio).setScale(8, RoundingMode.HALF_UP);
        BigDecimal realizedProfit = originProfit.multiply(closeRatio).setScale(8, RoundingMode.HALF_UP);
        BigDecimal profit = releasedDeposit.add(realizedProfit);
        if (ContractOrder.ORDER_FOLLOW == order.getFollow()) { // 跟单还得减去利息收益
            BigDecimal orderAmount = order.getUnitAmount().multiply(order.getTradeAvgPrice()).multiply(order.getLeverRate()); //订单总金额
@@ -669,87 +701,256 @@
        }
        order.setAmountClose(order.getAmountClose().add(profit));
        order.setVolume(order.getVolume().subtract(volume));
        order.setDeposit(order.getDeposit().subtract(order.getDepositOpen()));
        if (order.getVolume().compareTo(BigDecimal.ZERO) <= 0) {
        BigDecimal remainVolume = currentVolume.subtract(volume);
        BigDecimal remainDeposit = currentDeposit.subtract(releasedDeposit).max(BigDecimal.ZERO);
        BigDecimal remainBorrowed = currentBorrowed.subtract(currentBorrowed.multiply(closeRatio).setScale(8, RoundingMode.HALF_UP)).max(BigDecimal.ZERO);
        BigDecimal remainProfit = originProfit.subtract(realizedProfit).setScale(8, RoundingMode.HALF_UP);
        order.setVolume(remainVolume.max(BigDecimal.ZERO));
        if (remainVolume.compareTo(BigDecimal.ZERO) > 0) {
            order.setVolumeOpen(remainVolume);
            order.setDeposit(remainDeposit);
            order.setDepositOpen(remainDeposit);
            order.setBorrowedAmount(remainBorrowed);
            order.setProfit(remainProfit);
        } else {
            order.setState(ContractOrder.STATE_CREATED);
            order.setCloseTime(DateUtil.currentSeconds());
            order.setCloseTimeTs(DateUtil.currentSeconds());
            order.setDeposit(BigDecimal.ZERO);
            order.setBorrowedAmount(BigDecimal.ZERO);
            order.setProfit(originProfit);
        }
        order.setProfit(originProfit);
        return profit;
    }
    /**
     * 累计资金费带来的盈亏(正数表示用户获利):多仓为 -合约价值×费率×次数,空仓为 +合约价值×费率×次数。
     * 费率来自 syspara funding_fee;结算锚点为系统默认时区、从当天 0:00 起每 {@link #FUNDING_SETTLEMENT_INTERVAL_MINUTES} 分钟一档。
     */
    public BigDecimal calculateAccruedFundingPnl(ContractOrder order, BigDecimal markPrice, Date asOf) {
        if (order == null || markPrice == null || asOf == null || order.getCreateTime() == null) {
            return BigDecimal.ZERO;
        }
        BigDecimal vol = defaultZero(order.getVolume());
        if (vol.compareTo(BigDecimal.ZERO) <= 0 || markPrice.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
        long periods = countFundingSettlementPoints(
                order.getCreateTime().toInstant(),
                asOf.toInstant(),
                ZoneId.systemDefault(),
                FUNDING_SETTLEMENT_INTERVAL_MINUTES);
        if (periods <= 0) {
            return BigDecimal.ZERO;
        }
        BigDecimal notional = markPrice.multiply(vol);
        BigDecimal rate = getContractFundingRate();
        BigDecimal signed = ContractOrder.DIRECTION_BUY.equalsIgnoreCase(order.getDirection())
                ? rate.negate() : rate;
        return notional.multiply(signed).multiply(BigDecimal.valueOf(periods)).setScale(8, RoundingMode.HALF_UP);
    }
    public BigDecimal getContractFundingRate() {
        try {
            Syspara p = sysparaService.find("funding_fee");
            if (p == null || StringUtils.isEmptyString(p.getSvalue())) {
                return BigDecimal.ZERO;
            }
            return new BigDecimal(p.getSvalue().trim());
        } catch (Exception e) {
            log.warn("parse funding_fee syspara failed", e);
            return BigDecimal.ZERO;
        }
    }
    /**
     * 平仓手续费:手续费率×平仓价×数量(与开仓计费规则一致:U 本位用 unitPercentage,否则 unitFee×价×量)。
     */
    public BigDecimal calculateCloseTradingFee(Item item, BigDecimal closePrice, BigDecimal closeVolume) {
        if (item == null || closePrice == null || closeVolume == null
                || closeVolume.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
        Syspara syspara = sysparaService.find("u_standard_contract");
        if (ObjectUtils.isNotEmpty(syspara) && "1".equals(syspara.getSvalue())) {
            return closePrice.multiply(closeVolume).multiply(BigDecimal.valueOf(item.getUnitPercentage()))
                    .setScale(8, RoundingMode.HALF_UP);
        }
        return BigDecimal.valueOf(item.getUnitFee()).multiply(closePrice).multiply(closeVolume)
                .setScale(8, RoundingMode.HALF_UP);
    }
    private static ZonedDateTime nextFundingSettlementStrictlyAfter(ZonedDateTime t, int intervalMinutes) {
        int step = intervalMinutes <= 0 ? 240 : intervalMinutes;
        ZonedDateTime tTrunc = t.withSecond(0).withNano(0);
        ZonedDateTime dayStart = tTrunc.toLocalDate().atStartOfDay(tTrunc.getZone());
        long minutesFromDayStart = ChronoUnit.MINUTES.between(dayStart, tTrunc);
        long slotIndex = minutesFromDayStart / step;
        ZonedDateTime slotStart = dayStart.plusMinutes(slotIndex * step);
        ZonedDateTime next = slotStart;
        while (!next.isAfter(t)) {
            next = next.plusMinutes(step);
        }
        return next;
    }
    private static long countFundingSettlementPoints(Instant open, Instant end, ZoneId zone, int intervalMinutes) {
        if (open == null || end == null || !end.isAfter(open)) {
            return 0;
        }
        int step = intervalMinutes <= 0 ? 240 : intervalMinutes;
        ZonedDateTime zEnd = end.atZone(zone);
        ZonedDateTime cursor = nextFundingSettlementStrictlyAfter(open.atZone(zone), step);
        long cnt = 0;
        while (!cursor.isAfter(zEnd)) {
            cnt++;
            cursor = cursor.plusMinutes(step);
        }
        return cnt;
    }
    private BigDecimal defaultZero(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }
    private BigDecimal calculateBorrowedAmount(BigDecimal positionBase, BigDecimal leverRate) {
        BigDecimal safeBase = defaultZero(positionBase);
        BigDecimal safeLeverRate = defaultZero(leverRate);
        if (safeBase.compareTo(BigDecimal.ZERO) <= 0 || safeLeverRate.compareTo(BigDecimal.ONE) <= 0) {
            return BigDecimal.ZERO;
        }
        return safeBase.multiply(safeLeverRate.subtract(BigDecimal.ONE))
                .divide(safeLeverRate, 10, RoundingMode.HALF_UP);
    }
    private BigDecimal getCurrentBorrowedAmount(ContractOrder order) {
        if (order == null) {
            return BigDecimal.ZERO;
        }
        BigDecimal borrowedAmount = defaultZero(order.getBorrowedAmount());
        if (borrowedAmount.compareTo(BigDecimal.ZERO) > 0) {
            return borrowedAmount;
        }
        return calculateBorrowedAmount(order.getDeposit(), order.getLeverRate());
    }
    private boolean shouldMergeCryptoPosition(Item item) {
        return item != null && Item.cryptos.equalsIgnoreCase(item.getType());
    }
    private void mergeOpenPosition(ContractOrder order, ContractApplyOrder applyOrder, BigDecimal tradePrice) {
        BigDecimal oldVolume = defaultZero(order.getVolume());
        BigDecimal newVolume = defaultZero(applyOrder.getVolume());
        BigDecimal totalVolume = oldVolume.add(newVolume);
        if (totalVolume.compareTo(BigDecimal.ZERO) <= 0) {
            return;
        }
        BigDecimal weightedTradeAmount = defaultZero(order.getTradeAvgPrice()).multiply(oldVolume)
                .add(defaultZero(tradePrice).multiply(newVolume));
        BigDecimal avgTradePrice = weightedTradeAmount.divide(totalVolume, 10, RoundingMode.HALF_UP);
        order.setTradeAvgPrice(avgTradePrice);
        order.setVolume(totalVolume);
        order.setVolumeOpen(totalVolume);
        order.setDeposit(defaultZero(order.getDeposit()).add(defaultZero(applyOrder.getDeposit())));
        order.setDepositOpen(defaultZero(order.getDepositOpen()).add(defaultZero(applyOrder.getDeposit())));
        order.setFee(defaultZero(order.getFee()).add(defaultZero(applyOrder.getFee())));
        order.setBorrowedAmount(getCurrentBorrowedAmount(order)
                .add(calculateBorrowedAmount(applyOrder.getDeposit(), applyOrder.getLeverRate())));
        order.setLeverRate(applyOrder.getLeverRate());
        order.setOrderPriceType(applyOrder.getOrderPriceType());
        if (applyOrder.getStopPriceProfit() != null && applyOrder.getStopPriceProfit().compareTo(BigDecimal.ZERO) > 0) {
            order.setStopPriceProfit(applyOrder.getStopPriceProfit());
        }
        if (applyOrder.getStopPriceLoss() != null && applyOrder.getStopPriceLoss().compareTo(BigDecimal.ZERO) > 0) {
            order.setStopPriceLoss(applyOrder.getStopPriceLoss());
        }
    }
    @Transactional
    public void saveOpen(ContractApplyOrder applyOrder, Realtime realtime) {
        Item item = this.itemService.findBySymbol(applyOrder.getSymbol());
        BigDecimal tradePrice = BigDecimal.valueOf(realtime.getClose());
        ContractOrder order;
        List<ContractOrder> submittedOrders = shouldMergeCryptoPosition(item)
                ? this.findSubmitted(applyOrder.getPartyId(), applyOrder.getSymbol(), applyOrder.getDirection())
                : new ArrayList<>();
        if (CollectionUtil.isNotEmpty(submittedOrders)) {
            order = submittedOrders.get(0);
            mergeOpenPosition(order, applyOrder, tradePrice);
            update(order);
        } else {
            order = new ContractOrder();
            order.setPartyId(applyOrder.getPartyId());
            order.setSymbol(applyOrder.getSymbol());
            String orderNo = com.yami.trading.common.util.DateUtil.formatDate(new Date(), "yyMMddHHmmss") + RandomUtil.getRandomNum(8);
            order.setOrderNo(orderNo);
            order.setDirection(applyOrder.getDirection());
            order.setLeverRate(applyOrder.getLeverRate());
            order.setVolume(applyOrder.getVolume());
            order.setVolumeOpen(applyOrder.getVolumeOpen());
            order.setOrderPriceType(applyOrder.getOrderPriceType());
            order.setUnitAmount(applyOrder.getUnitAmount());
            order.setFee(applyOrder.getFee());
            order.setFundingFee(BigDecimal.ZERO);
            order.setBorrowedAmount(calculateBorrowedAmount(applyOrder.getDeposit(), applyOrder.getLeverRate()));
            order.setDeposit(applyOrder.getDeposit());
            order.setDepositOpen(applyOrder.getDeposit());
        ContractOrder order = new ContractOrder();
        order.setPartyId(applyOrder.getPartyId());
        order.setSymbol(applyOrder.getSymbol());
        String orderNo = com.yami.trading.common.util.DateUtil.formatDate(new Date(), "yyMMddHHmmss") + RandomUtil.getRandomNum(8);
        order.setOrderNo(orderNo);
        order.setDirection(applyOrder.getDirection());
        order.setLeverRate(applyOrder.getLeverRate());
        order.setVolume(applyOrder.getVolume());
        order.setVolumeOpen(applyOrder.getVolumeOpen());
        order.setOrderPriceType(applyOrder.getOrderPriceType());
        order.setUnitAmount(applyOrder.getUnitAmount());
        order.setFee(applyOrder.getFee());
        order.setDeposit(applyOrder.getDeposit());
        order.setDepositOpen(applyOrder.getDeposit());
            order.setTradeAvgPrice(tradePrice);
            order.setStopPriceProfit(applyOrder.getStopPriceProfit());
            order.setStopPriceLoss(applyOrder.getStopPriceLoss());
        order.setTradeAvgPrice(BigDecimal.valueOf(realtime.getClose()));
        order.setStopPriceProfit(applyOrder.getStopPriceProfit());
        order.setStopPriceLoss(applyOrder.getStopPriceLoss());
            order.setPips(BigDecimal.valueOf(item.getPips()));
            order.setPipsAmount(BigDecimal.valueOf(item.getPipsAmount()));
            order.setFollow(applyOrder.getFollow());
            save(order);
            RedisUtil.set(ContractRedisKeys.CONTRACT_ORDERNO + order.getOrderNo(), order);
        order.setPips(BigDecimal.valueOf(item.getPips()));
        order.setPipsAmount(BigDecimal.valueOf(item.getPipsAmount()));
        order.setFollow(applyOrder.getFollow());
        // 爆仓是爆整个钱包
//        BigDecimal forceClose = BigDecimal.ZERO;
//        BigDecimal base = order.getDepositOpen().multiply(order.getPips()).divide(order.getPipsAmount(), 10, RoundingMode.HALF_UP).divide(order.getVolume(),10, RoundingMode.HALF_UP);
//        if(order.getDirection().equalsIgnoreCase(ContractOrder.DIRECTION_BUY)){
//            forceClose = order.getTradeAvgPrice().subtract(base).setScale(item.getDecimals(), RoundingMode.HALF_UP);
//        }else if(order.getDirection().equalsIgnoreCase(ContractOrder.DIRECTION_SELL)) {
//            forceClose = order.getTradeAvgPrice().add(base).setScale(item.getDecimals(), RoundingMode.HALF_UP);
//        }
//        if(forceClose.compareTo(BigDecimal.ZERO) <0 ){
//            forceClose  =  BigDecimal.ZERO;
//        }
//        order.setForceClosePrice(forceClose.toPlainString());
        save(order);
        RedisUtil.set(ContractRedisKeys.CONTRACT_ORDERNO + order.getOrderNo(), order);
            Map<String, ContractOrder> map = RedisUtil
                    .get(ContractRedisKeys.CONTRACT_SUBMITTED_ORDER_PARTY_ID + order.getPartyId());
            if (map == null) {
                map = new ConcurrentHashMap<String, ContractOrder>();
            }
            map.put(order.getOrderNo(), order);
            RedisUtil.set(ContractRedisKeys.CONTRACT_SUBMITTED_ORDER_PARTY_ID + order.getPartyId(), map);
        Map<String, ContractOrder> map = RedisUtil
                .get(ContractRedisKeys.CONTRACT_SUBMITTED_ORDER_PARTY_ID + order.getPartyId());
        if (map == null) {
            map = new ConcurrentHashMap<String, ContractOrder>();
            // 获取单个订单的合约总资产、总保证金、总未实现盈利
            Map<String, BigDecimal> contractAssetsOrder = this.walletService.getMoneyContractByOrder(order);
            BigDecimal contractAssets = RedisUtil.get(ContractRedisKeys.CONTRACT_ASSETS_PARTY_ID + order.getPartyId());
            if (contractAssets == null) {
                contractAssets = BigDecimal.ZERO;
            }
            BigDecimal contractAssetsDeposit = RedisUtil.get(ContractRedisKeys.CONTRACT_ASSETS_DEPOSIT_PARTY_ID + order.getPartyId());
            if (contractAssetsDeposit == null) {
                contractAssetsDeposit = BigDecimal.ZERO;
            }
            BigDecimal contractAssetsProfit = RedisUtil.get(ContractRedisKeys.CONTRACT_ASSETS_PROFIT_PARTY_ID + order.getPartyId());
            if (contractAssetsProfit == null) {
                contractAssetsProfit = BigDecimal.ZERO;
            }
            RedisUtil.set(ContractRedisKeys.CONTRACT_ASSETS_PARTY_ID + order.getPartyId(),
                    contractAssets.add(contractAssetsOrder.get("money_contract")));
            RedisUtil.set(ContractRedisKeys.CONTRACT_ASSETS_DEPOSIT_PARTY_ID + order.getPartyId(),
                    contractAssetsDeposit.add(contractAssetsOrder.get("money_contract_deposit")));
            RedisUtil.set(ContractRedisKeys.CONTRACT_ASSETS_PROFIT_PARTY_ID + order.getPartyId(),
                    contractAssetsProfit.add(contractAssetsOrder.get("money_contract_profit")));
        }
        map.put(order.getOrderNo(), order);
        RedisUtil.set(ContractRedisKeys.CONTRACT_SUBMITTED_ORDER_PARTY_ID + order.getPartyId(), map);
        // 获取单个订单的合约总资产、总保证金、总未实现盈利
        Map<String, BigDecimal> contractAssetsOrder = this.walletService.getMoneyContractByOrder(order);
        BigDecimal contractAssets = RedisUtil.get(ContractRedisKeys.CONTRACT_ASSETS_PARTY_ID + order.getPartyId());
        if (contractAssets == null) {
            contractAssets = BigDecimal.ZERO;
        }
        BigDecimal contractAssetsDeposit = RedisUtil.get(ContractRedisKeys.CONTRACT_ASSETS_DEPOSIT_PARTY_ID + order.getPartyId());
        if (contractAssetsDeposit == null) {
            contractAssetsDeposit = BigDecimal.ZERO;
        }
        BigDecimal contractAssetsProfit = RedisUtil.get(ContractRedisKeys.CONTRACT_ASSETS_PROFIT_PARTY_ID + order.getPartyId());
        if (contractAssetsProfit == null) {
            contractAssetsProfit = BigDecimal.ZERO;
        }
        RedisUtil.set(ContractRedisKeys.CONTRACT_ASSETS_PARTY_ID + order.getPartyId(),
                contractAssets.add(contractAssetsOrder.get("money_contract")));
        RedisUtil.set(ContractRedisKeys.CONTRACT_ASSETS_DEPOSIT_PARTY_ID + order.getPartyId(),
                contractAssetsDeposit.add(contractAssetsOrder.get("money_contract_deposit")));
        RedisUtil.set(ContractRedisKeys.CONTRACT_ASSETS_PROFIT_PARTY_ID + order.getPartyId(),
                contractAssetsProfit.add(contractAssetsOrder.get("money_contract_profit")));
        ContractOrder traderOpenOrder = new ContractOrder();
        traderOpenOrder.setPartyId(order.getPartyId());
        traderOpenOrder.setOrderNo(order.getOrderNo());
        traderOpenOrder.setSymbol(order.getSymbol());
        traderOpenOrder.setDirection(order.getDirection());
        traderOpenOrder.setLeverRate(applyOrder.getLeverRate());
        traderOpenOrder.setVolume(applyOrder.getVolume());
        traderOpenOrder.setVolumeOpen(applyOrder.getVolumeOpen());
        traderOpenOrder.setUnitAmount(applyOrder.getUnitAmount());
        traderOpenOrder.setTradeAvgPrice(tradePrice);
        traderOpenOrder.setStopPriceProfit(applyOrder.getStopPriceProfit());
        traderOpenOrder.setStopPriceLoss(applyOrder.getStopPriceLoss());
        /**
         * 进入市场
@@ -769,7 +970,7 @@
         */
        Trader trader = traderService.findByPartyIdAndChecked(applyOrder.getPartyId(), 1); // 交易员存在
        if (trader != null) {
            traderFollowUserOrderService.traderOpen(order, contractApplyOrderService, this, 1); // 交易员跟随者开启永续合约委托, 加个跟单标识
            traderFollowUserOrderService.traderOpen(traderOpenOrder, contractApplyOrderService, this, 1); // 交易员跟随者开启永续合约委托, 加个跟单标识
        }
        /**
@@ -811,16 +1012,34 @@
             */
            return applyOrder;
        }
        BigDecimal preVolume = order.getVolume();
        contractOrderCalculationService.refreshMarkPriceProfit(order);
        BigDecimal volume;
        if (applyOrder.getVolume().compareTo(order.getVolume()) > 0) {
            volume = order.getVolume();
        } else {
            volume = applyOrder.getVolume();
        }
        /**
         * 平仓退回的金额
         */
        BigDecimal profit = this.settle(order, volume);
        BigDecimal markPrice = BigDecimal.valueOf(realtime.getClose());
        ContractOrderProfit cache = getCacheProfit(order.getUuid());
        if (cache != null && cache.getCloseAvgPrice() != null
                && cache.getCloseAvgPrice().compareTo(BigDecimal.ZERO) > 0) {
            markPrice = cache.getCloseAvgPrice();
        }
        BigDecimal fundingAccruedTotal = calculateAccruedFundingPnl(order, markPrice, new Date());
        BigDecimal fundingPnlChunk = fundingAccruedTotal.multiply(volume.divide(preVolume, 10, RoundingMode.HALF_UP))
                .setScale(8, RoundingMode.HALF_UP);
        Item item = itemService.findBySymbol(order.getSymbol());
        BigDecimal closeFee = calculateCloseTradingFee(item, markPrice, volume);
        BigDecimal profit = this.settle(order, volume).subtract(closeFee);
        BigDecimal netProfit = profit;
        order.setFee(defaultZero(order.getFee()).add(closeFee));
        order.setAmountClose(order.getAmountClose().subtract(closeFee));
        order.setFundingFee(defaultZero(order.getFundingFee()).add(fundingPnlChunk));
        update(order);
        Wallet wallet = this.walletService.findByUserId(order.getPartyId());
        BigDecimal amount_before = wallet.getMoney();
@@ -828,14 +1047,15 @@
        String symbol = order.getSymbol();
//        Item item = itemService.findBySymbol(symbol);
//        profit = exchangeRateService.getUsdtByType(profit, item.getType());
        if (wallet.getMoney().add(profit).compareTo(BigDecimal.ZERO) < 0) {
            profit = wallet.getMoney().negate();
        if (wallet.getMoney().add(netProfit).compareTo(BigDecimal.ZERO) < 0) {
            netProfit = wallet.getMoney().negate();
        }
        walletService.updateMoney(symbol, order.getPartyId(), profit, BigDecimal.ZERO,
        walletService.updateMoney(symbol, order.getPartyId(), netProfit, BigDecimal.ZERO,
                Constants.MONEYLOG_CATEGORY_CONTRACT, Constants.WALLET_USDT, Constants.MONEYLOG_CONTENT_CONTRACT_CLOSE, "平仓,平仓合约数[" + volume + "],订单号[" + order.getOrderNo() + "]");
        applyOrder.setVolume(applyOrder.getVolume().subtract(volume));
        applyOrder.setFundingFee(defaultZero(applyOrder.getFundingFee()).add(fundingPnlChunk));
        if (applyOrder.getVolume().compareTo(BigDecimal.ZERO) <= 0) {
            applyOrder.setState(ContractApplyOrder.STATE_CREATED);
        }
@@ -921,9 +1141,10 @@
            map.put("stop_price_loss", order.getStopPriceLoss());
        }
        map.put("state", order.getState());
        map.put("amount", order.getVolume().multiply(order.getUnitAmount()));
        map.put("amount_open", order.getVolumeOpen().multiply(order.getUnitAmount()));
        map.put("amount", order.getDeposit());
        map.put("amount_open", order.getDeposit());
        map.put("fee", order.getFee());
        map.put("funding_fee", order.getFundingFee());
        map.put("deposit", order.getDeposit());
        map.put("deposit_open", order.getDepositOpen());
        map.put("change_ratio", order.getChangeRatio());