package com.copy.trade.service.impl;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.copy.trade.bean.RedisLock;
import com.copy.trade.constant.KafkaTopic;
import com.copy.trade.constant.RedisKey;
import com.copy.trade.dao.ClosePositionMapper;
import com.copy.trade.entity.dto.ClosePositionDeal;
import com.copy.trade.entity.dto.ContractOrderEntrust;
import com.copy.trade.entity.dto.FollowClosePosition;
import com.copy.trade.entity.dto.TraderClosePosition;
import com.copy.trade.entity.form.ClosePositionForm;
import com.copy.trade.entity.po.ClosePosition;
import com.copy.trade.entity.po.CopiedTrade;
import com.copy.trade.entity.po.Customer;
import com.copy.trade.entity.po.Position;
import com.copy.trade.entity.vo.ClosePositionVo;
import com.copy.trade.enums.CloseTypeEnum;
import com.copy.trade.enums.EntrustStatusEnum;
import com.copy.trade.enums.EntrustTypeEnum;
import com.copy.trade.enums.MessageTypeEnum;
import com.copy.trade.enums.PositionStatusEnum;
import com.copy.trade.service.IClosePositionService;
import com.copy.trade.service.ICopiedTradeService;
import com.copy.trade.service.ICustomerService;
import com.copy.trade.service.IMessageService;
import com.copy.trade.service.IPositionService;
import com.copy.trade.service.ITraderLevelService;
import com.copy.trade.util.RandomUtil;

/**
 * <p>
 * 平仓记录 服务实现类
 * </p>
 *
 * @author wz
 * @since 2022-11-22
 */
@Service
public class ClosePositionServiceImpl extends ServiceImpl<ClosePositionMapper, ClosePosition> implements IClosePositionService {

	private final Logger log = LoggerFactory.getLogger(ClosePositionServiceImpl.class);
	
	private String CLOSE_POSITION_PREFIX = "close";

    // positionId -> 需重试的平仓订单
    private final Map<Long, ContractOrderEntrust> closeRetryOrders = new ConcurrentHashMap<>();
    // positionId -> triggerBatchFollowClose 执行时间（毫秒）
    private final Map<Long, Long> closeRetryTimestamps = new ConcurrentHashMap<>();

    @Autowired
    private IPositionService positionServiceImpl;

    @Autowired
    private ICopiedTradeService copiedTradeServiceImpl;

    @Autowired
    private KafkaTemplate kafkaTemplate;

    @Autowired
    private IClosePositionService closePositionServiceImpl;

    @Autowired
    private ICustomerService customerServiceImpl;
    
    @Autowired
    private ITraderLevelService traderLevelServiceImpl;
    
    @Autowired
    private IMessageService messageServiceImpl;
    
    @Autowired
    private RedisLock redisLock;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    /**
     * 非事务入口：通过代理调 doClosePositionDB（事务），事务提交后无需再触发跟单员平仓
     * （带单员平仓通知已由 doge follow-trader-close-notify 接管）。
     */
    @Override
    public Integer traderClosePositionSuccess(ContractOrderEntrust contractOrderEntrust) {
        return closePositionServiceImpl.doClosePositionDB(contractOrderEntrust);
    }

    @Override
    @Transactional
    public Integer doClosePositionDB(ContractOrderEntrust contractOrderEntrust) {
    	log.info("平仓:仓位id:{},order数据:{}",contractOrderEntrust.getPositionId(),JSON.toJSONString(contractOrderEntrust));
        boolean lock = redisLock.lock(RedisKey.LOCK_FOLLOW_POSITION + contractOrderEntrust.getPositionId().toString(), contractOrderEntrust.getPositionId().toString(), 100);
        if(!lock){
            log.info("平仓:仓位id:{},获取锁失败",contractOrderEntrust.getPositionId());
            return 1;
        }
        //1.查询持仓
    	Position position = positionServiceImpl.getByPositionId(contractOrderEntrust.getPositionId());
    	if(position == null){
    		redisLock.unlock(RedisKey.LOCK_FOLLOW_POSITION + contractOrderEntrust.getPositionId().toString(), contractOrderEntrust.getPositionId().toString());
            log.info("平仓:仓位id:{},还未同步等待数据同步",contractOrderEntrust.getPositionId());
            return 2;
    	}
    	//持仓已全部平仓，直接返回
    	if(PositionStatusEnum.DONE.getStatus().equals(position.getStatus())){
    		log.info("平仓:仓位id:{}已全部平仓，跳过处理", contractOrderEntrust.getPositionId());
    		redisLock.unlock(RedisKey.LOCK_FOLLOW_POSITION + contractOrderEntrust.getPositionId().toString(), contractOrderEntrust.getPositionId().toString());
    		return 0;
    	}
    	//2.保存平仓订单
    	ClosePosition closePosition = new ClosePosition();
    	closePosition.setPositionId(position.getPositionId());
    	closePosition.setType(EntrustTypeEnum.getByTypeStr(contractOrderEntrust.getType()).getType());
    	closePosition.setContractOrderEntrustId(contractOrderEntrust.getContractOrderEntrustId());
    	closePosition.setCloseType(0);
    	if(contractOrderEntrust.getCloseType() != null) {
    		closePosition.setCloseType(CloseTypeEnum.getByCloseTypeStr(contractOrderEntrust.getCloseType()).getCloseType());
    	}
    	closePosition.setEntrustPrice(contractOrderEntrust.getEntrustPrice());
    	closePosition.setQuantity(contractOrderEntrust.getVolume());
    	closePosition.setEntrustTime(contractOrderEntrust.getDealTime());
    	
    	//添加原有的平仓成功代码
    	closePosition.setStatus(EntrustStatusEnum.ENTRUST_SUCCESS.getStatus());
        closePosition.setTransactionAmount(contractOrderEntrust.getPrincipalAmount());
        closePosition.setTransactionPrice(contractOrderEntrust.getTradedPrice());
        closePosition.setTransactionTime(contractOrderEntrust.getDealTime());
        closePosition.setTransactionFee(contractOrderEntrust.getCloseFee());
    	closePositionServiceImpl.save(closePosition);
    	
    	
    	//这里默认是部分平仓
    	Integer status = PositionStatusEnum.PARTLYDONE.getStatus();
        if(position.getCurrentPosition().compareTo(closePosition.getQuantity()) <= 0){
            status = PositionStatusEnum.DONE.getStatus();
            position.setPrincipalAmount(new BigDecimal("0"));
        }
        
        BigDecimal closePrice = position.getClosePrice();
        position.setClosePrice(contractOrderEntrust.getTradedPrice());
        //position.setProfitAndLoss(contractOrderEntrust.getProfitAndLoss());
        if(closePrice != null) {
			
			  BigDecimal totalPositionOld = position.getTotalPosition(); 
			  BigDecimal currentPositionOld = position.getCurrentPosition(); 
			  BigDecimal closeVolumeNew = contractOrderEntrust.getVolume();
			  BigDecimal closePriceNew = contractOrderEntrust.getTradedPrice(); 
			  
			  BigDecimal closePriceEnd = ((closePrice.multiply(totalPositionOld.subtract(currentPositionOld))).add(closePriceNew.multiply(
				closeVolumeNew))).divide(closeVolumeNew.add(totalPositionOld.subtract(currentPositionOld)),8, BigDecimal.ROUND_DOWN);
			  position.setClosePrice(closePriceEnd);
        	
        }
        BigDecimal profitAndLoss = position.getProfitAndLoss() == null ? new BigDecimal(0) : position.getProfitAndLoss();
        position.setProfitAndLoss(contractOrderEntrust.getProfitAndLoss().add(profitAndLoss));
        
        position.setCloseTime(contractOrderEntrust.getDealTime());
        position.setAvailablePosition((position.getAvailablePosition().subtract(contractOrderEntrust.getVolume())).compareTo(new BigDecimal("0")) < 0 ? new BigDecimal("0") :(position.getAvailablePosition().subtract(contractOrderEntrust.getVolume())));
        position.setCurrentPosition((position.getCurrentPosition().subtract(contractOrderEntrust.getVolume())).compareTo(new BigDecimal("0")) < 0 ? new BigDecimal("0") :(position.getCurrentPosition().subtract(contractOrderEntrust.getVolume())));
        position.setStatus(status);
        position.setTransactionFee(position.getTransactionFee().add(contractOrderEntrust.getCloseFee()));
        
        if(position.getCurrentPosition() != null && position.getCurrentPosition().compareTo(new BigDecimal("0")) > 0) {
        	position.setPrincipalAmount((position.getPrincipalAmount().subtract(contractOrderEntrust.getPrincipalAmount())).compareTo(new BigDecimal("0")) < 0 ? new BigDecimal("0") :(position.getPrincipalAmount().subtract(contractOrderEntrust.getPrincipalAmount())));
        }
        
        //如果是学生且全部平仓且是盈利的,则根据学生的盈利算出老师的分润,并添加到数据中去
        if(status == PositionStatusEnum.DONE.getStatus() && position.getCustomerId() != position.getTraderId() && contractOrderEntrust.getDividendPercent() != null && (contractOrderEntrust.getDividendPercent()).compareTo(new BigDecimal("0")) > 0) {
        	String contractOrderEntrustId = position.getContractOrderEntrustId();
        	Position positionTrader = positionServiceImpl.getByPositionId(contractOrderEntrust.getFollowId());
        	if(positionTrader == null) {
        		positionTrader = positionServiceImpl.getTraderPosition(position.getCustomerId(),contractOrderEntrustId);
        	}
        	positionTrader.setDividendFee(contractOrderEntrust.getDividendPercent());
        	positionServiceImpl.updateById(positionTrader);
        	position.setDividendFee(contractOrderEntrust.getDividendPercent());
        }
        log.info("平仓:仓位id:{},position数据:{}",contractOrderEntrust.getPositionId(),JSON.toJSONString(position));
        positionServiceImpl.updateById(position);
        redisLock.unlock(RedisKey.LOCK_FOLLOW_POSITION + contractOrderEntrust.getPositionId().toString(), contractOrderEntrust.getPositionId().toString());

    	return 0;
    }

    // triggerBatchFollowClose 已迁移至 doge 侧 follow-trader-close-notify 消费者中实现，
    // copy-trade 不再负责跟单员批量平仓调度。
    @Deprecated
    private void triggerBatchFollowClose(Position traderPosition, ContractOrderEntrust traderOrder) {
        // 与开仓时记录的跟单员数量对比，等待所有跟单员仓位建立完成
        String batchSizeKey = RedisKey.CT_FOLLOW_BATCH_SIZE + traderPosition.getPositionId();
        String expectedSizeStr = stringRedisTemplate.opsForValue().get(batchSizeKey);
        List<Position> followPositions = positionServiceImpl.followPositionByTraderPositionId(traderPosition.getPositionId());
       try {
           if (StringUtils.isNotEmpty(expectedSizeStr)) {
               int expectedSize = Integer.parseInt(expectedSizeStr);
               int attempt = 0;
               while (followPositions.size() < expectedSize && attempt < 6) {
                   log.info("triggerBatchFollowClose: 跟单员仓位尚未全部建立, positionId={}, expected={}, actual={}, attempt={}",
                           traderPosition.getPositionId(), expectedSize, followPositions.size(), attempt);
                   try {
                       Thread.sleep(2000);
                   } catch (InterruptedException ie) {
                       Thread.currentThread().interrupt();
                       break;
                   }
                   followPositions = positionServiceImpl.followPositionByTraderPositionId(traderPosition.getPositionId());
                   attempt++;
               }
           }
       }catch (Exception e){
           log.info("等待开仓结束问题，不再等待进行平仓",e);
       }

        if (followPositions == null || followPositions.isEmpty()) {
            return;
        }

        // traderClosePositionSuccess 已将 availablePosition 扣减，加回来还原平仓前可用量
        BigDecimal preAvailable = traderPosition.getAvailablePosition().add(traderOrder.getVolume());
        if (preAvailable.compareTo(BigDecimal.ZERO) <= 0) preAvailable = traderOrder.getVolume();
        BigDecimal proportion = traderOrder.getVolume().divide(preAvailable, 8, BigDecimal.ROUND_DOWN);
        // 全平：带单员本次平仓量 >= 平仓前可用量
        boolean isFullClose = proportion.compareTo(BigDecimal.ONE) >= 0;

        List<ContractOrderEntrust> batchCloseList = new ArrayList<>();
        followPositions.forEach(followPosition -> {
            BigDecimal positionVolume;
            if (isFullClose) {
                // 全平时取跟单员当前全部持仓，避免 availablePosition 因挂单被锁而平不干净
                positionVolume = followPosition.getCurrentPosition();
            } else {
                if (followPosition.getAvailablePosition() == null
                        || followPosition.getAvailablePosition().compareTo(BigDecimal.ZERO) <= 0) {
                    return;
                }
                positionVolume = followPosition.getAvailablePosition().multiply(proportion);
            }
            if (positionVolume == null || positionVolume.compareTo(BigDecimal.ZERO) <= 0) {
                return;
            }
            positionVolume = positionVolume.setScale(traderOrder.getDecimalScale(), BigDecimal.ROUND_DOWN);
            if (positionVolume.compareTo(BigDecimal.ZERO) <= 0) {
                return;
            }

            // Position 表无 memberId，通过 customerId 查 Customer.memberId
            Customer followCustomer = customerServiceImpl.getById(followPosition.getCustomerId());
            if (followCustomer == null || followCustomer.getMemberId() == null) {
                log.warn("triggerBatchFollowClose: customerId={} 查不到 memberId，跳过", followPosition.getCustomerId());
                return;
            }
            if (followPosition.getTraderPositionId() == null) {
                log.warn("triggerBatchFollowClose: traderPositionId 为空，positionId={}", followPosition.getPositionId());
                return;
            }

            ContractOrderEntrust closeOrder = new ContractOrderEntrust();
            closeOrder.setPositionId(followPosition.getPositionId());
            closeOrder.setMemberId(followCustomer.getMemberId());
            closeOrder.setContractId(traderOrder.getContractId());
            closeOrder.setSymbol(followPosition.getSymbol());
            closeOrder.setCoinSymbol(traderOrder.getCoinSymbol());
            closeOrder.setBaseSymbol(traderOrder.getBaseSymbol());
            closeOrder.setPrincipalUnit(traderOrder.getPrincipalUnit());
            closeOrder.setDirection(String.valueOf(followPosition.getDirection()));
            closeOrder.setVolume(positionVolume);
            closeOrder.setTradedPrice(traderOrder.getTradedPrice());
            closeOrder.setEntrustPrice(traderOrder.getEntrustPrice());
            closeOrder.setDealTime(traderOrder.getDealTime());
            closeOrder.setCloseType(traderOrder.getCloseType());
            closeOrder.setFollowId(followPosition.getTraderPositionId());
            closeOrder.setFollowSystemId(followPosition.getId());
            closeOrder.setDividendPercent(traderOrder.getDividendPercent());
            closeOrder.setLeverage(followPosition.getLeverage());
            closeOrder.setPatterns(traderOrder.getPatterns());
            closeOrder.setPositionType(traderOrder.getPositionType());
            closeOrder.setShareNumber(traderOrder.getShareNumber());
            closeOrder.setIsPercentage(traderOrder.getIsPercentage());
            closeOrder.setEntrustType("1");
            closeOrder.setDecimalScale(traderOrder.getDecimalScale());
            closeOrder.setMoneyType(traderOrder.getMoneyType());
            closeOrder.setType("0");
            batchCloseList.add(closeOrder);
        });
        if (batchCloseList.isEmpty()) return;

        try {
            kafkaTemplate.send("follow-batch-close", JSON.toJSONString(batchCloseList)).get();
            log.info("triggerBatchFollowClose: traderPositionId={}, total={}", traderPosition.getPositionId(), batchCloseList.size());
        } catch (Exception e) {
            log.error("follow-batch-close 发送失败, traderPositionId={}, size={}", traderPosition.getPositionId(), batchCloseList.size(), e);
        }
    }

    @Override
    @Transactional
    public void reconstructOpenAndClose(ContractOrderEntrust order) {
        log.info("reconstructOpenAndClose: 重建开仓平仓记录, positionId={}", order.getPositionId());

        // 1. 通过订单 memberId 查 Customer 获取 customerId（主路径）
        Long customerId = null;
        Long traderId = null;
        if (order.getMemberId() != null) {
            Customer followerCustomer = customerServiceImpl.getByMemberId(order.getMemberId());
            if (followerCustomer != null) {
                customerId = followerCustomer.getId();
            }
        }

        // 2. 通过上级仓位（带单员 position）获取真实开仓数据，同时补全 traderId
        Position traderPos = null;
        if (order.getFollowId() != null) {
            traderPos = positionServiceImpl.getByPositionId(order.getFollowId());
            if (traderPos != null && traderId == null) {
                traderId = traderPos.getCustomerId();
            }
        }

        // traderId 备用：从 CopiedTrade 取
        if (traderId == null && order.getFollowSystemId() != null) {
            CopiedTrade ct = copiedTradeServiceImpl.getById(order.getFollowSystemId());
            if (ct != null) {
                traderId = ct.getTraderId();
                if (customerId == null) {
                    customerId = ct.getCustomerId();
                }
            }
        }

        if (customerId == null) {
            log.error("reconstructOpenAndClose: 无法获取 customerId，跳过重建, positionId={}, memberId={}, followSystemId={}",
                    order.getPositionId(), order.getMemberId(), order.getFollowSystemId());
            return;
        }
        log.info("reconstructOpenAndClose: customerId={}, traderId={}, positionId={}", customerId, traderId, order.getPositionId());

        // 3. 检查是否已存在 Position，避免重复写入
        Position existPos = positionServiceImpl.getByPositionId(order.getPositionId());
        if (existPos != null) {
            log.warn("reconstructOpenAndClose: Position 已存在，改走正常平仓流程, positionId={}", order.getPositionId());
            closePositionServiceImpl.doClosePositionDB(order);
            return;
        }
        // 开仓价：优先取带单员仓位的 openPrice，若无则降级使用平仓价
        BigDecimal openPrice = (traderPos != null && traderPos.getOpenPrice() != null)
                ? traderPos.getOpenPrice() : order.getTradedPrice();
        // 杠杆：优先取带单员仓位的 leverage，若无则取平仓消息中的值
        Integer leverage = (traderPos != null && traderPos.getLeverage() != null)
                ? traderPos.getLeverage() : order.getLeverage();
        // 开仓时间：优先取带单员仓位的 openTime，若无则以平仓时间代替
        Long openTime = (traderPos != null && traderPos.getOpenTime() != null)
                ? traderPos.getOpenTime() : order.getDealTime();
        log.info("reconstructOpenAndClose: openPrice={}, openTime={} (from traderPos={}), positionId={}",
                openPrice, openTime, traderPos != null ? order.getFollowId() : "N/A", order.getPositionId());

        // 4. 直接构建 DONE 状态的 Position（P&L 来自交易所）
        boolean shouldDividend = !customerId.equals(traderId)
                && order.getDividendPercent() != null
                && order.getDividendPercent().compareTo(BigDecimal.ZERO) > 0;

        Position pos = new Position();
        pos.setCustomerId(customerId);
        pos.setPositionId(order.getPositionId());
        pos.setTraderId(traderId);
        pos.setSymbol(order.getSymbol());
        pos.setDirection(com.copy.trade.enums.DirectionEnum.getByDirectionStr(order.getDirection()).getDirection());
        pos.setLeverage(leverage);
        pos.setOpenPrice(openPrice);
        pos.setTotalPosition(order.getVolume());
        pos.setTotalPrincipalAmount(order.getPrincipalAmount());
        pos.setAvailablePosition(BigDecimal.ZERO);
        pos.setCurrentPosition(BigDecimal.ZERO);
        pos.setPrincipalAmount(BigDecimal.ZERO);
        pos.setContractOrderEntrustId(order.getContractOrderEntrustId());
        pos.setStatus(PositionStatusEnum.DONE.getStatus());
        pos.setTransactionFee(order.getCloseFee() != null ? order.getCloseFee() : BigDecimal.ZERO);
        pos.setOpenTime(openTime);
        pos.setCloseTime(order.getDealTime());
        pos.setClosePrice(order.getTradedPrice());
        pos.setProfitAndLoss(order.getProfitAndLoss() != null ? order.getProfitAndLoss() : BigDecimal.ZERO);
        pos.setTraderPositionId(order.getFollowId());
        // 分润（与 doClosePositionDB 保持一致：全平且是学生且有分润比例时写入）
        if (shouldDividend) {
            pos.setDividendFee(order.getDividendPercent());
        }
        positionServiceImpl.save(pos);

        // 更新带单员仓位的分润字段
        if (shouldDividend) {
            if (traderPos == null) {
                traderPos = positionServiceImpl.getByPositionId(order.getFollowId());
            }
            if (traderPos != null) {
                traderPos.setDividendFee(order.getDividendPercent());
                positionServiceImpl.updateById(traderPos);
            } else {
                log.warn("reconstructOpenAndClose: 带单员仓位未找到, followId={}", order.getFollowId());
            }
        }

        // 4. 构建成功状态的 ClosePosition
        ClosePosition cp = new ClosePosition();
        cp.setPositionId(order.getPositionId());
        cp.setType(EntrustTypeEnum.getByTypeStr(order.getType()).getType());
        cp.setContractOrderEntrustId(order.getContractOrderEntrustId());
        cp.setCloseType(order.getCloseType() != null
                ? CloseTypeEnum.getByCloseTypeStr(order.getCloseType()).getCloseType() : 0);
        cp.setEntrustPrice(order.getEntrustPrice());
        cp.setQuantity(order.getVolume());
        cp.setEntrustTime(order.getDealTime());
        cp.setStatus(EntrustStatusEnum.ENTRUST_SUCCESS.getStatus());
        cp.setTransactionAmount(order.getPrincipalAmount());
        cp.setTransactionPrice(order.getTradedPrice());
        cp.setTransactionTime(order.getDealTime());
        cp.setTransactionFee(order.getCloseFee());
        save(cp);

        log.info("reconstructOpenAndClose: 重建完成, positionId={}, customerId={}, traderId={}", order.getPositionId(), customerId, traderId);
    }

    @Override
    @Transactional
    public Boolean traderClosePosition(TraderClosePosition traderClosePosition) {
        //1.查询持仓
        Position position = positionServiceImpl.getByPositionId(traderClosePosition.getPositionId());
        if(position == null){
            return true;
        }
        //2.保存平仓订单
        ClosePosition closePosition = new ClosePosition();
        closePosition.setPositionId(position.getPositionId());
        closePosition.setType(EntrustTypeEnum.getByTypeStr(traderClosePosition.getType()).getType());
        closePosition.setContractOrderEntrustId(traderClosePosition.getContractClosePositionEntrustNo());
        closePosition.setCloseType(CloseTypeEnum.getByCloseTypeStr(traderClosePosition.getCloseType()).getCloseType());
        closePosition.setEntrustPrice(traderClosePosition.getEntrustPrice());
        closePosition.setQuantity(traderClosePosition.getVolume());
        closePosition.setEntrustTime(traderClosePosition.getEntrustTime());
        closePosition.setStatus(EntrustStatusEnum.ENTRUST_ING.getStatus());
        closePositionServiceImpl.save(closePosition);

        //用户平仓修改持仓信息
        //减少可用持仓
        positionServiceImpl.updateAvailablePosition(closePosition.getQuantity().multiply(new BigDecimal("-1")), position.getPositionId());

        //3.验证是否是交易员持仓平仓
        CopiedTrade traderOrder = copiedTradeServiceImpl.getByContractOrderEntrustId(position.getContractOrderEntrustId());
        if(traderOrder.getCopyContractOrderEntrustId() != null) {
            return true;
        }
        //交易员持仓平仓
        //查询跟单持仓
        List<Position> positionList = positionServiceImpl.followPosition(traderOrder.getContractOrderEntrustId());

        //当前可平仓位
        BigDecimal availablePosition = position.getAvailablePosition();
        //本次平仓仓位
        BigDecimal volume = traderClosePosition.getVolume();
        //平仓比例
        BigDecimal proportion = volume.divide(availablePosition,BigDecimal.ROUND_CEILING);
        positionList.forEach(followPosition -> {
            BigDecimal positionVolume = followPosition.getAvailablePosition().multiply(proportion);
            //保存平仓委托
            ClosePosition customerClosePosition = new ClosePosition();
            customerClosePosition.setQuantity(positionVolume);
            customerClosePosition.setEntrustPrice(traderClosePosition.getEntrustPrice());
            customerClosePosition.setStatus(EntrustStatusEnum.ENTRUST_ING.getStatus());
            customerClosePosition.setEntrustTime(System.currentTimeMillis());
            customerClosePosition.setCloseType(closePosition.getCloseType());
            customerClosePosition.setPositionId(followPosition.getPositionId());
            customerClosePosition.setType(closePosition.getType());
            String orderNo = RandomUtil.orderNo(CLOSE_POSITION_PREFIX);
            customerClosePosition.setClosePositionNo(orderNo);
            customerClosePosition.setCopyContractOrderEntrustId(traderClosePosition.getContractClosePositionEntrustNo());
            closePositionServiceImpl.save(customerClosePosition);

            //发送平仓消息
            FollowClosePosition followClosePosition = new FollowClosePosition();
            followClosePosition.setEntrustPrice(traderClosePosition.getEntrustPrice());
            followClosePosition.setVolume(positionVolume);
            followClosePosition.setPositionId(followPosition.getPositionId());
            followClosePosition.setType(traderClosePosition.getType());

            kafkaTemplate.send(KafkaTopic.FOLLOW_CLOSE_POSITION, JSON.toJSONString(followClosePosition));
        });
        return true;
    }


    @Override
    public Boolean followClosePositionCallback(ContractOrderEntrust contractOrderEntrust) {
    	log.info("copyTrade:学生平仓失败,学生memberId:{},order数据:{}",contractOrderEntrust.getMemberId(),JSON.toJSONString(contractOrderEntrust));
        //保存平仓失败的记录
    	ClosePosition closePosition = new ClosePosition();
    	closePosition.setPositionId(contractOrderEntrust.getPositionId());
    	closePosition.setType(EntrustTypeEnum.getByTypeStr(contractOrderEntrust.getType()).getType());
    	closePosition.setContractOrderEntrustId(contractOrderEntrust.getContractOrderEntrustId());
    	closePosition.setCloseType(CloseTypeEnum.getByCloseTypeStr(contractOrderEntrust.getCloseType()).getCloseType());
    	closePosition.setEntrustPrice(contractOrderEntrust.getEntrustPrice());
    	closePosition.setQuantity(contractOrderEntrust.getVolume());
    	closePosition.setEntrustTime(contractOrderEntrust.getDealTime());
    	closePosition.setStatus(EntrustStatusEnum.ENTRUST_FAILURE.getStatus());
        closePosition.setTransactionPrice(contractOrderEntrust.getTradedPrice());
        
    	closePositionServiceImpl.save(closePosition);
        
        return true;
    }

    @Override
    @Transactional
    public Boolean cancel(String contractClosePositionEntrustNo) {
        QueryWrapper<ClosePosition> closePositionQueryWrapper = new QueryWrapper<>();
        closePositionQueryWrapper.eq("contract_close_position_entrust_no",contractClosePositionEntrustNo);
        ClosePosition closePosition = getOne(closePositionQueryWrapper);
        if(closePosition == null || !EntrustStatusEnum.ENTRUST_ING.getStatus().equals(closePosition.getStatus())){
            return true;
        }
        closePosition.setStatus(EntrustStatusEnum.ENTRUST_CANCEL.getStatus());
        closePositionServiceImpl.updateById(closePosition);
        //增加可用持仓
        positionServiceImpl.updateAvailablePosition(closePosition.getQuantity(), closePosition.getPositionId());
        //撤销跟随平仓订单
        QueryWrapper<ClosePosition> followClosePositionQuery = new QueryWrapper<>();
        followClosePositionQuery.eq("follow_contract_close_position_entrust_no",contractClosePositionEntrustNo);
        List<ClosePosition> list = list(followClosePositionQuery);
        list.forEach(followClosePosition ->{
            kafkaTemplate.send(KafkaTopic.CLOSE_POSITION_CANCEL,followClosePosition.getContractOrderEntrustId());
        });
        return true;
    }

    @Override
    @Transactional
    public Boolean closePositionDeal(ClosePositionDeal closePositionDeal) {
        //查询平仓委托
        QueryWrapper<ClosePosition> closePositionQueryWrapper = new QueryWrapper<>();
        closePositionQueryWrapper.eq("contract_close_position_entrust_no",closePositionDeal.getContractClosePositionEntrustNo());
        ClosePosition closePosition = getOne(closePositionQueryWrapper);
        if(!EntrustStatusEnum.ENTRUST_ING.getStatus().equals(closePosition.getStatus())){
            return true;
        }
        //更新平仓
        closePosition.setStatus(EntrustStatusEnum.ENTRUST_SUCCESS.getStatus());
        closePosition.setTransactionAmount(closePositionDeal.getTransactionAmount());
        closePosition.setTransactionPrice(closePositionDeal.getTransactionPrice());
        closePosition.setTransactionTime(closePositionDeal.getTransactionTime());
        closePositionServiceImpl.updateById(closePosition);
        //更新持仓
        Position position = positionServiceImpl.getByPositionId(closePosition.getPositionId());
        Integer status = PositionStatusEnum.PARTLYDONE.getStatus();
        if(position.getCurrentPosition().compareTo(closePosition.getQuantity()) == 0){
            status = PositionStatusEnum.DONE.getStatus();
        }
        positionServiceImpl.updateCurrentPosition(closePosition.getQuantity().multiply(new BigDecimal("-1")),closePosition.getPositionId(),status,closePositionDeal.getTransactionTime());
        return true;
    }

    @Override
    public List<ClosePosition> closePositionByPositionId(Long positionId) {
        QueryWrapper<ClosePosition> closePositionQueryWrapper = new QueryWrapper<>();
        closePositionQueryWrapper.eq("position_id",positionId)
                .eq("status", EntrustStatusEnum.ENTRUST_SUCCESS.getStatus());
        return list(closePositionQueryWrapper);
    }
    
    @Override
    public Page<ClosePositionVo> list(ClosePositionForm closePositionForm) {
    	Page page = closePositionForm.newFormPage();
    	List<ClosePositionVo> list = ((ClosePositionMapper)baseMapper).page(page, closePositionForm);
        page.setRecords(list);
        return page;
    }

    @Override
    public void processCloseRetryQueue() {
        // 跟单员批量平仓调度已迁移至 doge 侧，此方法不再执行任何操作。
    }
}
