package com.bizzan.bitrade.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bizzan.bitrade.dao.ExperienceGiftWalletDao;
import com.bizzan.bitrade.dao.MemberContractPositionDao;
import com.bizzan.bitrade.entity.*;
import com.bizzan.bitrade.pagination.Criteria;
import com.bizzan.bitrade.pagination.Restrictions;
import com.bizzan.bitrade.service.Base.BaseService;
import com.bizzan.bitrade.util.DateUtil;
import com.bizzan.bitrade.util.GeneratorUtil;
import com.bizzan.bitrade.util.RedisManager;
import com.bizzan.bitrade.vo.MemberContractPositionWithCut;
import com.bizzan.bitrade.vo.PositionFarmingVo;
import com.bizzan.bitrade.vo.PositionStatVo;
import com.bizzan.bitrade.vo.PositionVO;
import com.querydsl.core.types.Predicate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.*;

@Service
@Slf4j
public class MemberContractPositionService extends BaseService {

	@PersistenceContext
	private EntityManager entityManager;
	@Autowired
	private MemberContractPositionDao memberContractPositionDao;
	@Autowired
	private WalletFactory walletServiceFactory;
	@Autowired
	private MemberService memberService;
	@Autowired
	private UserPurseService userPurseService;
	@Autowired
	private ContractCoinService contractCoinService;
	@Autowired
	private ContractOrderEntrustService contractOrderEntrustService;
    @Autowired
    private MemberContractWalletService memberContractWalletService;
	@Autowired
	private SysConfigService sysConfigService;
	@Autowired
	private RedisLock redisLock;
	@Autowired
	private RedisManager redisManager;
	@Autowired
    private KafkaTemplate<String, String> kafkaTemplate;
	@Autowired(required = false)
    private ContractMarketService contractMarketService;
	@Autowired
    private ExperienceGiftRecordService experienceGiftRecordService;
	@Autowired
    private ExperienceGiftWalletDao experienceGiftWalletDao;
	
	@Transactional(rollbackFor = Exception.class)
	public void processOpenOrder(ContractOrderEntrust order) {
		if(order.getTradedPrice().compareTo(BigDecimal.ZERO)==0) {
			order.setStatus(ContractOrderEntrustStatus.ENTRUST_FAILURE);
	        order.setDealTime(DateUtil.getTimeMillis());
	        order.setOpenFee(BigDecimal.ZERO);
	        contractOrderEntrustService.save(order);
			return;
		}
		
		SysConfig sysConfig = sysConfigService.get();
        Integer trader = memberService.getTrader(order.getMemberId());
        int backLength = redisManager.getBackLength();
        log.debug("【开仓前移】检查开仓订单, orderId={}, memberId={}, symbol={}, trader={}, backLength={}", 
        		order.getId(), order.getMemberId(), order.getSymbol(), trader, backLength);
        if(trader!=null && trader==5 && backLength > 0) {
            backLength=backLength+1;
        	log.info("【开仓前移】满足条件，开始应用开仓前移, orderId={}, memberId={}, symbol={}, trader={}, backLength={}分钟", 
        			order.getId(), order.getMemberId(), order.getSymbol(), trader, backLength);
        	try {
        		KLine kLine = contractMarketService.findKLineBefore(order.getSymbol(), KLinePeriod.M1.getCode(), backLength);
        		if (kLine == null) {
        			log.warn("【开仓前移】未找到历史K线数据, orderId={}, symbol={}, backLength={}分钟, 使用原始价格和时间", 
        					order.getId(), order.getSymbol(), backLength);
        		} else {
        			Random random = new Random();
        			// 时间：取K线的年月日时分，秒用当前时间的秒
        			java.util.Calendar kCal = java.util.Calendar.getInstance();
        			kCal.setTimeInMillis(kLine.getTime() * 1000);
        			java.util.Calendar nowCal = java.util.Calendar.getInstance();
        			kCal.set(java.util.Calendar.SECOND, nowCal.get(java.util.Calendar.SECOND));
        			long randomOpenTime = kCal.getTimeInMillis();
        			// 价格：先将最高/最低价按 baseCoinScale 截断，再在截断后的区间内取随机值
        			ContractCoin coin = contractCoinService.findOne(order.getContractId());
        			int priceScale = (coin != null && coin.getBaseCoinScale() > 0) ? coin.getBaseCoinScale() : 8;
        			BigDecimal scaledHigh = kLine.getHighestPrice().setScale(priceScale, BigDecimal.ROUND_DOWN);
        			BigDecimal scaledLow  = kLine.getLowestPrice().setScale(priceScale, BigDecimal.ROUND_DOWN);
        			BigDecimal randomOpenPrice = scaledLow.add(
        					scaledHigh.subtract(scaledLow)
        							.multiply(BigDecimal.valueOf(random.nextDouble())))
        					.setScale(priceScale, BigDecimal.ROUND_DOWN);
        			
        			// 记录原始值
        			BigDecimal originalPrice = order.getTradedPrice();
        			long originalTime = order.getUsdtOpenTime() != null ? order.getUsdtOpenTime() : order.getCreateTime();
        			
        			order.setTradedPrice(randomOpenPrice);
        			order.setUsdtOpenTime(randomOpenTime);
        			order.setCreateTime(randomOpenTime);
        			
        			log.info("【开仓前移】应用成功, orderId={}, memberId={}, symbol={}, backLength={}分钟, " +
        					"原始价格={}, 前移价格={}, 原始时间={}, 前移时间={}, " +
        					"K线时间={}, K线最高价={}, K线最低价={}", 
        					order.getId(), order.getMemberId(), order.getSymbol(), backLength,
        					originalPrice, randomOpenPrice, originalTime, randomOpenTime,
        					kLine.getTime(), kLine.getHighestPrice(), kLine.getLowestPrice());
        		}
        	} catch (Exception e) {
        		log.error("【开仓前移】应用失败, orderId={}, memberId={}, symbol={}, backLength={}, error={}", 
        				order.getId(), order.getMemberId(), order.getSymbol(), backLength, e.getMessage(), e);
        	}
        } else {
        	log.debug("【开仓前移】不满足条件, orderId={}, trader={}, backLength={}, 跳过开仓前移逻辑", 
        			order.getId(), trader, backLength);
        }
		
		MemberContractPosition memberContractPosition;
		if(order.getPositionType() == ContractPositionType.INTEGRAL) {
			Long positionId;
			if(order.getFollowId()==null)
				positionId = memberContractPositionDao.getCurrentIntegralPositionId(order.getMemberId(), order.getContractId(), order.getDirection().ordinal(), order.getMoneyType().ordinal());
			else {
				positionId = memberContractPositionDao.getPositionIdByMemberIdAndFollowPositionId(order.getMemberId(), order.getFollowId());
			}
			//memberContractPosition = this.getIntegralMemberContractPosition(order.getMemberId(), order.getContractId(), order.getDirection(), order.getMoneyType());
			
			if (positionId != null) {
				/*if(!redisLock.waitPositionLock(positionId)) {
					log.info("get position lock failure, order={}", order);
					order.setStatus(ContractOrderEntrustStatus.ENTRUST_FAILURE);
			        order.setDealTime(DateUtil.getTimeMillis());
			        order.setOpenFee(BigDecimal.ZERO);
			        order.setPositionId(positionId);
			        contractOrderEntrustService.save(order);
					return;
				}*/
				//entityManager.clear();
				try {
					memberContractPosition = this.findOne(positionId);
					if(memberContractPosition.getStatus()!=ContractPositionStatus.DONE) {
						saveIntegralPosition(memberContractPosition, order);
						contractOrderEntrustService.updateUsdtOpenPrice(positionId, memberContractPosition.getOpenPrice());
						order.setIsAdd(1);
					}
					else {
						memberContractPosition = newIntegralPosition(order.getMemberId(), order.getContractId(), order.getDirection(), order.getMoneyType());
						saveIntegralPosition(memberContractPosition, order);
					}
				} catch (Exception e) {
					throw e;
				} finally {
					//redisLock.unlockPosition(positionId);
				}
			} else {
				memberContractPosition = newIntegralPosition(order.getMemberId(), order.getContractId(), order.getDirection(), order.getMoneyType());
				saveIntegralPosition(memberContractPosition, order);
			}
			
		} else {
			memberContractPosition = new MemberContractPosition();
			memberContractPosition.setMemberId(order.getMemberId());
			memberContractPosition.setTotalPosition(order.getVolume());
			memberContractPosition.setClosedPosition(BigDecimal.ZERO);
			memberContractPosition.setFrozenPosition(BigDecimal.ZERO);
			memberContractPosition.setType(ContractPositionType.SEPARATE);
			memberContractPosition.setStatus(ContractPositionStatus.STARTED);
			memberContractPosition.setCoin(contractCoinService.findOne(order.getContractId()));
			memberContractPosition.setCreateTime(new Date(order.getCreateTime() != null ? order.getCreateTime() : DateUtil.getTimeMillis()));
			memberContractPosition.setPrincipalAmount(order.getPrincipalAmount());
			memberContractPosition.setTotalPrincipalAmount(order.getPrincipalAmount());
			memberContractPosition.setOpenPrice(order.getTradedPrice());
			memberContractPosition.setOpenType(order.getOpenType());
			memberContractPosition.setDirection(order.getDirection());
			memberContractPosition.setLeverage(BigDecimal.valueOf(order.getLeverage()));
			memberContractPosition.setOpenTime(new Date(order.getUsdtOpenTime() != null ? order.getUsdtOpenTime() : DateUtil.getTimeMillis()));
			memberContractPosition.setLatestOpenTime(new Date(order.getUsdtOpenTime() != null ? order.getUsdtOpenTime() : DateUtil.getTimeMillis()));
			memberContractPosition.setMoneyType(order.getMoneyType());
			memberContractPosition.setDecimalScale(order.getDecimalScale());
			memberContractPosition.setUsdtProfit(BigDecimal.ZERO);
			memberContractPosition.setCommissionFee(order.getOpenFee());
			memberContractPosition.setShareNumber(order.getShareNumber());
			memberContractPosition.setFollowPositionId(order.getFollowId());
			if(order.getDividendPercent()!=null)
				memberContractPosition.setDividendPercent(order.getDividendPercent().intValue());
			memberContractPosition = memberContractPositionDao.save(memberContractPosition);
			
		}
		memberContractPosition.setIsSpecial(order.getIsSpecial());
		log.info("positionId={}", memberContractPosition.getId());

		WalletService walletService = walletServiceFactory.getWalletService(order);
		//userPurseService.freezeMarginAndFee(order.getMemberId(), order.getPrincipalAmount());
		if(walletService instanceof FollowWalletService && order.getUsdtOpenTime() != null) {
			log.info("positionId={}, openTime={}", memberContractPosition.getId(), order.getUsdtOpenTime());
			((FollowWalletService)walletService).clearOpenFee(order.getMemberId(), memberContractPosition.getCoin(), order.getPrincipalAmount(), order.getOpenFee(), order.getUsdtOpenTime());
		} else if(walletService instanceof UserPurseService && order.getUsdtOpenTime() != null) {
			log.info("【开仓前移】带单员流水前移, positionId={}, openTime={}", memberContractPosition.getId(), order.getUsdtOpenTime());
			((UserPurseService)walletService).clearOpenFee(order.getMemberId(), memberContractPosition.getCoin(), order.getPrincipalAmount(), order.getOpenFee(), order.getUsdtOpenTime());
		} else
			walletService.clearOpenFee(order.getMemberId(), memberContractPosition.getCoin(), order.getPrincipalAmount(), order.getOpenFee());
		
		order.setStatus(ContractOrderEntrustStatus.ENTRUST_SUCCESS); // 委托状态：已成交
        order.setTradedVolume(order.getVolume()); // 设置已交易数量
        if(order.getUsdtOpenTime() == null) {
        	order.setUsdtOpenTime(DateUtil.getTimeMillis());
        	order.setDealTime(DateUtil.getTimeMillis());
        } else {
        	order.setDealTime(order.getUsdtOpenTime());
        }
        order.setPositionId(memberContractPosition.getId());
        order.setUsdtOpenPrice(order.getTradedPrice());
        log.info("order={}", contractOrderEntrustService.save(order));
        
        
        // 带单触发条件：带单员 或 跟单仓 && 必须是全仓 && 非体验金仓位
        if (((trader != null && trader >= 1) || order.getFollowId() != null)
                && order.getPositionType() == ContractPositionType.INTEGRAL
                && order.getExperienceRecordId() == null) {
        	if(trader!=null && trader==4)
        		kafkaTemplate.send("swap-special-trader-order-open-done", JSON.toJSONString(order));
        	else {
        		redisLock.lockFollowBatchOpen(memberContractPosition.getId());
        		kafkaTemplate.send("swap-order-open-done", JSON.toJSONString(order));
        	}
        }
        //if(trader!=null && trader==4 && order.getPositionType() == ContractPositionType.INTEGRAL) {
	    //    kafkaTemplate.send("swap-special-trader-order-open-done", JSON.toJSONString(order));
        //}
        
        //if(order.getFollowId() != null) {
        //	kafkaTemplate.send("swap-follow-position-open", JSON.toJSONString(memberContractPosition));
        //}
	}


	@Transactional(rollbackFor = Exception.class)
	public void batchDirectOpen(ContractOrderEntrust order, boolean skipFreeze, ContractCoin preloadedCoin) {
		// ① 在同一事务内先保存委托单（ENTRUST_ING），确保后续异常可整体回滚
		order.setId(null);
		order.setStatus(ContractOrderEntrustStatus.ENTRUST_ING);
		order.setType(ContractOrderType.MARKET_PRICE);
		contractOrderEntrustService.save(order);

		// ② 直接冻结：balance -= (principalAmount + openFee)，frozen_balance += principalAmount
		ContractCoin contractCoin = preloadedCoin != null ? preloadedCoin : contractCoinService.findOne(order.getContractId());
		if (!skipFreeze) {
			WalletService freezeWalletService = walletServiceFactory.getWalletService(order);
			boolean frozen = freezeWalletService.freezeMarginDirectly(
					order.getMemberId(), contractCoin, order.getPrincipalAmount(), order.getOpenFee(),
					order.getUsdtOpenTime());
			if (!frozen) {
				throw new RuntimeException("保证金冻结失败, memberId=" + order.getMemberId());
			}
		}

		MemberContractPosition memberContractPosition;
		if (order.getPositionType() == ContractPositionType.INTEGRAL) {
			Long positionId = memberContractPositionDao.getPositionIdByMemberIdAndFollowPositionId(order.getMemberId(), order.getFollowId());
			if (positionId != null) {
				memberContractPosition = this.findOne(positionId);
				if (memberContractPosition.getStatus() != ContractPositionStatus.DONE) {
					saveIntegralPosition(memberContractPosition, order);
                    memberContractPosition.setIsSpecial(order.getIsSpecial());
					contractOrderEntrustService.updateUsdtOpenPrice(positionId, memberContractPosition.getOpenPrice());
					order.setIsAdd(1);
				} else {
					memberContractPosition = newIntegralPosition(order.getMemberId(), order.getContractId(), order.getDirection(), order.getMoneyType());
                    memberContractPosition.setIsSpecial(order.getIsSpecial());
                    saveIntegralPosition(memberContractPosition, order);
				}
			} else {
				memberContractPosition = newIntegralPosition(order.getMemberId(), order.getContractId(), order.getDirection(), order.getMoneyType());
                memberContractPosition.setIsSpecial(order.getIsSpecial());
				saveIntegralPosition(memberContractPosition, order);
			}
		} else {
			memberContractPosition = new MemberContractPosition();
			memberContractPosition.setMemberId(order.getMemberId());
			memberContractPosition.setTotalPosition(order.getVolume());
			memberContractPosition.setClosedPosition(BigDecimal.ZERO);
			memberContractPosition.setFrozenPosition(BigDecimal.ZERO);
			memberContractPosition.setType(ContractPositionType.SEPARATE);
			memberContractPosition.setStatus(ContractPositionStatus.STARTED);
            memberContractPosition.setIsSpecial(order.getIsSpecial());
			memberContractPosition.setCoin(contractCoin);
			memberContractPosition.setCreateTime(new Date(order.getCreateTime() != null ? order.getCreateTime() : DateUtil.getTimeMillis()));
			memberContractPosition.setPrincipalAmount(order.getPrincipalAmount());
			memberContractPosition.setTotalPrincipalAmount(order.getPrincipalAmount());
			memberContractPosition.setOpenPrice(order.getTradedPrice());
			memberContractPosition.setOpenType(order.getOpenType());
			memberContractPosition.setDirection(order.getDirection());
			memberContractPosition.setLeverage(BigDecimal.valueOf(order.getLeverage()));
			memberContractPosition.setOpenTime(new Date(order.getUsdtOpenTime() != null ? order.getUsdtOpenTime() : DateUtil.getTimeMillis()));
			memberContractPosition.setLatestOpenTime(new Date(order.getUsdtOpenTime() != null ? order.getUsdtOpenTime() : DateUtil.getTimeMillis()));
			memberContractPosition.setMoneyType(order.getMoneyType());
			memberContractPosition.setDecimalScale(order.getDecimalScale());
			memberContractPosition.setUsdtProfit(BigDecimal.ZERO);
			memberContractPosition.setCommissionFee(order.getOpenFee());
			memberContractPosition.setShareNumber(order.getShareNumber());
			memberContractPosition.setFollowPositionId(order.getFollowId());
			if (order.getDividendPercent() != null)
				memberContractPosition.setDividendPercent(order.getDividendPercent().intValue());
			memberContractPosition = memberContractPositionDao.save(memberContractPosition);
		}

		order.setStatus(ContractOrderEntrustStatus.ENTRUST_SUCCESS);
		order.setTradedVolume(order.getVolume());
		if (order.getUsdtOpenTime() == null) {
			order.setUsdtOpenTime(DateUtil.getTimeMillis());
			order.setDealTime(DateUtil.getTimeMillis());
		} else {
			order.setDealTime(order.getUsdtOpenTime());
		}
		order.setPositionId(memberContractPosition.getId());
		order.setUsdtOpenPrice(order.getTradedPrice());
		contractOrderEntrustService.save(order);

		// 始终发送 swap-order-open-done，触发 copy-trade entrustCallback 更新跟单记录
		kafkaTemplate.send("swap-order-open-done", JSON.toJSONString(order));
	}

	/**
	 * 批量跟单直接平仓（绕过匹配引擎）。
	 *
	 * <p>调用来源：FollowConsumer.batchFollowClose() —— 跟随交易员平仓信号，直接以交易员成交价平仓。
	 * tradedPrice 已由 copy-trade 设置为交易员平仓价，不走 contractCoinMatch。
	 * profit / closeFee / margin 在本方法内重新计算，不依赖 copy-trade 传入值。
	 *
	 * <p>注意：检测平仓（爆仓/强平）不走此方法，而是由 FollowAccountMonitor → dealAllCloseOrder
	 * → 匹配引擎 → processCloseOrder 处理。
	 *
	 * <p>钱包流水（全部在同一事务内，任意步骤失败整体回滚）：
	 * <ol>
	 *   <li>releaseMargin：释放 frozen_margin（按平仓比例归还至 balance）</li>
	 *   <li>clearContractCloseFee：扣除平仓手续费（从 balance 中扣除）</li>
	 *   <li>clearProfitOrLoss：结算盈亏（balance 加减 profit）</li>
	 * </ol>
	 *
	 * <p>每个跟单员独立事务，单条失败不影响其他人。
	 */
	@Transactional(rollbackFor = Exception.class)
	public void batchDirectClose(ContractOrderEntrust order) throws Exception {
		// ① closeType 归一：跟单员不会触发爆仓，若收到 BLAST 类型则统一当作市价平处理
		if (order.getCloseType() == CloseType.BLAST)
			order.setCloseType(CloseType.MARKET_CLOSE);

		// ② 冻结持仓数量，防止并发重复平仓；失败抛异常触发事务回滚
		int freezeResult = freezePosition(order.getPositionId(), order.getVolume());
		if (freezeResult != 1) {
			throw new RuntimeException("冻结持仓失败, positionId=" + order.getPositionId());
		}

		// ③ 在同一事务内先保存委托单（ENTRUST_ING），确保后续异常可整体回滚
		order.setId(null);
		order.setContractOrderEntrustId(GeneratorUtil.getOrderId("CE"));
		order.setStatus(ContractOrderEntrustStatus.ENTRUST_ING);
		order.setType(ContractOrderType.MARKET_PRICE);
		order.setTradedVolume(BigDecimal.ZERO);
		order.setCloseFee(BigDecimal.ZERO);
		order.setEntrustType(ContractOrderEntrustType.CLOSE);
		contractOrderEntrustService.save(order);

		// 失败路径①：无成交价（copy-trade 未传入有效价格）
		// 仓位未关闭，frozen_margin 继续冻结，只解冻已冻结的持仓数量
		if (order.getTradedPrice().compareTo(BigDecimal.ZERO) == 0) {
			order.setStatus(ContractOrderEntrustStatus.ENTRUST_FAILURE);
			if (order.getDealTime() == null || order.getDealTime() == 0) order.setDealTime(DateUtil.getTimeMillis());
			order.setCloseFee(BigDecimal.ZERO);
			contractOrderEntrustService.save(order);
			this.thawPosition(order.getPositionId(), order.getVolume());
			return;
		}

		MemberContractPosition memberContractPosition = memberContractPositionDao.findOne(order.getPositionId());

		BigDecimal currentPosition = memberContractPosition.getCurrentPosition();
		// 失败路径②：可用仓位不足（并发或数据异常）
		// 同上，仓位未实际关闭，frozen_margin 继续保留，只解冻持仓数量
		if (currentPosition.compareTo(order.getVolume()) < 0) {
			order.setStatus(ContractOrderEntrustStatus.ENTRUST_FAILURE);
			if (order.getDealTime() == null || order.getDealTime() == 0) order.setDealTime(DateUtil.getTimeMillis());
			order.setCloseFee(BigDecimal.ZERO);
			contractOrderEntrustService.save(order);
			this.thawPosition(order.getPositionId(), order.getVolume());
			return;
		}

		// getCurrentMargin() 依赖 currentPrice，先赋值供下方计算用（不做 save，通过 applyCloseResult 写库）
		memberContractPosition.setCurrentPrice(order.getTradedPrice());

		WalletService walletService = walletServiceFactory.getWalletService(order);

		BigDecimal frozenMargin;
		BigDecimal margin;
		ContractPositionStatus newStatus;
		BigDecimal newPrincipalAmount;
		if (currentPosition.compareTo(order.getVolume()) == 0) {
			newStatus = ContractPositionStatus.DONE;
			margin = memberContractPosition.getCurrentMargin();
			frozenMargin = memberContractPosition.getPrincipalAmount();
			newPrincipalAmount = BigDecimal.ZERO;
			List<ContractOrderEntrust> cutList = contractOrderEntrustService.queryAllEntrustCutOrdersByPositionId(memberContractPosition.getId());
			for (ContractOrderEntrust cutOrder : cutList) {
				if (!cutOrder.getId().equals(order.getId()))
					kafkaTemplate.send("swap-order-cancel", JSON.toJSONString(cutOrder));
			}
		} else {
			newStatus = ContractPositionStatus.PARTLYDONE;
			margin = memberContractPosition.getCurrentMargin().multiply(order.getVolume()).divide(currentPosition, 8, RoundingMode.HALF_UP);
			BigDecimal remainedMargin = memberContractPosition.getCurrentMargin().subtract(margin);
			frozenMargin = memberContractPosition.getPrincipalAmount().subtract(remainedMargin);
			newPrincipalAmount = remainedMargin;
		}
		// ④ 钱包流水①：释放冻结保证金 → frozen_margin 减少，balance 增加
		walletService.releaseMargin(memberContractPosition.getMemberId(), frozenMargin);

		// 加权均价字段：Java 计算后直接覆盖写入
		BigDecimal newClosedOpenPrice = calculateAveragePrice(memberContractPosition.getMoneyType(),
				memberContractPosition.getClosedOpenPrice(), memberContractPosition.getClosedPosition(),
				order.getTradedPrice(), order.getVolume());
		// closed_position/frozen_position 由 applyCloseResult 的 delta SQL 处理，此处仅用于 closePrice 均价计算
		BigDecimal newClosedPosition = memberContractPosition.getClosedPosition().add(order.getVolume());

		// 盈亏计算：COIN计价按价差×张数，USDT计价按价差×张数×每张面值÷开仓价
		BigDecimal profit;
		if (memberContractPosition.getMoneyType() == ContractMoneyType.COIN) {
			profit = order.getTradedPrice().subtract(memberContractPosition.getOpenPrice()).multiply(order.getVolume());
		} else {
			profit = order.getTradedPrice().subtract(memberContractPosition.getOpenPrice())
					.multiply(order.getVolume()).multiply(memberContractPosition.getShareNumber())
					.divide(memberContractPosition.getOpenPrice(), 8, RoundingMode.HALF_UP);
		}
		// 空头方向盈亏取反（价格下跌为盈）
		if (memberContractPosition.getDirection() == ContractOrderDirection.SELL)
			profit = BigDecimal.ZERO.subtract(profit);

		// ⑤ 钱包流水②：扣除平仓手续费（爆仓时已归一为 MARKET_CLOSE，此处不会跳过）
		// 跟单优先使用 followFee 费率；无 followFee 则降级为 closeFee
		BigDecimal closeFee = BigDecimal.ZERO;
		if (order.getCloseType() != CloseType.BLAST) {
			BigDecimal tradedMoney;
			if (memberContractPosition.getMoneyType() == ContractMoneyType.COIN)
				tradedMoney = order.getVolume().multiply(order.getTradedPrice());
			else
				tradedMoney = order.getVolume().multiply(memberContractPosition.getShareNumber());
			BigDecimal feeRate = memberContractPosition.getCoin().getFollowFee() != null
					? memberContractPosition.getCoin().getFollowFee()
					: memberContractPosition.getCoin().getCloseFee();
			closeFee = tradedMoney.multiply(feeRate);
			walletService.clearContractCloseFee(order.getMemberId(), memberContractPosition.getCoin(), closeFee);
		}

		// ⑥ 钱包流水③：结算盈亏 → balance 加减 profit（正数为盈，负数为亏）
		BigDecimal newUsdtProfit = memberContractPosition.getUsdtProfit().add(profit);
		walletService.clearProfitOrLoss(order.getMemberId(), memberContractPosition.getCoin(), profit);

		// 分润（跟单盈利全部平仓时）
		if (order.getFollowId() != null && memberContractPosition.getDividendPercent() != null
				&& newUsdtProfit.compareTo(BigDecimal.ZERO) > 0
				&& newStatus == ContractPositionStatus.DONE) {
			Member follower = memberService.findOne(memberContractPosition.getMemberId());
			if (follower == null || follower.getIsInternal() == null || follower.getIsInternal() != 1) {
				BigDecimal dividend = newUsdtProfit
						.multiply(BigDecimal.valueOf(Double.valueOf(memberContractPosition.getDividendPercent()) / 100));
				MemberContractPosition traderPosition = memberContractPositionDao.findOne(order.getFollowId());
				if (traderPosition != null) {
					if (walletService instanceof FollowWalletService)
						((FollowWalletService) walletService).clearFollowDividend(
								memberContractPosition.getMemberId(), traderPosition.getMemberId(),
								memberContractPosition.getCoin(), dividend);
					else
						((SpecialFollowWalletService) walletService).clearFollowDividend(
								memberContractPosition.getMemberId(), traderPosition.getMemberId(),
								memberContractPosition.getCoin(), dividend);
				} else {
					log.error("【批量平仓】traderPosition 为空, followId={}", order.getFollowId());
				}
				order.setDividendPercent(dividend);
			}
		}

		Date newCloseTime = (order.getDealTime() == null || order.getDealTime() == 0)
				? new Date() : new Date(order.getDealTime());
		BigDecimal newClosePrice = order.getTradedPrice();
		if (memberContractPosition.getClosePrice() != null)
			newClosePrice = calculateAveragePrice(memberContractPosition.getMoneyType(),
					memberContractPosition.getClosePrice(), newClosedPosition,
					order.getTradedPrice(), order.getVolume());

		// 原子写入：delta 字段用 field+delta，加权均价/状态等直接覆盖，不走 JPA 全字段 save
		// WHERE 条件内置了 closed_position+volume<=total_position 且 frozen_position>=volume，
		// 影响行数为 0 表示并发异常（仓位已不足），抛出异常触发事务回滚
		int affected = memberContractPositionDao.applyCloseResult(
				memberContractPosition.getId(),
				order.getVolume(),
				closeFee,
				profit,
				newPrincipalAmount,
				newClosedOpenPrice,
				newClosePrice,
				newStatus.ordinal(),
				order.getCloseType().ordinal(),
				newCloseTime,
				order.getTradedPrice(),
				0
		);
		if (affected != 1) {
			throw new RuntimeException("平仓写入失败：仓位数量异常, positionId=" + memberContractPosition.getId()
					+ ", volume=" + order.getVolume());
		}
		entityManager.refresh(memberContractPosition);

		order.setCloseFee(closeFee);
		order.setProfitAndLoss(profit);
		order.setPrincipalAmount(margin);
		order.setUsdtOpenPrice(memberContractPosition.getOpenPrice());
		order.setUsdtOpenTime(memberContractPosition.getOpenTime().getTime());
		order.setStatus(ContractOrderEntrustStatus.ENTRUST_SUCCESS);
		order.setTradedVolume(order.getVolume());
		if (order.getDealTime() == null || order.getDealTime() == 0)
			order.setDealTime(DateUtil.getTimeMillis());
		order.setOpenType(memberContractPosition.getOpenType());
		contractOrderEntrustService.save(order);

		// 始终发送 swap-order-close-done，触发 copy-trade traderClosePositionSuccess 更新跟单记录
		kafkaTemplate.send("swap-order-close-done", JSON.toJSONString(order));
		log.info("【批量平仓】直接平仓完成, memberId={}, positionId={}", order.getMemberId(), order.getPositionId());
	}

	/**
	 * 平仓结算入口（匹配引擎回调）。
	 *
	 * <p>调用来源：ContractCoinMatch.trade() 匹配成功后回调，包含以下场景：
	 * <ul>
	 *   <li>用户主动平仓（市价/限价）</li>
	 *   <li>止盈止损触发平仓（CUT）</li>
	 *   <li>一键平仓 / 检测平仓（forceClose → dealAllCloseOrder → trade）</li>
	 *   <li>爆仓检测（follow-account-blast → dealAllCloseOrder → trade）</li>
	 * </ul>
	 *
	 * <p>跟单平仓由 dealAllCloseOrder 在 order 上设置 followId，
	 * walletServiceFactory.getWalletService(order) 会据此返回 FollowWalletService。
	 *
	 * <p>钱包流水（成功路径）：
	 * <ol>
	 *   <li>releaseMargin：释放 frozen_margin（按平仓比例归还至 balance）</li>
	 *   <li>clearContractCloseFee：扣除平仓手续费（爆仓时跳过）</li>
	 *   <li>clearProfitOrLoss：结算盈亏（爆仓时使用预设亏损额）</li>
	 * </ol>
	 */
	@Transactional(rollbackFor = Exception.class)
	public void processCloseOrder(ContractOrderEntrust order) throws Exception {
		/*if(!redisLock.waitPositionLock(order.getPositionId())) {
			log.info("get position lock failure, order={}", order);
	        order.setStatus(ContractOrderEntrustStatus.ENTRUST_FAILURE);
	        order.setDealTime(DateUtil.getTimeMillis());
	        order.setCloseFee(BigDecimal.ZERO);
	        contractOrderEntrustService.save(order);
	        this.thawPosition(order.getPositionId(), order.getVolume());
			return;
		}*/
		try {
			// 失败路径①：无成交价（匹配引擎无行情）
			// 此时仓位仍处于持仓状态，frozen_margin 继续保留，只需解冻已冻结的持仓数量
			if(order.getTradedPrice().compareTo(BigDecimal.ZERO)==0) {
				order.setStatus(ContractOrderEntrustStatus.ENTRUST_FAILURE);
		        order.setDealTime(DateUtil.getTimeMillis());
		        order.setCloseFee(BigDecimal.ZERO);
		        contractOrderEntrustService.save(order);
		        this.thawPosition(order.getPositionId(), order.getVolume()); // 只解冻持仓，保证金继续冻结
				return;
			}

			//entityManager.clear();
			MemberContractPosition memberContractPosition = memberContractPositionDao.findOne(order.getPositionId());

			BigDecimal currentPosition = memberContractPosition.getCurrentPosition();
			// 失败路径②：可用仓位不足（并发重复平仓或数据异常）
			// 同上，仓位未实际关闭，frozen_margin 继续保留，只解冻持仓数量
			if(currentPosition.compareTo(order.getVolume()) < 0) {
				log.info("currentPosition={},order.getVolume()={}", currentPosition, order.getVolume());
				//throw new Exception("currentPosition is less than order.getVolume()");
		        order.setStatus(ContractOrderEntrustStatus.ENTRUST_FAILURE);
		        order.setDealTime(DateUtil.getTimeMillis());
		        order.setCloseFee(BigDecimal.ZERO);
		        order.setPositionId(order.getPositionId());
		        contractOrderEntrustService.save(order);
		        this.thawPosition(order.getPositionId(), order.getVolume()); // 只解冻持仓，保证金继续冻结
		        return;
			}
			BigDecimal closedPercentage = order.getVolume().divide(currentPosition,8,RoundingMode.HALF_DOWN);
			// getCurrentMargin() 依赖 currentPrice，先赋值供下方计算用（不做 save，通过 applyCloseResult 写库）
			memberContractPosition.setCurrentPrice(order.getTradedPrice());

			WalletService walletService = walletServiceFactory.getWalletService(order);
			log.info("followId={}", order.getFollowId());
			log.info("walletService={}", walletService.getClass().getName());

			BigDecimal frozenMargin;
			BigDecimal margin;
			ContractPositionStatus newStatus;
			BigDecimal newPrincipalAmount;
			if(currentPosition.compareTo(order.getVolume()) == 0) {
				newStatus = ContractPositionStatus.DONE;
				margin = memberContractPosition.getCurrentMargin();
				frozenMargin = memberContractPosition.getPrincipalAmount() != null ? memberContractPosition.getPrincipalAmount() : BigDecimal.ZERO;
				newPrincipalAmount = BigDecimal.ZERO;

				List<ContractOrderEntrust> cutList = contractOrderEntrustService.queryAllEntrustCutOrdersByPositionId(memberContractPosition.getId());
				for(ContractOrderEntrust cutOrder: cutList) {
					if(cutOrder.getId().longValue()!=order.getId()) {
						log.info("取消止盈止损, orderid={}, order={}", order.getId(), cutOrder);
						kafkaTemplate.send("swap-order-cancel", JSON.toJSONString(cutOrder));
					}
				}
			} else {
				newStatus = ContractPositionStatus.PARTLYDONE;
				margin = memberContractPosition.getCurrentMargin().multiply(order.getVolume()).divide(currentPosition,8,RoundingMode.HALF_UP);
				BigDecimal remainedMargin = memberContractPosition.getCurrentMargin().subtract(margin);
				BigDecimal pa = memberContractPosition.getPrincipalAmount() != null ? memberContractPosition.getPrincipalAmount() : BigDecimal.ZERO;
				frozenMargin = pa.subtract(remainedMargin);
				newPrincipalAmount = remainedMargin;
			}
			// 钱包流水①：释放冻结保证金 → frozen_margin 减少，balance 增加
			walletService.releaseMargin(memberContractPosition.getMemberId(), frozenMargin);

			// 加权均价字段：Java 计算后直接覆盖写入
			BigDecimal newClosedOpenPrice = calculateAveragePrice(memberContractPosition.getMoneyType(), memberContractPosition.getClosedOpenPrice(), memberContractPosition.getClosedPosition(), order.getTradedPrice(), order.getVolume());
			// closed_position/frozen_position 由 applyCloseResult 的 delta SQL 处理，此处仅用于 closePrice 均价计算
			BigDecimal newClosedPosition = memberContractPosition.getClosedPosition().add(order.getVolume());

			// 盈亏计算：COIN计价按价差×张数，USDT计价按价差×张数×每张面值÷开仓价
			BigDecimal profit;
			if(memberContractPosition.getMoneyType() == ContractMoneyType.COIN)
				profit = order.getTradedPrice().subtract(memberContractPosition.getOpenPrice()).multiply(order.getVolume());
			else {
				profit = order.getTradedPrice().subtract(memberContractPosition.getOpenPrice()).multiply(order.getVolume()).multiply(memberContractPosition.getShareNumber()).divide(memberContractPosition.getOpenPrice(),8,RoundingMode.HALF_UP);
			}
			// 空头方向盈亏取反（价格下跌为盈）
			if(memberContractPosition.getDirection() == ContractOrderDirection.SELL) {
				profit = BigDecimal.ZERO.subtract(profit);
			}

			int violateTime = 0;
			IWallet wallet = walletService.findOne(memberContractPosition.getMemberId());
			if(wallet instanceof UserPurse) {
				UserPurse userPurse = (UserPurse)wallet;
				Integer isSupervised = userPurse.getIsSupervised();
				int fineTimeInterval = sysConfigService.get().getFineTimeInterval();
				if(profit.compareTo(BigDecimal.ZERO)>0 && System.currentTimeMillis() - memberContractPosition.getLatestOpenTime().getTime() < fineTimeInterval*1000) {
					violateTime = 1;
					order.setViolateTime(1);
					if(isSupervised!=null && isSupervised==1 && order.getCloseType() != CloseType.BLAST && order.getCloseType() != CloseType.CUT) {
						profit = margin.multiply(BigDecimal.valueOf(-0.1));

						BigDecimal fakeClosePrice;
						if(order.getDirection() == ContractOrderDirection.BUY)
							fakeClosePrice = memberContractPosition.getOpenPrice().subtract(profit.divide(order.getVolume(),8,RoundingMode.HALF_UP));
						else
							fakeClosePrice = memberContractPosition.getOpenPrice().add(profit.divide(order.getVolume(),8,RoundingMode.HALF_UP));
						order.setTradedPrice(fakeClosePrice);
					}
				}
			}

			Member member = memberService.findOne(order.getMemberId());

			// 钱包流水②：扣除平仓手续费（爆仓时免手续费）
			BigDecimal closeFee = BigDecimal.ZERO;
			if(order.getCloseType() != CloseType.BLAST) {
				BigDecimal tradedMoney;
		        if(memberContractPosition.getMoneyType() == ContractMoneyType.COIN)
		        	tradedMoney = order.getVolume().multiply(order.getTradedPrice());
		        else
		        	tradedMoney = order.getVolume().multiply(memberContractPosition.getShareNumber());
		        closeFee = tradedMoney.multiply(memberContractPosition.getCoin().getCloseFee());
		        // 跟单单独费率（followFee），优先于普通平仓费率
		        if(order.getFollowId()!=null && memberContractPosition.getCoin().getFollowFee() != null) {
		        	closeFee = tradedMoney.multiply(memberContractPosition.getCoin().getFollowFee());
		        }
		        walletService.clearContractCloseFee(order.getMemberId(), memberContractPosition.getCoin(), closeFee);
			}
			// 爆仓时 profit 使用 dealAllCloseOrder 预设的 blastedAmount（含穿仓亏损），而非重新计算
			if(order.getCloseType() == CloseType.BLAST) {
				profit = order.getProfitAndLoss();
			} else {
				// 余额不足兜底：防止扣费后余额为负
				if(wallet.getFrozenBalance().compareTo(BigDecimal.ZERO)==0
						&& wallet.getFrozenMargin().compareTo(BigDecimal.ZERO)==0
						&& wallet.getBalance().subtract(closeFee).add(profit).compareTo(BigDecimal.ZERO)<0)
					profit = BigDecimal.ZERO.subtract(wallet.getBalance().subtract(closeFee));
			}
			log.info("close profit={}", profit);

			// 钱包流水③：结算盈亏 → balance 加减 profit（正数为盈，负数为亏）
			BigDecimal prevUsdtProfit = memberContractPosition.getUsdtProfit() != null ? memberContractPosition.getUsdtProfit() : BigDecimal.ZERO;
			BigDecimal newUsdtProfit = prevUsdtProfit.add(profit);
			walletService.clearProfitOrLoss(order.getMemberId(), memberContractPosition.getCoin(), profit);

			if(order.getFollowId()!=null && memberContractPosition.getDividendPercent()!=null
					&& newUsdtProfit.compareTo(BigDecimal.ZERO)>0
					&& newStatus == ContractPositionStatus.DONE) {
				Member follower = memberService.findOne(memberContractPosition.getMemberId());
				if (follower == null || follower.getIsInternal() == null || follower.getIsInternal() != 1) {
					BigDecimal dividend = newUsdtProfit.multiply(BigDecimal.valueOf(Double.valueOf(memberContractPosition.getDividendPercent())/100));
					MemberContractPosition traderPosition = memberContractPositionDao.findOne(order.getFollowId());
					if(traderPosition!=null) {
						if(walletService instanceof FollowWalletService)
							((FollowWalletService)walletService).clearFollowDividend(memberContractPosition.getMemberId(), traderPosition.getMemberId(), memberContractPosition.getCoin(), dividend);
						else
							((SpecialFollowWalletService)walletService).clearFollowDividend(memberContractPosition.getMemberId(), traderPosition.getMemberId(), memberContractPosition.getCoin(), dividend);
					} else {
						log.error("traderPosition is null");
					}
					log.info("dividend={}", dividend);
					order.setDividendPercent(dividend);
				}
			}

			Date newCloseTime = (order.getDealTime()==null || order.getDealTime()==0) ? new Date() : new Date(order.getDealTime());
			BigDecimal newClosePrice = order.getTradedPrice();
			if(memberContractPosition.getClosePrice()!=null)
				newClosePrice = calculateAveragePrice(memberContractPosition.getMoneyType(), memberContractPosition.getClosePrice(), newClosedPosition, order.getTradedPrice(), order.getVolume());

			// 原子写入：delta 字段用 field+delta，加权均价/状态等直接覆盖，不走 JPA 全字段 save
			// WHERE 条件内置了 closed_position+volume<=total_position 且 frozen_position>=volume，
			// 影响行数为 0 表示并发异常（仓位已不足），抛出异常触发事务回滚
			int affected = memberContractPositionDao.applyCloseResult(
					memberContractPosition.getId(),
					order.getVolume(),
					closeFee,
					profit,
					newPrincipalAmount,
					newClosedOpenPrice,
					newClosePrice,
					newStatus.ordinal(),
					order.getCloseType().ordinal(),
					newCloseTime,
					order.getTradedPrice(),
					violateTime
			);
			if (affected != 1) {
				throw new RuntimeException("平仓写入失败：仓位数量异常, positionId=" + memberContractPosition.getId()
						+ ", volume=" + order.getVolume());
			}
			entityManager.refresh(memberContractPosition);
			log.info("processCloseOrder, positionId={}", memberContractPosition.getId());
			
			order.setCloseFee(closeFee);
			order.setProfitAndLoss(profit);
			order.setPrincipalAmount(margin);
	        order.setUsdtOpenPrice(memberContractPosition.getOpenPrice());
	        order.setUsdtOpenTime(memberContractPosition.getOpenTime().getTime());
	        order.setStatus(ContractOrderEntrustStatus.ENTRUST_SUCCESS);
	        order.setTradedVolume(order.getVolume());
	        if(order.getDealTime()==null || order.getDealTime()==0)
	        	order.setDealTime(DateUtil.getTimeMillis());
	        order.setOpenType(memberContractPosition.getOpenType());
	        contractOrderEntrustService.save(order);
	        
	        boolean isTrader = member.getIsTrader()!=null && member.getIsTrader()>0;
//	        if(isTrader) {
//	        	ContractOrderEntrust orderByPertage = new ContractOrderEntrust();
//		        BeanUtils.copyProperties(order, orderByPertage);
//		        orderByPertage.setVolume(closedPercentage);
//		        orderByPertage.setIsPercentage(1);
//		        kafkaTemplate.send("swap-trader-order-close-done", JSON.toJSONString(orderByPertage));
//	        }
	        if(isTrader || order.getFollowId()!=null) {
	        	log.info("kafka swap-order-close-done, order={}", order);
	        	kafkaTemplate.send("swap-order-close-done", JSON.toJSONString(order));
	        }
	        // 带单员自己平仓，触发跟单员批量平仓
	        if(isTrader && order.getFollowId() == null) {
	        	kafkaTemplate.send("follow-trader-close-notify", JSON.toJSONString(order));
	        }
	        if(member.getInviterId() != null && order.getFollowId()==null) {
	        	JSONObject json = new JSONObject();
	        	json.put("memberId", member.getId());
	        	json.put("inviterId", member.getInviterId());
	        	kafkaTemplate.send("after-swap-order-done", JSON.toJSONString(json));
	        }
	        
	        //if(order.getFollowId() != null) {
	        //ContractOrderEntrust orderByPertage = new ContractOrderEntrust();
	        //BeanUtils.copyProperties(order, orderByPertage);
	        //orderByPertage.setVolume(closedPercentage);
	        //orderByPertage.setIsPercentage(1);
	        //kafkaTemplate.send("swap-order-close-done", JSON.toJSONString(orderByPertage));
	        //}
	        
	        //if(order.getFollowId()!=null && savedPosition.getStatus() == ContractPositionStatus.DONE) {
	        //	kafkaTemplate.send("swap-follow-position-close", JSON.toJSONString(orderByPertage));
	        //}
	        
	        log.info("processCloseOrder done. memberId={}", order.getMemberId());
		} catch (Exception e) {
			throw e;
		} finally {
			//redisLock.unlockPosition(order.getPositionId());
		}
	}
	
	public void saveIntegralPosition(MemberContractPosition memberContractPosition, ContractOrderEntrust order) {
		BigDecimal newOpenPrice = calculateAveragePrice(order.getMoneyType(), memberContractPosition.getOpenPrice(), memberContractPosition.getCurrentPosition(), order.getTradedPrice(), order.getVolume());
		if(memberContractPosition.getTotalPosition().compareTo(BigDecimal.ZERO) > 0) {
			// 补仓：delta 字段用原子 UPDATE，加权均价/时间直接覆盖，不走 JPA 全字段 save
			int newOpenType = (memberContractPosition.getOpenType() != null
					&& !memberContractPosition.getOpenType().equals(order.getOpenType())) ? 3 : order.getOpenType();
			long newOpenTimeMs = DateUtil.getTimeMillis() - (long)(
					(DateUtil.getTimeMillis() - memberContractPosition.getOpenTime().getTime())
					* memberContractPosition.getCurrentPosition().doubleValue()
					/ (memberContractPosition.getCurrentPosition().doubleValue() + order.getVolume().doubleValue()));
			memberContractPositionDao.applyOpenIncrement(
					memberContractPosition.getId(),
					order.getVolume(),
					order.getPrincipalAmount(),
					order.getOpenFee(),
					newOpenPrice,
					new Date(newOpenTimeMs),
					new Date(),
					BigDecimal.valueOf(order.getLeverage()),
					newOpenType,
					order.getShareNumber()
			);
			entityManager.refresh(memberContractPosition);
		} else {
			// 新建仓位：首次开仓，走 save() INSERT
			memberContractPosition.setOpenType(order.getOpenType());
			memberContractPosition.setOpenTime(new Date(order.getCreateTime()));
			memberContractPosition.setCreateTime(new Date(order.getCreateTime()));
			memberContractPosition.setLatestOpenTime(new Date());
			memberContractPosition.setLeverage(BigDecimal.valueOf(order.getLeverage()));
			memberContractPosition.setPrincipalAmount(memberContractPosition.getPrincipalAmount().add(order.getPrincipalAmount()));
			memberContractPosition.setTotalPrincipalAmount(memberContractPosition.getTotalPrincipalAmount().add(order.getPrincipalAmount()));
			memberContractPosition.setOpenPrice(newOpenPrice);
			memberContractPosition.setTotalPosition(memberContractPosition.getTotalPosition().add(order.getVolume()));
			if(memberContractPosition.getDecimalScale() == null)
				memberContractPosition.setDecimalScale(order.getDecimalScale());
			memberContractPosition.setShareNumber(order.getShareNumber());
			memberContractPosition.setFollowPositionId(order.getFollowId());
			if(order.getFollowId() != null) {
				MemberContractPosition followPosition = this.findOne(order.getFollowId());
				memberContractPosition.setFollowMemberId(followPosition.getMemberId());
			}
			if(order.getDividendPercent() != null)
				memberContractPosition.setDividendPercent(order.getDividendPercent().intValue());
			memberContractPosition.setCommissionFee(memberContractPosition.getCommissionFee().add(order.getOpenFee()));
			memberContractPositionDao.save(memberContractPosition);
		}
	}
	
	/**
	 * 体验金开仓（同事务）
	 * 1. 构建并保存开仓委托记录
	 * 2. 构建并保存仓位
	 * 3. 回写委托的 positionId
	 * 4. 激活体验金券（状态改为持仓中 IN_USE）
	 * 以上全部在同一事务内执行，任意步骤失败则全部回滚。
	 *
	 * @param memberId      用户 ID
	 * @param contractCoin  合约交易对
	 * @param direction     开仓方向
	 * @param record        体验金券记录
	 * @param leverage      杠杆（已由调用方从产品配置或券快照取得）
	 * @param openPrice     开仓价（含滑点，由调用方从行情引擎取得）
	 * @return Map 包含 "order"(ContractOrderEntrust) 和 "position"(MemberContractPosition)
	 */
	@Transactional(rollbackFor = Exception.class)
	public Map<String, Object> openExperienceGiftPosition(
			Long memberId,
			ContractCoin contractCoin,
			ContractOrderDirection direction,
			ExperienceGiftRecord record,
			int leverage,
			BigDecimal openPrice) {

		BigDecimal leverageBd = BigDecimal.valueOf(leverage);
		int volScale = contractCoin.getBaseCoinScale() > 0 ? contractCoin.getBaseCoinScale() : 4;

		// 计算持仓量（面额 × 杠杆 / 开仓价）
		BigDecimal volume = record.getAmount().multiply(leverageBd)
				.divide(openPrice, volScale, RoundingMode.DOWN);

		// 计算手续费（向上取整，避免费率极小时截断为0）
		BigDecimal tradedMoney = openPrice.multiply(volume);
		BigDecimal openFee     = tradedMoney.multiply(contractCoin.getOpenFee()).setScale(8, RoundingMode.UP);
		BigDecimal closeFeeEst = tradedMoney.multiply(contractCoin.getCloseFee()).setScale(8, RoundingMode.UP);

		// 计算自动平仓价（达到最大盈利后触发）
		BigDecimal totalCost = record.getMaxProfit().add(openFee).add(closeFeeEst);
		BigDecimal autoClosePrice;
		if (direction == ContractOrderDirection.BUY) {
			autoClosePrice = openPrice.add(totalCost.divide(volume, 8, RoundingMode.UP));
		} else {
			autoClosePrice = openPrice.subtract(totalCost.divide(volume, 8, RoundingMode.UP));
		}

		Date now = new Date();
		long nowMs = now.getTime();

		// 1. 构建开仓委托记录
		ContractOrderEntrust orderEntrust = new ContractOrderEntrust();
		orderEntrust.setContractId(contractCoin.getId());
		orderEntrust.setMemberId(memberId);
		orderEntrust.setSymbol(contractCoin.getSymbol());
		orderEntrust.setBaseSymbol(contractCoin.getSymbol().split("/")[1]);
		orderEntrust.setCoinSymbol(contractCoin.getSymbol().split("/")[0]);
		orderEntrust.setDirection(direction);
		orderEntrust.setContractOrderEntrustId(GeneratorUtil.getOrderId("EG"));
		orderEntrust.setVolume(volume);
		orderEntrust.setTradedVolume(volume);
		orderEntrust.setTradedPrice(openPrice);
		orderEntrust.setPrincipalUnit("USDT");
		orderEntrust.setPrincipalAmount(record.getAmount());
		orderEntrust.setCreateTime(nowMs);
		orderEntrust.setUsdtOpenTime(nowMs);
		orderEntrust.setDealTime(nowMs);
		orderEntrust.setType(ContractOrderType.MARKET_PRICE);
		orderEntrust.setEntrustPrice(BigDecimal.ZERO);
		orderEntrust.setEntrustType(ContractOrderEntrustType.OPEN);
		orderEntrust.setTriggeringTime(0L);
		orderEntrust.setShareNumber(contractCoin.getShareNumber());
		orderEntrust.setProfitAndLoss(BigDecimal.ZERO);
		orderEntrust.setPatterns(ContractOrderPattern.CROSSED);
		orderEntrust.setOpenFee(openFee);
		orderEntrust.setCloseFee(BigDecimal.ZERO);
		orderEntrust.setStatus(ContractOrderEntrustStatus.ENTRUST_SUCCESS);
		orderEntrust.setCurrentPrice(openPrice);
		orderEntrust.setIsBlast(0);
		orderEntrust.setIsFromSpot(0);
		orderEntrust.setIsReversedOpen(0);
		orderEntrust.setIsReward(1);
		orderEntrust.setLeverage(leverage);
		orderEntrust.setOpenType(ContractOrderType.MARKET_PRICE.ordinal());
		orderEntrust.setPositionType(ContractPositionType.SEPARATE);
		orderEntrust.setMoneyType(ContractMoneyType.COIN);
		orderEntrust.setExperienceRecordId(record.getId());
		orderEntrust.setDecimalScale(volScale);
		orderEntrust.setUsdtOpenPrice(openPrice);
		ContractOrderEntrust savedOrder = contractOrderEntrustService.save(orderEntrust);

		// 2. 构建仓位
		MemberContractPosition position = new MemberContractPosition();
		position.setMemberId(memberId);
		position.setTotalPosition(volume);
		position.setClosedPosition(BigDecimal.ZERO);
		position.setFrozenPosition(BigDecimal.ZERO);
		position.setType(ContractPositionType.SEPARATE);
		position.setStatus(ContractPositionStatus.STARTED);
		position.setCoin(contractCoin);
		position.setDirection(direction);
		position.setCreateTime(now);
		position.setOpenTime(now);
		position.setLatestOpenTime(now);
		position.setOpenPrice(openPrice);
		position.setOpenType(ContractOrderType.MARKET_PRICE.ordinal());
		position.setLeverage(leverageBd);
		position.setPrincipalAmount(record.getAmount());
		position.setTotalPrincipalAmount(record.getAmount());
		position.setCommissionFee(openFee);
		position.setShareNumber(contractCoin.getShareNumber());
		position.setMoneyType(ContractMoneyType.COIN);
		position.setUsdtProfit(BigDecimal.ZERO);
		position.setDecimalScale(volScale);
		position.setExperienceRecordId(record.getId());
		MemberContractPosition savedPosition = memberContractPositionDao.save(position);

		// 3. 回写委托的 positionId
		savedOrder.setPositionId(savedPosition.getId());
		contractOrderEntrustService.save(savedOrder);

		// 4. 激活体验金券（状态改为持仓中 IN_USE，同事务）
		boolean activated = experienceGiftRecordService.activate(
				record.getId(), savedPosition.getId(),
				contractCoin.getSymbol(), direction,
				openFee, autoClosePrice);
		if (!activated) {
			throw new RuntimeException("体验金激活失败，recordId=" + record.getId());
		}

		// 5. 回写 record 对象字段（与数据库保持一致），并写入内存缓存供定时任务直接读取
		record.setStatus(ExperienceGiftRecordStatus.IN_USE);
		record.setPositionId(savedPosition.getId());
		record.setCoinSymbol(contractCoin.getSymbol());
		record.setPositionDirection(direction);
		record.setOpenFee(openFee);
		record.setAutoClosePrice(autoClosePrice);
		record.setUsedAt(now);
		record.setUpdatedAt(now);
		experienceGiftRecordService.putToCache(record);

		Map<String, Object> result = new HashMap<>();
		result.put("order",    savedOrder);
		result.put("position", savedPosition);
		return result;
	}

	/**
	 * 体验金仓位直接关闭（跳过真实钱包操作）
	 * 1. 生成平仓委托记录
	 * 2. 原子更新仓位状态（含盈亏、closed_open_price 等字段）
	 * 3. 若盈利，更新体验金钱包盈利资金池 experience_gift_wallet.total_profit
	 * 4. 更新体验金券状态
	 * 以上全部在同一事务内执行，完成后发送账户缓存刷新消息。
	 * @return true=成功，false=仓位不存在/已关闭/非体验金仓位
	 */
	@Transactional(rollbackFor = Exception.class)
	public boolean closeExperienceGiftPosition(Long positionId, BigDecimal closePrice,
			CloseType closeType, BigDecimal pnl, Long recordId, ExperienceGiftCloseReason closeReason) {
		MemberContractPosition pos = memberContractPositionDao.findOne(positionId);
		if (pos == null || pos.getStatus() != ContractPositionStatus.STARTED
				|| pos.getExperienceRecordId() == null) {
			return false;
		}

		// 1. 生成平仓委托记录
		ContractOrderEntrust closeOrder = new ContractOrderEntrust();
		closeOrder.setContractId(pos.getCoin().getId());
		closeOrder.setMemberId(pos.getMemberId());
		closeOrder.setPositionId(positionId);
		closeOrder.setContractOrderEntrustId(GeneratorUtil.getOrderId("EG"));
		closeOrder.setEntrustType(ContractOrderEntrustType.CLOSE);
		closeOrder.setDirection(pos.getDirection());
		closeOrder.setType(ContractOrderType.MARKET_PRICE);
		closeOrder.setSymbol(pos.getCoin().getSymbol());
		closeOrder.setCoinSymbol(pos.getCoin().getCoinSymbol());
		closeOrder.setBaseSymbol(pos.getCoin().getBaseSymbol());
		closeOrder.setTradedPrice(closePrice);
		closeOrder.setVolume(pos.getCurrentPosition());
		closeOrder.setTradedVolume(pos.getCurrentPosition());
		closeOrder.setProfitAndLoss(pnl);
		closeOrder.setCloseFee(BigDecimal.ZERO);
		closeOrder.setOpenFee(BigDecimal.ZERO);
		closeOrder.setShareNumber(pos.getShareNumber());
		closeOrder.setMoneyType(pos.getMoneyType());
		closeOrder.setCloseType(closeType);
		closeOrder.setLeverage(pos.getLeverage() != null ? pos.getLeverage().intValue() : null);
		closeOrder.setOpenType(pos.getOpenType());
		closeOrder.setPositionType(pos.getType());
		closeOrder.setPatterns(ContractOrderPattern.CROSSED);
		closeOrder.setPrincipalAmount(pos.getPrincipalAmount());
		closeOrder.setPrincipalUnit("USDT");
		closeOrder.setCurrentPrice(closePrice);
		closeOrder.setEntrustPrice(BigDecimal.ZERO);
		closeOrder.setTriggeringTime(0L);
		closeOrder.setDecimalScale(pos.getDecimalScale() != null ? pos.getDecimalScale() : 4);
		closeOrder.setStatus(ContractOrderEntrustStatus.ENTRUST_SUCCESS);
		long now = DateUtil.getTimeMillis();
		closeOrder.setCreateTime(now);
		closeOrder.setDealTime(now);
		closeOrder.setUsdtOpenTime(pos.getOpenTime() != null ? pos.getOpenTime().getTime() : now);
		closeOrder.setUsdtOpenPrice(pos.getOpenPrice());
		closeOrder.setExperienceRecordId(pos.getExperienceRecordId());
		contractOrderEntrustService.save(closeOrder);

		// 2. 计算平仓手续费 = 持仓量 * 平仓价 * 平仓费率（向上取整，与开仓手续费口径一致）
		BigDecimal closeFee = closePrice
				.multiply(pos.getCurrentPosition())
				.multiply(pos.getCoin().getCloseFee())
				.setScale(8, RoundingMode.UP);

		// 3. 原子更新仓位（含 usdt_profit、commission_fee 累加、closed_open_price）
		int affected = memberContractPositionDao.closeExperienceGiftPosition(
				positionId, closePrice, closeType.ordinal(), new Date(), pnl, pos.getOpenPrice(), closeFee);
		if (affected != 1) {
			throw new RuntimeException("体验金仓位平仓写入失败，positionId=" + positionId);
		}

		// 4. 计算净盈亏（无论盈亏均扣除开平手续费，保证 record.pnl 与钱包口径一致）
		BigDecimal netPnl;
		if (closeReason == ExperienceGiftCloseReason.PROFIT_LIMIT) {
			// 盈利触顶：autoClosePrice 计算时已将开平手续费纳入，pnl 本身即为净盈利
			netPnl = pnl;
		} else {
			// 其他平仓：pnl 为原始价差盈亏（未扣手续费），需手动扣除开平手续费
			BigDecimal openFee = pos.getCommissionFee() != null ? pos.getCommissionFee() : BigDecimal.ZERO;
			netPnl = pnl.subtract(openFee).subtract(closeFee);
		}

		// 5. 净盈利才累加体验金钱包盈利资金池
		if (netPnl.compareTo(BigDecimal.ZERO) > 0) {
			experienceGiftWalletDao.addProfit(pos.getMemberId(), netPnl);
		}

		// 6. 更新体验金券状态，存入净盈亏及平仓手续费
		experienceGiftRecordService.closeRecord(recordId, netPnl, closeFee, ExperienceGiftRecordStatus.USED, closeReason);

		// 7. 更新仓位的止盈止损委托单状态（与平仓原因对应，保证在同一事务内完成）
		// 止盈/止损触发：将对应切单标为 ENTRUST_SUCCESS 并补全成交字段
		// 其他原因（爆仓、盈利触顶、手动平仓）：将未触发的切单标为 ENTRUST_CANCEL
		List<ContractOrderEntrust> cutOrders = contractOrderEntrustService.queryEntrustCutOrdersByPositionId(positionId);
		for (ContractOrderEntrust cutOrder : cutOrders) {
			if (closeReason == ExperienceGiftCloseReason.STOP_PROFIT
					|| closeReason == ExperienceGiftCloseReason.STOP_LOSS) {
				// 止盈=1，止损=0
				cutOrder.setCutType(closeReason == ExperienceGiftCloseReason.STOP_PROFIT ? 1 : 0);
				cutOrder.setTriggerPrice(closePrice);
				cutOrder.setType(ContractOrderType.MARKET_PRICE);
				cutOrder.setTradedPrice(closePrice);
				cutOrder.setStatus(ContractOrderEntrustStatus.ENTRUST_SUCCESS);
			} else {
				cutOrder.setStatus(ContractOrderEntrustStatus.ENTRUST_CANCEL);
			}
			cutOrder.setDealTime(DateUtil.getTimeMillis());
			contractOrderEntrustService.save(cutOrder);
		}

		// 8. 从内存缓存移除（DB 已提交后缓存立即失效，定时任务不再重复触发）
		// 注意：trading-account-change Kafka 消息由各调用方在事务提交后发送，此处不发，避免事务未提交时竞态脏写
		experienceGiftRecordService.removeFromCache(recordId);

		return true;
	}

	@Transactional(rollbackFor = Exception.class)
	public int freezePosition(Long id, BigDecimal volume) {
		if(!redisLock.lockPosition(id)) {
			return 0;
		}
		
		try {
			MemberContractPosition memberContractPosition = findOne(id);
			//return memberContractPositionDao.freezePosition(id, amount);
			if(memberContractPosition.getAvailablePosition().compareTo(volume)>=0) {
				memberContractPosition.setFrozenPosition(memberContractPosition.getFrozenPosition().add(volume));
				memberContractPositionDao.save(memberContractPosition);
				return 1;
			} else
				return 0;
		} catch (Exception e) {
			log.error("freezePosition error", e);
			return 0;
		} finally {
			redisLock.unlockPosition(id);
		}
	}
	
	@Transactional(rollbackFor = Exception.class)
	public int thawPosition(Long id, BigDecimal volume) {
		//memberContractPositionDao.thawPosition(id, amount);
		MemberContractPosition memberContractPosition = findOne(id);
		if(memberContractPosition.getFrozenPosition().compareTo(volume)>=0) {
			memberContractPosition.setFrozenPosition(memberContractPosition.getFrozenPosition().subtract(volume));
			memberContractPositionDao.save(memberContractPosition);
			return 1;
		} else
			return 0;
	}
	
	public BigDecimal sumCurrentPositionByMemberId(Long memberId) {
		return BigDecimal.ZERO;
	}
	
	private BigDecimal calculateAveragePrice(ContractMoneyType moneyType, BigDecimal originalPrice, BigDecimal originalVolume, BigDecimal newPrice, BigDecimal newVolume) {
		if(originalVolume.compareTo(BigDecimal.ZERO)==0)
			return newPrice;
		else {
			if(moneyType == ContractMoneyType.COIN)
				return originalPrice.multiply(originalVolume)
					.add(newPrice.multiply(newVolume))
					.divide(originalVolume.add(newVolume), 8, BigDecimal.ROUND_DOWN);
			else {
				BigDecimal originalCoinPosition = originalVolume.divide(originalPrice,8,RoundingMode.HALF_UP);
				BigDecimal newCoinPosition = newVolume.divide(newPrice,8,RoundingMode.HALF_UP);
				return originalVolume.add(newVolume).divide(originalCoinPosition.add(newCoinPosition),8,RoundingMode.HALF_UP);
			}
		}
	}
	
	public MemberContractPosition getIntegralMemberContractPosition(Long memberId, Long contractCoinId, ContractOrderDirection direction, ContractMoneyType moneyType) {
		MemberContractPosition memberContractPosition = memberContractPositionDao.findCurrentIntegralPosition(memberId, contractCoinId, direction.ordinal(), moneyType.ordinal());
		if(memberContractPosition == null) {
			memberContractPosition =  newIntegralPosition(memberId, contractCoinId, direction, moneyType);
		}
		
		return memberContractPosition;
	}
	
	public MemberContractPosition getIntegralFollowPosition(Long memberId, Long contractCoinId, ContractOrderDirection direction, ContractMoneyType moneyType) {
		MemberContractPosition memberContractPosition = memberContractPositionDao.findCurrentIntegralFollowPosition(memberId, contractCoinId, direction.ordinal(), moneyType.ordinal());
		if(memberContractPosition == null) {
			memberContractPosition =  newIntegralPosition(memberId, contractCoinId, direction, moneyType);
		}
		
		return memberContractPosition;
	}
	
	private MemberContractPosition newIntegralPosition(Long memberId, Long contractCoinId, ContractOrderDirection direction, ContractMoneyType moneyType) {
		MemberContractPosition memberContractPosition = new MemberContractPosition();
		memberContractPosition.setMemberId(memberId);
		memberContractPosition.setTotalPosition(BigDecimal.ZERO);
		memberContractPosition.setClosedPosition(BigDecimal.ZERO);
		memberContractPosition.setFrozenPosition(BigDecimal.ZERO);
		memberContractPosition.setType(ContractPositionType.INTEGRAL);
		memberContractPosition.setMoneyType(moneyType);
		memberContractPosition.setStatus(ContractPositionStatus.STARTED);
		memberContractPosition.setCreateTime(new Date());
		memberContractPosition.setDirection(direction);
		memberContractPosition.setPrincipalAmount(BigDecimal.ZERO);
		memberContractPosition.setTotalPrincipalAmount(BigDecimal.ZERO);
		memberContractPosition.setUsdtProfit(BigDecimal.ZERO);
		memberContractPosition.setCommissionFee(BigDecimal.ZERO);
		memberContractPosition.setCoin(contractCoinService.findOne(contractCoinId));
		return memberContractPosition;
	}
	
	public MemberContractPosition findOne(Long id) {
		return memberContractPositionDao.findOne(id);
	}

	public MemberContractPosition save(MemberContractPosition position) {
		return memberContractPositionDao.save(position);
	}
	
	public List<MemberContractPosition> findCurrentPositions() {
		Criteria<MemberContractPosition> specification = new Criteria<MemberContractPosition>();
		specification.add(Restrictions.ne("status", 2, false));
		specification.add(Restrictions.isNull("followPositionId"));
		return memberContractPositionDao.findAll(specification);
	}
	
	public List<MemberContractPosition> findHistoryPositions() {
		Criteria<MemberContractPosition> specification = new Criteria<MemberContractPosition>();
		specification.add(Restrictions.ne("status", 0, false));
		return memberContractPositionDao.findAll(specification);
	}
	
	public List<MemberContractPosition> findCurrentPositionsByMemberId(Long memberId) {
		Criteria<MemberContractPosition> specification = new Criteria<MemberContractPosition>();
		specification.add(Restrictions.ne("status", 2, false));
		specification.add(Restrictions.eq("memberId", memberId, false));
		specification.add(Restrictions.isNull("followPositionId"));
		return memberContractPositionDao.findAll(specification);	
	}
	
	public List<MemberContractPosition> findCurrentFollowPositionsByMemberId(Long memberId) {
		Criteria<MemberContractPosition> specification = new Criteria<MemberContractPosition>();
		specification.add(Restrictions.ne("status", 2, false));
		specification.add(Restrictions.eq("memberId", memberId, false));
		specification.add(Restrictions.isNotNull("followPositionId", null, false));
		return memberContractPositionDao.findAll(specification);	
	}
	
	public long countCurrentFollowPositionsByPositionId(Long positionId) {
		Criteria<MemberContractPosition> specification = new Criteria<MemberContractPosition>();
		specification.add(Restrictions.ne("status", 2, false));
		specification.add(Restrictions.eq("followPositionId", positionId, false));
		return memberContractPositionDao.count(specification);
	}
	
	public List<MemberContractPosition> findCurrentFollowPositions() {
		Criteria<MemberContractPosition> specification = new Criteria<MemberContractPosition>();
		specification.add(Restrictions.ne("status", 2, false));
		specification.add(Restrictions.isNotNull("followPositionId", null, false));
		return memberContractPositionDao.findAll(specification);	
	}
	
	public List<MemberContractPosition> findCurrentIntegralPositionsByMemberId(Long memberId) {
		Criteria<MemberContractPosition> specification = new Criteria<MemberContractPosition>();
		specification.add(Restrictions.ne("status", 2, false));
		specification.add(Restrictions.eq("type", 0, false));
		specification.add(Restrictions.eq("memberId", memberId, false));
		return memberContractPositionDao.findAll(specification);
	}
	
	public List<BigInteger> findIdListByMemberIdAndContractId(Long memberId, Long contractId) {
		return memberContractPositionDao.findIdListByMemberIdAndContractId(memberId, contractId);
	}
	public List<BigInteger> findIdListByMemberId(Long memberId) {
		return memberContractPositionDao.findIdListByMemberId(memberId);
	}
	
	public List<MemberContractPosition> findPositionsByFollowPositionId(Long positionId) {
		Criteria<MemberContractPosition> specification = new Criteria<MemberContractPosition>();
		specification.add(Restrictions.ne("status", 2, false));
		specification.add(Restrictions.eq("followPositionId", positionId, false));
		return memberContractPositionDao.findAll(specification);
	}
	
	public MemberContractPosition findPostionByMemberIdAndFollowPositionId(Long memberId, Long positionId) {
		Criteria<MemberContractPosition> specification = new Criteria<MemberContractPosition>();
		specification.add(Restrictions.ne("status", 2, false));
		specification.add(Restrictions.eq("memberId", memberId, false));
		specification.add(Restrictions.eq("followPositionId", positionId, false));
		List<MemberContractPosition> list = memberContractPositionDao.findAll(specification);
		if (list.size()>0)
			return list.get(0);
		else
			return null;
	}
	
	public Page<MemberContractPosition> findHistoryPositionsByMemberId(Long memberId, int pageNo, int pageSize) {
		Sort orders = new Sort(new Sort.Order(Sort.Direction.DESC, "createTime"));
		
		Criteria<MemberContractPosition> specification = new Criteria<MemberContractPosition>();
		PageRequest pageRequest = new PageRequest(pageNo - 1, pageSize, orders);
		
		specification.add(Restrictions.ne("status", 0, false));
		specification.add(Restrictions.eq("memberId", memberId, false));
		return memberContractPositionDao.findAll(specification, pageRequest);
	}
	
	public void addCurrentPrice(List<MemberContractPosition> positions, Map<String, BigDecimal> currentPriceMap) {
		for(MemberContractPosition position: positions) {
			position.setCurrentPrice(currentPriceMap.get(position.getCoin().getSymbol()));
		}
	}
	
	public void addCurrentPrice(Set<MemberContractPosition> positions, Map<String, BigDecimal> currentPriceMap) {
		for(MemberContractPosition position: positions) {
			position.setCurrentPrice(currentPriceMap.get(position.getCoin().getSymbol()));
		}
	}
	
	public void addCurrentPrice(Page<MemberContractPositionWithCut> positions, Map<String, BigDecimal> currentPriceMap) {
		for(MemberContractPosition position: positions.getContent()) {
			position.setCurrentPrice(currentPriceMap.get(position.getCoin().getSymbol()));
		}
	}
	
	public void addCurrentPriceForPage(Page<MemberContractPosition> positions, Map<String, BigDecimal> currentPriceMap) {
		for(MemberContractPosition position: positions.getContent()) {
			position.setCurrentPrice(currentPriceMap.get(position.getCoin().getSymbol()));
		}
	}
	
	@Transactional(rollbackFor = Exception.class)
	public void modifyLeverage(Long id, BigDecimal leverage, BigDecimal marginDifference) {
		memberContractPositionDao.modifyLeverage(id, leverage, marginDifference);
	}
	
	public Page<MemberContractPositionWithCut> findAllWithCut(Predicate predicate, Pageable pageable) {
    	Page<MemberContractPosition> all = findAll(predicate, pageable);
    	List<MemberContractPosition> list = all.getContent();
        List<MemberContractPositionWithCut> retList = new ArrayList<MemberContractPositionWithCut>();
        for(MemberContractPosition position :list) {
        	MemberContractPositionWithCut positionWithCut = new MemberContractPositionWithCut();
            BeanUtils.copyProperties(position, positionWithCut);
        	retList.add(positionWithCut);
        	positionWithCut.setCutList(contractOrderEntrustService.queryEntrustCutOrdersByPositionId(position.getId()));
        	
        }
        Page<MemberContractPositionWithCut> newAll = new PageImpl<MemberContractPositionWithCut>(retList, pageable, all.getTotalElements());
        return newAll;
    }
	public Page<MemberContractPosition> findAll(Predicate predicate, Pageable pageable) {
        return memberContractPositionDao.findAll(predicate, pageable);
    }
    
    public void updateIsLock(Long id, int isLock) {
    	memberContractPositionDao.updateIsLock(id, isLock);
    }
    
    public PositionVO sumPositionByCoin(Long contractId) {
    	PositionVO positionVO = new PositionVO();
    	positionVO.setBuyPosition(memberContractPositionDao.sumPositionsByCoinAndDirection(contractId, 0));
    	positionVO.setSellPosition(memberContractPositionDao.sumPositionsByCoinAndDirection(contractId, 1));
    	return positionVO;
    }

    public PositionVO sumSelfPositionByCoin(Long contractId) {
    	PositionVO positionVO = new PositionVO();
    	positionVO.setBuyPosition(memberContractPositionDao.sumSelfPositionsByCoinAndDirection(contractId, 0));
    	positionVO.setSellPosition(memberContractPositionDao.sumSelfPositionsByCoinAndDirection(contractId, 1));
    	return positionVO;
    }

    public PositionVO sumCopyPositionByCoin(Long contractId) {
    	PositionVO positionVO = new PositionVO();
    	positionVO.setBuyPosition(memberContractPositionDao.sumCopyPositionsByCoinAndDirection(contractId, 0));
    	positionVO.setSellPosition(memberContractPositionDao.sumCopyPositionsByCoinAndDirection(contractId, 1));
    	return positionVO;
    }
    
    @Transactional(rollbackFor = Exception.class)
    public void modifyLeverage(Long memberId, Long contractId, BigDecimal leverage, Map<String, BigDecimal> priceMap) throws Exception {
    	BigDecimal totalMarginDifference = BigDecimal.ZERO;
		//List<MemberContractPosition> positions = memberContractPositionService.findCurrentIntegralPositionsByMemberId(authMember.getId());
		List<BigInteger> positionIdList = findIdListByMemberIdAndContractId(memberId, contractId);
		log.info("positionIdList={}", positionIdList);
		for(BigInteger bPositionId: positionIdList) {
			Long positionId = bPositionId.longValue();
			//if(redisLock.waitPositionLock(positionId)) {
				try {
					MemberContractPosition position = findOne(positionId);
					// 体验金仓位杠杆固定，不参与修改
					if(position.getExperienceRecordId() != null) continue;
					position.setCurrentPrice(priceMap.get(position.getCoin().getSymbol()));
					if(leverage.compareTo(position.getLeverage())!=0 && position.getStatus() != ContractPositionStatus.DONE) {
						BigDecimal newMargin = position.getCurrentOpenValue().divide(leverage, 8, BigDecimal.ROUND_DOWN);
						BigDecimal marginDifference = position.getPrincipalAmount().subtract(newMargin);
						memberContractPositionDao.modifyLeverage(position.getId(), leverage, marginDifference);
						totalMarginDifference = totalMarginDifference.add(marginDifference);
					}
				} catch (Exception e) {
					
				} finally {
					//redisLock.unlockPosition(positionId);
				}
			//}
		}
		MemberContractWallet memberContractWallet = memberContractWalletService.findByMemberIdAndContractCoin(memberId, contractCoinService.findOne(contractId));
		memberContractWalletService.modifyUsdtBuyAndSellLeverage(memberContractWallet.getId(), leverage);
		log.info("totalMarginDifference={}", totalMarginDifference);
		int successCount = userPurseService.modifyLeverage(memberId, leverage, totalMarginDifference);
		if(successCount != 1)
			throw new Exception();
    }
    
    @Transactional(rollbackFor = Exception.class, propagation=Propagation.REQUIRES_NEW)
    public void adjustMarginByCurrentPrice(Long memberId, Map<String, BigDecimal> priceMap) {
    	BigDecimal totalMarginDifference = BigDecimal.ZERO;
		List<BigInteger> positionIdList = findIdListByMemberId(memberId);
		if(positionIdList.size()==0)
			return;
		for(BigInteger bPositionId: positionIdList) {
			Long positionId = bPositionId.longValue();
			//if(redisLock.waitPositionLock(positionId)) {
				try {
					MemberContractPosition position = findOne(positionId);
					log.info("adjustMargin position={}", position);
					position.setCurrentPrice(priceMap.get(position.getCoin().getSymbol()));
					BigDecimal newMargin = position.getCurrentOpenValue().divide(position.getLeverage(), 8, BigDecimal.ROUND_DOWN);
					BigDecimal marginDifference = position.getPrincipalAmount().subtract(newMargin);
					memberContractPositionDao.modifyLeverage(position.getId(), position.getLeverage(), marginDifference);
					totalMarginDifference = totalMarginDifference.add(marginDifference);
				} catch (Exception e) {
					
				} finally {
					//redisLock.unlockPosition(positionId);
				}
			//}
		}
		userPurseService.adjustMargin(memberId, totalMarginDifference);
    }
    
    public List<BigInteger> findSupervised(String startTime) {
    	return memberContractPositionDao.findSupervisedMemberList(startTime);
    }
    
    public PositionStatVo findStatVo(Long memberId, String startTime, String endTime) {
    	PositionStatVo vo = new PositionStatVo();
    	Object[] obj = memberContractPositionDao.statSupervised(memberId, startTime, endTime)[0];
    	if(obj[0]!=null)
    		vo.setPositionCount(Integer.parseInt(obj[0].toString()));
    	if(obj[1]!=null)
    		vo.setViolateCount(Integer.parseInt(obj[1].toString()));
    	return vo;
    }
    
    public List<PositionFarmingVo> findFarmingList(long startTime, long endTime) {
    	List<PositionFarmingVo> list = new ArrayList<>();
    	Object[][] objArray = memberContractPositionDao.statFarming(startTime, endTime);
    	for (Object[] obj : objArray) {
        	PositionFarmingVo vo = new PositionFarmingVo();
        	if(obj[0]!=null)
        		vo.setMemberId(Long.parseLong(obj[0].toString()));
        	if(obj[1]!=null)
        		vo.setTotalCount(Integer.parseInt(obj[1].toString()));
        	if(obj[2]!=null)
        		vo.setShortDurationCount(Integer.parseInt(obj[2].toString()));
        	list.add(vo);
    	}
    	return list;
    }

    /**
     * 撤销体验金止盈止损委托单（事务内完成 DB 变更，内存操作由调用方负责）
     * <p>
     * 事务范围：
     * 1. 委托单状态更新为 ENTRUST_CANCEL
     * 2. 体验金记录止盈止损价格清零
     * </p>
     *
     * @param entrustOrder 需撤销的委托单（必须满足：experienceRecordId != null, type == SPOT_LIMIT, entrustType == CLOSE）
     * @param memberId     当前用户 ID（用于权限校验）
     * @throws IllegalArgumentException 委托单不属于当前用户或不符合体验金止盈止损单条件
     * @author auto
     * @date 2026-04-16
     */
    @Transactional(rollbackFor = Exception.class)
    public void cancelExperienceGiftCutOrder(ContractOrderEntrust entrustOrder, Long memberId) {
        // 校验委托单归属
        if (!entrustOrder.getMemberId().equals(memberId)) {
            throw new IllegalArgumentException("委托单不属于当前用户 entrustId=" + entrustOrder.getId());
        }
        // 校验委托单类型
        if (entrustOrder.getExperienceRecordId() == null
                || entrustOrder.getType() != ContractOrderType.SPOT_LIMIT
                || entrustOrder.getEntrustType() != ContractOrderEntrustType.CLOSE) {
            throw new IllegalArgumentException("非体验金止盈止损委托单 entrustId=" + entrustOrder.getId());
        }

        // 1. 将委托单状态更新为已撤销
        contractOrderEntrustService.updateStatusAndDealTime(
                entrustOrder.getId(), ContractOrderEntrustStatus.ENTRUST_CANCEL, DateUtil.getTimeMillis());

        // 2. 清除体验金记录的止盈止损价格
        experienceGiftRecordService.updateStopPrices(
                entrustOrder.getExperienceRecordId(), memberId,
                BigDecimal.ZERO, BigDecimal.ZERO);
    }
}
