From befbf57e4112d07003bff18102f556a1e5a154de Mon Sep 17 00:00:00 2001
From: zj <1772600164@qq.com>
Date: Wed, 22 Apr 2026 10:53:37 +0800
Subject: [PATCH] 1

---
 trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderService.java |  554 +++++++++++++++++++++++++++++++++++++++++--------------
 1 files changed, 411 insertions(+), 143 deletions(-)

diff --git a/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderService.java b/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderService.java
index f29d4a5..cea79b3 100644
--- a/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderService.java
+++ b/trading-order-service/src/main/java/com/yami/trading/service/contract/ContractOrderService.java
@@ -16,10 +16,10 @@
 import com.yami.trading.bean.contract.domain.ContractOrder;
 import com.yami.trading.bean.contract.domain.ContractOrderProfit;
 import com.yami.trading.bean.contract.dto.ContractOrderDTO;
+import com.yami.trading.bean.contract.dto.TraderOwnClosedAggDTO;
 import com.yami.trading.bean.contract.query.ContractOrderQuery;
 import com.yami.trading.bean.data.domain.Realtime;
 import com.yami.trading.bean.item.domain.Item;
-import com.yami.trading.bean.model.FollowWallet;
 import com.yami.trading.bean.model.User;
 import com.yami.trading.bean.model.Wallet;
 import com.yami.trading.bean.syspara.domain.Syspara;
@@ -31,12 +31,12 @@
 import com.yami.trading.common.constants.ContractRedisKeys;
 import com.yami.trading.common.constants.RedisKeys;
 import com.yami.trading.common.constants.TipConstants;
+import com.yami.trading.common.util.Arith;
 import com.yami.trading.common.util.DateUtils;
 import com.yami.trading.common.util.RandomUtil;
 import com.yami.trading.common.util.RedisUtil;
 import com.yami.trading.common.util.StringUtils;
 import com.yami.trading.dao.contract.ContractOrderMapper;
-import com.yami.trading.service.FollowWalletService;
 import com.yami.trading.service.WalletService;
 import com.yami.trading.service.item.ItemService;
 import com.yami.trading.service.syspara.SysparaService;
@@ -63,8 +63,13 @@
 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.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
@@ -86,6 +91,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);
 
@@ -134,10 +145,6 @@
     private WalletService walletService;
 
     @Autowired
-    private FollowWalletService followWalletService;
-
-
-    @Autowired
     private UserService userService;
     @Autowired
     private TipService tipService;
@@ -161,6 +168,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) {
@@ -226,6 +237,89 @@
         queryWrapper.orderByDesc("create_time");
         return list(queryWrapper);
     }
+
+    /**
+     * 仅查询交易员本人持仓(排除其作为跟单用户产生的跟单订单)。
+     */
+    public List<ContractOrder> findSubmittedTraderOwn(String partyId, String symbol) {
+        QueryWrapper<ContractOrder> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq(StrUtil.isNotBlank(partyId), "party_id", partyId);
+        queryWrapper.eq(StrUtil.isNotBlank(symbol), "symbol", symbol);
+        queryWrapper.eq("state", ContractOrder.STATE_SUBMITTED);
+        // 交易员本人持仓:排除明确的跟单单(follow=1),并兼容历史数据 follow 为空
+        queryWrapper.and(w -> w.ne("follow", ContractOrder.ORDER_FOLLOW).or().isNull("follow"));
+        queryWrapper.orderByDesc("create_time");
+        return list(queryWrapper);
+    }
+
+	/**
+	 * 交易员本人、已平仓、非跟单单:全品种已实现盈亏合计(与 T_TRADER.profit 相比以合约表为准)。
+	 */
+	public BigDecimal sumClosedProfitTraderOwn(String partyId) {
+		if (StrUtil.isBlank(partyId)) {
+			return BigDecimal.ZERO;
+		}
+		Map<String, TraderOwnClosedAggDTO> m = mapClosedTraderOwnAggByPartyIds(Collections.singletonList(partyId));
+		TraderOwnClosedAggDTO row = m.get(partyId);
+		if (row == null || row.getClosedProfitSum() == null) {
+			return BigDecimal.ZERO;
+		}
+		return row.getClosedProfitSum();
+	}
+
+	/**
+	 * 交易员本人、已平仓、非跟单单:全品种开仓保证金 deposit_open 合计(与当前持仓 deposit 同一口径,用于收益率分母)。
+	 */
+	public BigDecimal sumClosedDepositOpenTraderOwn(String partyId) {
+		if (StrUtil.isBlank(partyId)) {
+			return BigDecimal.ZERO;
+		}
+		Map<String, TraderOwnClosedAggDTO> m = mapClosedTraderOwnAggByPartyIds(Collections.singletonList(partyId));
+		TraderOwnClosedAggDTO row = m.get(partyId);
+		if (row == null || row.getClosedMarginSum() == null) {
+			return BigDecimal.ZERO;
+		}
+		return row.getClosedMarginSum();
+	}
+
+	public Map<String, TraderOwnClosedAggDTO> mapClosedTraderOwnAggByPartyIds(List<String> partyIds) {
+		if (partyIds == null || partyIds.isEmpty()) {
+			return Collections.emptyMap();
+		}
+		List<String> distinct = partyIds.stream().filter(StrUtil::isNotBlank).distinct().collect(Collectors.toList());
+		if (distinct.isEmpty()) {
+			return Collections.emptyMap();
+		}
+		List<TraderOwnClosedAggDTO> rows = getBaseMapper().sumClosedTraderOwnAggByPartyIds(distinct);
+		if (rows == null || rows.isEmpty()) {
+			return Collections.emptyMap();
+		}
+		return rows.stream()
+				.filter(r -> r != null && StrUtil.isNotBlank(r.getPartyId()))
+				.collect(Collectors.toMap(TraderOwnClosedAggDTO::getPartyId, r -> r, (a, b) -> a));
+	}
+
+	/**
+	 * 累计收益/收益率:历史已实现盈亏(合约表全品种)+ 偏差;历史分母优先用已平仓保证金合计(与当前持仓保证金一致),否则回退 T_TRADER.order_amount。
+	 */
+	public double historyProfitForTraderTotalYield(Trader entity) {
+		if (entity == null || StrUtil.isBlank(entity.getPartyId())) {
+			return 0D;
+		}
+		double closed = sumClosedProfitTraderOwn(entity.getPartyId()).doubleValue();
+		return Arith.add(closed, entity.getDeviationProfit());
+	}
+
+	public double historyAmountBasisForTraderTotalYield(Trader entity) {
+		if (entity == null) {
+			return 0D;
+		}
+		BigDecimal marginSum = sumClosedDepositOpenTraderOwn(entity.getPartyId());
+		if (marginSum != null && marginSum.compareTo(BigDecimal.ZERO) > 0) {
+			return Arith.add(marginSum.doubleValue(), entity.getDeviationOrderAmount());
+		}
+		return Arith.add(entity.getOrderAmount(), entity.getDeviationOrderAmount());
+	}
 
 
     public List<ContractOrder> findSubmitted(String partyId, String symbol, String direction, String startTime, String endTime, String symbolType) {
@@ -373,44 +467,38 @@
             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()) { // 跟单订单
-//		if (profit > 0) {
-            FollowWallet wallet = followWalletService.findByUserId(order.getPartyId());
-            BigDecimal amount_before = wallet.getMoney();
-
-//		wallet.setMoney(Arith.add(wallet.getMoney(), profit));
-
-            if (wallet.getMoney().add(profit).compareTo(BigDecimal.ZERO) < 0) {
-                profit = wallet.getMoney().negate();
-            }
-
-            followWalletService.updateMoney(order.getSymbol(), partyId, profit, BigDecimal.ZERO,
-                    Constants.MONEYLOG_CATEGORY_CONTRACT, Constants.WALLET_USDT, Constants.MONEYLOG_CONTENT_CONTRACT_CLOSE, "平仓,平仓合约数[" + volume + "],订单号[" + order.getOrderNo() + "]");
-        } else {
-            //		if (profit > 0) {
-            Wallet wallet = walletService.findByUserId(order.getPartyId());
-            BigDecimal amount_before = wallet.getMoney();
-
-//		wallet.setMoney(Arith.add(wallet.getMoney(), profit));
-            if (wallet.getMoney().add(profit).compareTo(BigDecimal.ZERO) < 0) {
-                profit = wallet.getMoney().negate();
-            }
-            walletService.updateMoney(order.getSymbol(), partyId, profit, BigDecimal.ZERO,
-                    Constants.MONEYLOG_CATEGORY_CONTRACT, Constants.WALLET_USDT, Constants.MONEYLOG_CONTENT_CONTRACT_CLOSE, "平仓,平仓合约数[" + volume + "],订单号[" + order.getOrderNo() + "]");
+        Wallet wallet = walletService.findByUserId(order.getPartyId());
+        if (wallet.getMoney().add(profit).compareTo(BigDecimal.ZERO) < 0) {
+            profit = wallet.getMoney().negate();
         }
-        order.setState(ContractOrder.STATE_CREATED);
-        order.setVolume(BigDecimal.ZERO);
-        order.setDeposit(BigDecimal.ZERO);
-        order.setCloseTime(DateUtil.currentSeconds());
-        order.setCloseTimeTs(DateUtil.currentSeconds());
+        walletService.updateMoney(order.getSymbol(), partyId, profit, BigDecimal.ZERO,
+                Constants.MONEYLOG_CATEGORY_CONTRACT, Constants.WALLET_USDT, Constants.MONEYLOG_CONTENT_CONTRACT_CLOSE, "平仓,平仓合约数[" + volume + "],订单号[" + order.getOrderNo() + "]");
 //        List<Realtime> list = this.dataService.realtime(order.getSymbol());
 //        // 平仓时候把当前价格先更新回去
 //        if (list.size() != 0) {
@@ -423,6 +511,10 @@
         }
 
         update(order);
+
+        if (ContractOrder.STATE_CREATED.equals(order.getState())) {
+            traderFollowUserOrderService.syncFollowUserOrderLinkAfterContractClose(order);
+        }
 
         /**
          * 交易员带单,用户跟单
@@ -466,6 +558,7 @@
             if (cacheProfit != null) {
                 contractOrder.setProfit(cacheProfit.getProfit());
                 contractOrder.setCloseAvgPrice(cacheProfit.getCloseAvgPrice());
+                contractOrder.setForceClosePrice(cacheProfit.getForceClosePrice());
             } else {
                 contractOrder.setProfit(BigDecimal.ZERO);
 
@@ -630,11 +723,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,114 +730,270 @@
             order.setCloseAvgPrice(cacheProfit.getCloseAvgPrice());
         }
 
-        BigDecimal profit = order.getDeposit().add(originProfit);
-
-        if (ContractOrder.ORDER_FOLLOW == order.getFollow()) { // 跟单还得减去利息收益
-            BigDecimal orderAmount = order.getUnitAmount().multiply(order.getTradeAvgPrice()).multiply(order.getLeverRate()); //订单总金额
-            TraderFollowUserOrder traderFollowUserOrder = traderFollowUserOrderService.findByPartyIdAndOrderNo(order.getPartyId(), order.getOrderNo());
-            if (null != traderFollowUserOrder) {
-                TraderFollowUser traderFollowUser = traderFollowUserService.findByPartyIdAndTrader_partyId(order.getPartyId(), traderFollowUserOrder.getTraderPartyId());
-                if (StringUtils.isNotEmpty(traderFollowUser.getDaysSetting())) {
-                    TraderDaysSetting traderDaysSetting = traderDaysSettingService.selectById(traderFollowUser.getDaysSetting());
-                    if (null != traderDaysSetting) { // 借款利率
-                        int days = 0;
-                        try {
-                            days = daysBetween(order.getCreateTime(), new Date());
-                        } catch (ParseException e) {
-//                            throw new RuntimeException(e);
-                            log.error(e.getMessage());
-                        }
-                        if (days < 0) {
-                            days = 0;
-                        }
-                    }
-                }
-            }
-
+        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);
+
+        // 跟单利息功能已下线,此处不再做历史利率扣减
 
         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 +1013,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 +1055,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 +1090,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);
         }
@@ -871,6 +1134,10 @@
             data.add(map);
         }
         return data;
+    }
+
+    public List<Map<String, Object>> buildDataFromOrders(List<ContractOrder> list) {
+        return bulidData(list);
     }
 
     public Map<String, Object> bulidOne(ContractOrder order) {
@@ -921,9 +1188,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());

--
Gitblit v1.9.3