package com.bizzan.bitrade.consumer;

import java.math.BigDecimal;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import javax.mail.MessagingException;

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;

import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bizzan.bitrade.constant.NotifyType;
import com.bizzan.bitrade.engine.ContractCoinMatch;
import com.bizzan.bitrade.engine.ContractCoinMatchFactory;
import com.bizzan.bitrade.engine.FollowAccountMonitor;
import com.bizzan.bitrade.entity.CloseType;
import com.bizzan.bitrade.entity.ContractCoin;
import com.bizzan.bitrade.entity.ContractMoneyType;
import com.bizzan.bitrade.entity.ContractOrderDirection;
import com.bizzan.bitrade.entity.ContractOrderEntrust;
import com.bizzan.bitrade.entity.ContractOrderEntrustStatus;
import com.bizzan.bitrade.entity.ContractOrderEntrustType;
import com.bizzan.bitrade.entity.ContractOrderPattern;
import com.bizzan.bitrade.entity.ContractOrderType;
import com.bizzan.bitrade.entity.ContractPositionStatus;
import com.bizzan.bitrade.entity.ContractPositionType;
import com.bizzan.bitrade.entity.FollowAccount;
import com.bizzan.bitrade.entity.KLine;
import com.bizzan.bitrade.entity.Member;
import com.bizzan.bitrade.entity.MemberContractPosition;
import com.bizzan.bitrade.entity.MemberContractWallet;
import com.bizzan.bitrade.service.ContractCoinService;
import com.bizzan.bitrade.service.ContractMarketService;
import com.bizzan.bitrade.service.ContractOrderEntrustService;
import com.bizzan.bitrade.service.FollowWalletService;
import com.bizzan.bitrade.service.KLineService;
import com.bizzan.bitrade.service.MemberContractPositionService;
import com.bizzan.bitrade.service.MemberContractWalletService;
import com.bizzan.bitrade.service.MemberService;
import com.bizzan.bitrade.service.RedisLock;
import com.bizzan.bitrade.service.SpecialFollowWalletService;
import com.bizzan.bitrade.service.SysConfigService;
import com.bizzan.bitrade.service.UserPurseService;
import com.bizzan.bitrade.util.Callback;
import com.bizzan.bitrade.util.DateUtil;
import com.bizzan.bitrade.util.GeneratorUtil;
import com.bizzan.bitrade.util.MessageResult;
import com.bizzan.bitrade.util.RedisManager;
import com.bizzan.bitrade.util.ThreadUtil;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class FollowConsumer {

    @Autowired
    private ContractCoinMatchFactory contractCoinMatchFactory;
    @Autowired
    private ContractCoinService contractCoinService;
    @Autowired
    private ContractOrderEntrustService contractOrderEntrustService;
    @Autowired
    private FollowWalletService followWalletService;
    @Autowired
    private SpecialFollowWalletService specialFollowWalletService;
    @Autowired
    private FollowAccountMonitor followAccountMonitor;
    @Autowired
    private MemberContractPositionService memberContractPositionService;
    @Autowired
    private MemberContractWalletService memberContractWalletService;
    @Autowired
    private MemberService memberService;
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;
    @Autowired
    private UserPurseService userPurseService;
    @Autowired
    private RedisManager redisManager;
    @Autowired
    private KLineService kLineService;
    @Autowired
    private ContractMarketService contractMarketService;
    @Autowired
    private SysConfigService sysConfigService;
    @Autowired
    private RedisLock redisLock;

    /** 批量开仓/平仓并发线程池，150 线程（对应 Druid maxActive=200，留 50 给其他业务） */
    private static final ExecutorService batchExecutor = Executors.newFixedThreadPool(150);

    @KafkaListener(topics = "follow-entrust", containerFactory = "kafkaListenerContainerFactory")
    public void followSwapOrderOpen(List<ConsumerRecord<String, String>> records) throws ParseException {
        log.info("【开仓订单】records.size()={}", records.size());
        for (int i = 0; i < records.size(); i++) {
            log.info("【开仓订单】start handling={}", i);
            ConsumerRecord<String, String> record = records.get(i);
            log.info("【开仓订单】接收跟单订单>>topic={},value={},size={}", record.topic(), record.value(), records.size());
            ContractOrderEntrust order = JSON.parseObject(record.value(), ContractOrderEntrust.class);
            if (order == null) {
                log.info("开仓订单转化失败");
                continue;
            }
            order.setPositionType(ContractPositionType.INTEGRAL); // 跟单强制全仓
            ThreadUtil.executeInThread(new Callback() {
                @Override
                public void execute(Object obj) {
                    ContractCoin contractCoin = contractCoinService.findOne(order.getContractId());
                    if (contractCoin == null) {
                        return;
                    }

                    MemberContractPosition memberContractPosition = memberContractPositionService.findOne(order.getPositionId());
                    if (memberContractPosition.getStatus() == ContractPositionStatus.DONE)
                        return;

                    redisLock.holdFollowPosition(order.getPositionId());
                    log.info("expire={}", redisLock.getExpire("FOLLOW_POSITION_LOCK:" + order.getPositionId()));

                    FollowAccount followAccount = followAccountMonitor.get(order.getMemberId());

                    ContractCoinMatch contractCoinMatch = contractCoinMatchFactory.getContractCoinMatch(order.getSymbol());
                    //contractCoinMatch.genTradedPriceForOrder(order, false);

                    boolean success;
                    if (order.getIsPercentage() != null && order.getIsPercentage() == 1) {
                        success = followAccountMonitor.genVolumeForOrder(order, memberContractPosition.getMemberId());
                        order.setTradedVolume(BigDecimal.ZERO);
                        order.setContractOrderEntrustId(GeneratorUtil.getOrderId("CE"));
                    } else {
                        BigDecimal orderAmount;
                        BigDecimal principalAmount;
                        if (order.getVolume() != null) {
                            BigDecimal volume = order.getVolume().setScale(order.getDecimalScale(), BigDecimal.ROUND_DOWN);
                            if (volume.compareTo(BigDecimal.ZERO) <= 0)
                                return;
                            order.setVolume(volume);

                            orderAmount = order.getVolume().multiply(order.getTradedPrice());
                            principalAmount = orderAmount.divide(BigDecimal.valueOf(order.getLeverage()), 8, BigDecimal.ROUND_DOWN);
                            order.setPrincipalAmount(principalAmount);
                        } else {
                            principalAmount = order.getPrincipalAmount();
                            orderAmount = principalAmount.multiply(BigDecimal.valueOf(order.getLeverage()));
                            BigDecimal volume = orderAmount.divide(order.getTradedPrice(), order.getDecimalScale(), BigDecimal.ROUND_DOWN);
                            if (volume.compareTo(BigDecimal.ZERO) <= 0)
                                return;
                            order.setVolume(volume);
                        }

                        if (followAccount.getAvailableBalanceForOpen().compareTo(order.getPrincipalAmount()) < 0) {
                            kafkaTemplate.send("swap-follow-order-open-fail", JSON.toJSONString(order));
                        }

                        BigDecimal fee;
                        if (contractCoin.getFollowFee() != null)
                            fee = contractCoin.getFollowFee();
                        else
                            fee = contractCoin.getOpenFee();
                        BigDecimal openFee = orderAmount.multiply(fee);
                        order.setOpenFee(openFee);
                        order.setTradedVolume(BigDecimal.ZERO);
                        order.setContractOrderEntrustId(GeneratorUtil.getOrderId("CE"));
                        log.info("冻结跟单保证金, memberId={}, order.getPrincipalAmount()={}", order.getMemberId(), principalAmount);
                        success = followWalletService.freezeMarginAndFee(order.getMemberId(), contractCoin, principalAmount.add(openFee));
                    }
                    if (success) {
                        log.info("冻结跟单保证金成功");
                        order.setId(null);
                        order.setFollowId(order.getPositionId());
                        order.setPositionId(null);
                        order.setStatus(ContractOrderEntrustStatus.ENTRUST_ING);
                        order.setType(ContractOrderType.MARKET_PRICE);
                        contractOrderEntrustService.save(order);

                        // 加入处理引擎
                        contractCoinMatch.trade(order);
                    } else {
                        kafkaTemplate.send("swap-follow-order-open-fail", JSON.toJSONString(order));
                    }
                }
            });


        }
    }

    @KafkaListener(topics = "special-follow-entrust", containerFactory = "kafkaListenerContainerFactory")
    public void specialFollowSwapOrderOpen(List<ConsumerRecord<String, String>> records) throws ParseException {
        for (int i = 0; i < records.size(); i++) {
            ConsumerRecord<String, String> record = records.get(i);
            log.info("【开仓订单】接收特级跟单订单>>topic={},value={},size={}", record.topic(), record.value(), records.size());
            ContractOrderEntrust order = JSON.parseObject(record.value(), ContractOrderEntrust.class);
            if (order == null) {
                log.info("开仓订单转化失败");
                continue;
            }
            order.setPositionType(ContractPositionType.INTEGRAL); // 跟单强制全仓

            ContractCoin contractCoin = contractCoinService.findOne(order.getContractId());
            if (contractCoin == null) {
                continue;
            }
            //if(!redisLock.lockSpecialFollowAccount(order.getMemberId()))
            //	continue;

            ThreadUtil.executeInThread(new Callback() {
                @Override
                public void execute(Object obj) {
					/*if(!redisLock.lockFollowAccount(order.getMemberId())) {
						log.info("跟单订单未获取到跟单账户锁", order);
			            return;
					}*/
                    try {
                        redisLock.holdFollowPosition(order.getPositionId());
                        log.info("expire={}", redisLock.getExpire("FOLLOW_POSITION_LOCK:" + order.getPositionId()));
                        ContractCoinMatch contractCoinMatch = contractCoinMatchFactory.getContractCoinMatch(order.getSymbol());
                        //BigDecimal openFee = order.getOpenFee().multiply(BigDecimal.valueOf(0.6));
                        //order.setOpenFee(openFee);

                        if (order.getIsAdd() != null && order.getIsAdd() == 1) {
                            MemberContractPosition memberContractPosition = memberContractPositionService.findPostionByMemberIdAndFollowPositionId(order.getMemberId(), order.getPositionId());
                            if (memberContractPosition == null || memberContractPosition.getStatus() == ContractPositionStatus.DONE)
                                return;

                            BigDecimal volume = memberContractPosition.getTotalPosition();
                            if (volume.compareTo(BigDecimal.ZERO) <= 0)
                                return;
                            order.setVolume(volume);

                            BigDecimal orderAmount = order.getVolume().multiply(order.getTradedPrice());
                            BigDecimal principalAmount = orderAmount.divide(BigDecimal.valueOf(order.getLeverage()), 8, BigDecimal.ROUND_DOWN);
                            order.setPrincipalAmount(principalAmount);
                            BigDecimal fee;
                            if (contractCoin.getFollowFee() != null)
                                fee = contractCoin.getFollowFee();
                            else
                                fee = contractCoin.getOpenFee();
                            BigDecimal openFee = orderAmount.multiply(fee);
                            order.setOpenFee(openFee);

                            boolean success = followWalletService.freezeMarginAndFee(order.getMemberId(), contractCoin, order.getPrincipalAmount().add(order.getOpenFee()));
                            if (success) {
                                log.info("冻结特级跟单加仓保证金成功");
                                order.setId(null);
                                order.setContractOrderEntrustId(GeneratorUtil.getOrderId("CE"));
                                order.setTradedVolume(BigDecimal.ZERO);
                                order.setFollowId(order.getPositionId());
                                order.setPositionId(null);
                                order.setStatus(ContractOrderEntrustStatus.ENTRUST_ING);

                                // 加入处理引擎
                                contractCoinMatch.trade(order);
                            } else {
                                log.info("冻结特级跟单加仓保证金失败");
                                kafkaTemplate.send("swap-follow-order-open-fail", JSON.toJSONString(order));
                            }
                        } else {
                            log.info("不是加仓，开正反向单");

                            //boolean success = followWalletService.freezeMarginAndFee(order.getMemberId(), contractCoin, order.getPrincipalAmount().add(order.getOpenFee()));
                            boolean success = followAccountMonitor.genVolumeForOrder(order, order.getMemberId());
                            if (success) {
                                log.info("冻结特级跟单保证金成功");
                                order.setId(null);
                                order.setContractOrderEntrustId(GeneratorUtil.getOrderId("CE"));
                                order.setTradedVolume(BigDecimal.ZERO);
                                order.setFollowId(order.getPositionId());
                                order.setPositionId(null);
                                order.setStatus(ContractOrderEntrustStatus.ENTRUST_ING);

                                // 加入处理引擎
                                contractCoinMatch.trade(order);
                            } else {
                                log.info("冻结特级跟单保证金失败");
                                kafkaTemplate.send("swap-follow-order-open-fail", JSON.toJSONString(order));
                                return;
                            }

                            if (order.getDirection() == ContractOrderDirection.BUY)
                                order.setDirection(ContractOrderDirection.SELL);
                            else
                                order.setDirection(ContractOrderDirection.BUY);
                            order.setContractOrderEntrustId(GeneratorUtil.getOrderId("CE"));
                            order.setTradedVolume(BigDecimal.ZERO);
                            order.setFollowId(order.getHedgeId());
                            order.setPositionId(null);
                            order.setId(null);
                            order.setStatus(ContractOrderEntrustStatus.ENTRUST_ING);

                            success = followWalletService.freezeMarginAndFee(order.getMemberId(), contractCoin, order.getPrincipalAmount().add(order.getOpenFee()));
                            if (success) {
                                log.info("冻结特级反向跟单保证金成功");
                                //contractOrderEntrustService.save(order);

                                // 加入处理引擎
                                contractCoinMatch.trade(order);
                            } else {
                                log.info("冻结特级反向跟单保证金失败");
                            }
                        }
                    } catch (Exception e) {
                        log.error(e.getMessage(), e);
                        return;
                    } finally {
                        //redisLock.unlockFollowAccount(order.getMemberId());
                    }
                }
            });
        }
    }

    @KafkaListener(topics = "swap-special-trader-order-open-done", containerFactory = "kafkaListenerContainerFactory")
    public void followSwapTraderOrderOpen(List<ConsumerRecord<String, String>> records) throws ParseException {
        for (int i = 0; i < records.size(); i++) {
            ConsumerRecord<String, String> record = records.get(i);
            log.info("【开仓订单】接收特级交易员开仓订单>>topic={},value={},size={}", record.topic(), record.value(), records.size());
            ContractOrderEntrust order = JSON.parseObject(record.value(), ContractOrderEntrust.class);
            if (order == null) {
                log.info("开仓订单转化失败");
                continue;
            }

            ContractCoin contractCoin = contractCoinService.findOne(order.getContractId());
            if (contractCoin == null) {
                continue;
            }

            //int specialTraderStatus = redisManager.getSpecialTraderStatus(order.getSymbol(), order.getMemberId());
            //if(specialTraderStatus!=0)
            //	return;
            if (order.getHedgeId() != null)
                continue;

            if (order.getIsAdd() != null && order.getIsAdd() == 1) {
                kafkaTemplate.send("swap-order-open-done", JSON.toJSONString(order));
                continue;
            }

            if (order.getDirection() == ContractOrderDirection.BUY)
                order.setDirection(ContractOrderDirection.SELL);
            else
                order.setDirection(ContractOrderDirection.BUY);
            order.setContractOrderEntrustId(GeneratorUtil.getOrderId("CE"));
            order.setTradedVolume(BigDecimal.ZERO);
            order.setHedgeId(order.getPositionId());
            order.setPositionId(null);
            order.setId(null);
            order.setStatus(ContractOrderEntrustStatus.ENTRUST_ING);

            ContractCoinMatch contractCoinMatch = contractCoinMatchFactory.getContractCoinMatch(order.getSymbol());

            boolean success = userPurseService.freezeMarginAndFee(order.getMemberId(), contractCoin, order.getPrincipalAmount().add(order.getOpenFee()));
            if (success) {
                //特级交易已开始
                //redisManager.saveSpecialTraderStatus(order.getSymbol(), order.getMemberId(), 1);
                //log.info("冻结特级交易员对冲保证金成功, status={}", redisManager.getSpecialTraderStatus(order.getSymbol(), order.getMemberId()));
                log.info("冻结特级交易员对冲保证金成功");
                contractOrderEntrustService.save(order);

                // 加入处理引擎
                contractCoinMatch.trade(order);
                // hedgeId = 原始 positionId，发 MQ 前上锁，等批量开仓完成后解锁
                redisLock.lockFollowBatchOpen(order.getHedgeId());
                kafkaTemplate.send("swap-order-open-done", JSON.toJSONString(order));
                //redisManager.saveSpecialTraderStatus(order.getSymbol(), order.getMemberId(), 2);
            } else {
                log.info("冻结特级交易员对冲保证金失败");
            }
        }
    }

    @KafkaListener(topics = "swap-special-trader-reset", containerFactory = "kafkaListenerContainerFactory")
    public void specialTraderReset(List<ConsumerRecord<String, String>> records) throws ParseException, InterruptedException {
        for (int i = 0; i < records.size(); i++) {
            ConsumerRecord<String, String> record = records.get(i);
            log.info("【平仓订单】接收特级交易员平仓订单>>topic={},value={},size={}", record.topic(), record.value(), records.size());
            JSONObject jsonObject = JSON.parseObject(record.value());
            if (jsonObject == null) {
                log.info("平仓订单转化失败");
                return;
            }
            redisManager.removeSpecialTraderStatus(jsonObject.getString("symbol"), jsonObject.getLong("memberId"));
        }
    }

    @KafkaListener(topics = "swap-special-trader-order-close", containerFactory = "kafkaListenerContainerFactory")
    public void specialTraderOrderClose(List<ConsumerRecord<String, String>> records) throws ParseException, InterruptedException {
        for (int i = 0; i < records.size(); i++) {
            ConsumerRecord<String, String> record = records.get(i);
            log.info("【平仓订单】接收特级交易员平仓订单>>topic={},value={},size={}", record.topic(), record.value(), records.size());
            JSONObject jsonObject = JSON.parseObject(record.value());
            if (jsonObject == null) {
                log.info("平仓订单转化失败");
                continue;
            }
            ContractOrderDirection direction = jsonObject.getInteger("direction") == 0 ? ContractOrderDirection.BUY : ContractOrderDirection.SELL;
            BigDecimal openPrice = jsonObject.getBigDecimal("openPrice");
            BigDecimal closePrice = jsonObject.getBigDecimal("closePrice");
            BigDecimal amount = jsonObject.getBigDecimal("amount");
            String symbol = jsonObject.getString("symbol");
            Long memberId = jsonObject.getLong("memberId");

            //int specialTraderStatus = redisManager.getSpecialTraderStatus(symbol, memberId);
            //if(specialTraderStatus!=2)
            //	continue;

            ContractCoinMatch contractCoinMatch = contractCoinMatchFactory.getContractCoinMatch(symbol);
            if (contractCoinMatch == null)
                continue;
            BigDecimal nowPrice = contractCoinMatch.getNowPrice();

            long dealTime = 0;
            KLine kLine = kLineService.getKLine(symbol, "1min");
            log.info("kLine={}", kLine);
            if (kLine.getHighestPrice().compareTo(closePrice) >= 0 && kLine.getLowestPrice().compareTo(closePrice) <= 0)
                dealTime = DateUtil.getTimeMillis();
            else
                dealTime = contractMarketService.findTimeByPrice(symbol, closePrice, 5, false);

            //Member member = memberService.getSpecialTrader();
            List<MemberContractPosition> positions = memberContractPositionService.findCurrentPositionsByMemberId(memberId);
            for (MemberContractPosition position : positions) {
                if (!position.getCoin().getSymbol().equals(symbol))
                    continue;

                if (position.getDirection() == direction) {
                    //kafkaTemplate.send("swap-order-close-all", JSON.toJSONString(position));
                    position.setCloseType(CloseType.MARKET_CLOSE);
                    contractCoinMatch.dealAllCloseOrder(position, closePrice, dealTime);
                } else {
                    ContractCoin contractCoin = position.getCoin();

                    //BigDecimal difference = profit.divide(position.getCurrentPosition(), 8, BigDecimal.ROUND_DOWN);
                    BigDecimal tradedPrice = openPrice;
		            /*if(direction == ContractOrderDirection.BUY) {
		            	tradedPrice = nowPrice.add(difference);
		            } else {
		            	tradedPrice = nowPrice.subtract(difference);
		            }*/
                    ContractOrderEntrust orderEntrust = new ContractOrderEntrust();
                    orderEntrust.setContractId(contractCoin.getId()); // 合约ID
                    orderEntrust.setMemberId(position.getMemberId()); // 用户ID
                    orderEntrust.setSymbol(contractCoin.getSymbol()); // 交易对符号
                    orderEntrust.setBaseSymbol(contractCoin.getSymbol().split("/")[1]); // 基币/结算币
                    orderEntrust.setCoinSymbol(contractCoin.getSymbol().split("/")[0]); // 币种符号
                    orderEntrust.setDirection(position.getDirection());
                    orderEntrust.setContractOrderEntrustId(GeneratorUtil.getOrderId("CE"));
                    orderEntrust.setVolume(amount); // 平仓张数
                    orderEntrust.setTradedVolume(BigDecimal.ZERO); // 已交易数量
                    orderEntrust.setTradedPrice(tradedPrice); // 成交价格
                    orderEntrust.setPrincipalUnit("USDT"); // 保证金单位
                    orderEntrust.setCreateTime(DateUtil.getTimeMillis()); // 开仓时间
                    orderEntrust.setType(ContractOrderType.MARKET_PRICE);
                    orderEntrust.setTriggerPrice(BigDecimal.ZERO); // 触发价
                    orderEntrust.setEntrustPrice(BigDecimal.ZERO); // 委托价格
                    orderEntrust.setEntrustType(ContractOrderEntrustType.OPEN); // 平仓
                    orderEntrust.setTriggeringTime(0L); // 触发时间，暂时无效
                    orderEntrust.setShareNumber(contractCoin.getShareNumber());
                    orderEntrust.setProfitAndLoss(BigDecimal.ZERO); // 盈亏（仅平仓计算）
                    orderEntrust.setPatterns(ContractOrderPattern.CROSSED); // 仓位模式
                    orderEntrust.setCloseFee(BigDecimal.ZERO);
                    orderEntrust.setStatus(ContractOrderEntrustStatus.ENTRUST_ING);
                    orderEntrust.setCurrentPrice(tradedPrice);
                    orderEntrust.setIsBlast(0); // 不是爆仓单
                    orderEntrust.setIsFromSpot(0);
                    orderEntrust.setPrincipalAmount(BigDecimal.ZERO); // 全部多仓保证金
                    orderEntrust.setPositionId(position.getId());
                    orderEntrust.setLeverage(position.getLeverage().intValue());
                    orderEntrust.setUsdtOpenPrice(position.getOpenPrice());
                    orderEntrust.setCloseType(position.getCloseType());
                    orderEntrust.setMoneyType(position.getMoneyType());
                    orderEntrust.setPositionType(position.getType());
                    orderEntrust.setIsSpecial(2);
                    //orderEntrust.setTradedPrice(nowPrice);
                    orderEntrust.setDecimalScale(position.getDecimalScale());
                    orderEntrust.setUsdtOpenTime(contractMarketService.findTimeByPrice(contractCoin.getSymbol(), tradedPrice, 50, true));
                    BigDecimal principleAmount = tradedPrice.multiply(orderEntrust.getVolume()).divide(position.getLeverage(), 8, BigDecimal.ROUND_DOWN);
                    BigDecimal openFee = tradedPrice.multiply(orderEntrust.getVolume()).multiply(contractCoin.getOpenFee()).setScale(8, BigDecimal.ROUND_DOWN);

                    orderEntrust.setPrincipalAmount(principleAmount); // 保证金数量
                    orderEntrust.setOpenFee(openFee);
                    orderEntrust.setStatus(ContractOrderEntrustStatus.ENTRUST_ING);

                    boolean success = userPurseService.freezeMarginAndFee(orderEntrust.getMemberId(), contractCoin, orderEntrust.getPrincipalAmount().add(orderEntrust.getOpenFee()));
                    if (success) {
                        log.info("冻结特级交易员保证金成功");
                        log.info("order={}", contractOrderEntrustService.save(orderEntrust));

                        // 加入处理引擎
                        contractCoinMatch.trade(orderEntrust);
                    } else {
                        log.info("冻结特级交易员保证金失败");
                    }
                    position.setTotalPosition(position.getTotalPosition().add(orderEntrust.getVolume()));
                    position.setCloseType(CloseType.MARKET_CLOSE);

                    do {
                        try {
                            log.info("still in opening process, wait 5s for close");
                            Thread.sleep(5000);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    } while (redisLock.hasFollowPosition(position.getId()));

                    //kafkaTemplate.send("swap-order-close-all", JSON.toJSONString(position));
                    contractCoinMatch.dealAllCloseOrder(position, closePrice, dealTime);

                    //redisManager.removeSpecialTraderStatus(contractCoin.getSymbol(), memberId);
                }
            }
        }
    }

    @KafkaListener(topics = "swap-inner-trader-open-close", containerFactory = "kafkaListenerContainerFactory")
    public void innerTraderOpenClose(List<ConsumerRecord<String, String>> records) throws ParseException, InterruptedException {
        for (int i = 0; i < records.size(); i++) {
            ConsumerRecord<String, String> record = records.get(i);
            log.info("【平仓订单】接收特级交易员平仓订单>>topic={},value={},size={}", record.topic(), record.value(), records.size());
            JSONObject jsonObject = JSON.parseObject(record.value());
            if (jsonObject == null) {
                log.info("平仓订单转化失败");
                return;
            }
            ContractOrderDirection direction = jsonObject.getInteger("direction") == 0 ? ContractOrderDirection.BUY : ContractOrderDirection.SELL;
            BigDecimal openPrice = jsonObject.getBigDecimal("openPrice");
            BigDecimal closePrice = jsonObject.getBigDecimal("closePrice");
            BigDecimal amount = jsonObject.getBigDecimal("amount");
            String symbol = jsonObject.getString("symbol");
            Long memberId = jsonObject.getLong("memberId");

            ContractCoinMatch contractCoinMatch = contractCoinMatchFactory.getContractCoinMatch(symbol);
            if (contractCoinMatch == null)
                return;
            BigDecimal nowPrice = contractCoinMatch.getNowPrice();

            Long[] startAndEndTime;

            KLine kLine = kLineService.getKLine(symbol, "1min");
            int length = 50;
            log.info("kLine={}", kLine);
            if (kLine.getHighestPrice().compareTo(closePrice) >= 0 && kLine.getLowestPrice().compareTo(closePrice) <= 0) {
                startAndEndTime = new Long[2];
                startAndEndTime[1] = DateUtil.getTimeMillis();
                startAndEndTime[0] = contractMarketService.findStartTimeByPrice(symbol, openPrice, length);
            } else {
                startAndEndTime = contractMarketService.findStartAndEndTimeByPrice(symbol, openPrice, closePrice, length);
            }
            if (startAndEndTime[0] == 0) {
                log.info("can not find kLine");
                return;
            }

            ContractCoin contractCoin = contractCoinService.findBySymbol(symbol);
            MemberContractWallet memberContractWallet = memberContractWalletService.findByMemberIdAndContractCoin(memberId, contractCoin);

            ContractOrderEntrust orderEntrust = new ContractOrderEntrust();
            orderEntrust.setContractId(contractCoin.getId()); // 合约ID
            orderEntrust.setMemberId(memberId); // 用户ID
            orderEntrust.setSymbol(contractCoin.getSymbol()); // 交易对符号
            orderEntrust.setBaseSymbol(contractCoin.getSymbol().split("/")[1]); // 基币/结算币
            orderEntrust.setCoinSymbol(contractCoin.getSymbol().split("/")[0]); // 币种符号
            orderEntrust.setDirection(direction);
            orderEntrust.setContractOrderEntrustId(GeneratorUtil.getOrderId("CE"));
            orderEntrust.setVolume(amount); // 平仓张数
            orderEntrust.setTradedVolume(BigDecimal.ZERO); // 已交易数量
            orderEntrust.setTradedPrice(openPrice); // 成交价格
            orderEntrust.setPrincipalUnit("USDT"); // 保证金单位
            orderEntrust.setCreateTime(DateUtil.getTimeMillis()); // 开仓时间
            orderEntrust.setType(ContractOrderType.MARKET_PRICE);
            orderEntrust.setTriggerPrice(BigDecimal.ZERO); // 触发价
            orderEntrust.setEntrustPrice(BigDecimal.ZERO); // 委托价格
            orderEntrust.setEntrustType(ContractOrderEntrustType.OPEN); // 平仓
            orderEntrust.setTriggeringTime(0L); // 触发时间，暂时无效
            orderEntrust.setShareNumber(contractCoin.getShareNumber());
            orderEntrust.setProfitAndLoss(BigDecimal.ZERO); // 盈亏（仅平仓计算）
            orderEntrust.setPatterns(ContractOrderPattern.CROSSED); // 仓位模式
            orderEntrust.setCloseFee(BigDecimal.ZERO);
            orderEntrust.setStatus(ContractOrderEntrustStatus.ENTRUST_ING);
            orderEntrust.setCurrentPrice(openPrice);
            orderEntrust.setIsBlast(0); // 不是爆仓单
            orderEntrust.setIsFromSpot(0);
            orderEntrust.setPrincipalAmount(BigDecimal.ZERO); // 全部多仓保证金
            orderEntrust.setLeverage(memberContractWallet.getUsdtBuyLeverage().intValue());
            orderEntrust.setUsdtOpenPrice(openPrice);
            orderEntrust.setMoneyType(ContractMoneyType.USDT);
            orderEntrust.setPositionType(ContractPositionType.INTEGRAL);
            orderEntrust.setIsSpecial(2);
            //orderEntrust.setTradedPrice(nowPrice);
            orderEntrust.setDecimalScale(contractCoin.getCoinScale());
            orderEntrust.setUsdtOpenTime(startAndEndTime[0]);
            BigDecimal principleAmount = openPrice.multiply(orderEntrust.getVolume()).divide(memberContractWallet.getUsdtBuyLeverage(), 8, BigDecimal.ROUND_DOWN);
            BigDecimal openFee = openPrice.multiply(orderEntrust.getVolume()).multiply(contractCoin.getOpenFee()).setScale(8, BigDecimal.ROUND_DOWN);

            orderEntrust.setPrincipalAmount(principleAmount); // 保证金数量
            orderEntrust.setOpenFee(openFee);
            orderEntrust.setStatus(ContractOrderEntrustStatus.ENTRUST_ING);

            boolean success = userPurseService.freezeMarginAndFee(orderEntrust.getMemberId(), contractCoin, orderEntrust.getPrincipalAmount().add(orderEntrust.getOpenFee()));
            if (success) {
                log.info("冻结特级交易员保证金成功");
                log.info("order={}", contractOrderEntrustService.save(orderEntrust));

                // 加入处理引擎
                contractCoinMatch.trade(orderEntrust);
            } else {
                log.info("冻结特级交易员保证金失败");
            }
            Thread.sleep(10000);

            List<MemberContractPosition> positions = memberContractPositionService.findCurrentPositionsByMemberId(memberId);
            for (MemberContractPosition position : positions) {
                if (!position.getCoin().getSymbol().equals(symbol))
                    continue;

                if (position.getDirection() == direction) {
                    position.setCloseType(CloseType.MARKET_CLOSE);
                    contractCoinMatch.dealAllCloseOrder(position, closePrice, startAndEndTime[1]);
                }
            }
        }
    }

    @KafkaListener(topics = "swap-inner-trader-order-close", containerFactory = "kafkaListenerContainerFactory")
    public void innerTraderOrderClose(List<ConsumerRecord<String, String>> records) throws ParseException, InterruptedException {
        for (int i = 0; i < records.size(); i++) {
            ConsumerRecord<String, String> record = records.get(i);
            log.info("【平仓订单】接收特级交易员平仓订单>>topic={},value={},size={}", record.topic(), record.value(), records.size());
            JSONObject jsonObject = JSON.parseObject(record.value());
            if (jsonObject == null) {
                log.info("平仓订单转化失败");
                return;
            }
            ContractOrderDirection direction = jsonObject.getInteger("direction") == 0 ? ContractOrderDirection.BUY : ContractOrderDirection.SELL;
            BigDecimal openPrice = jsonObject.getBigDecimal("openPrice");
            BigDecimal closePrice = jsonObject.getBigDecimal("closePrice");
            BigDecimal amount = jsonObject.getBigDecimal("amount");
            String symbol = jsonObject.getString("symbol");
            Long memberId = jsonObject.getLong("memberId");

            ContractCoinMatch contractCoinMatch = contractCoinMatchFactory.getContractCoinMatch(symbol);
            if (contractCoinMatch == null)
                return;
            BigDecimal nowPrice = contractCoinMatch.getNowPrice();


            List<MemberContractPosition> positions = memberContractPositionService.findCurrentPositionsByMemberId(memberId);
            for (MemberContractPosition position : positions) {
                if (!position.getCoin().getSymbol().equals(symbol))
                    continue;

                Long endTime;

                KLine kLine = kLineService.getKLine(symbol, "1min");
                log.info("kLine={}", kLine);
                if (kLine.getHighestPrice().compareTo(closePrice) >= 0 && kLine.getLowestPrice().compareTo(closePrice) <= 0) {
                    endTime = DateUtil.getTimeMillis();
                } else {
                    int length = (int) ((DateUtil.getTimeMillis() - position.getCreateTime().getTime()) / 1000) - 1;
                    log.info("length={}", length);
                    endTime = contractMarketService.findStartTimeByPrice(symbol, closePrice, length);
                }
                if (endTime == 0) {
                    log.info("can not find kLine");
                    return;
                }

                if (position.getDirection() != direction) {
                    position.setCloseType(CloseType.MARKET_CLOSE);
                    contractCoinMatch.dealAllCloseOrder(position, closePrice, endTime);
                }
            }
        }
    }

    @KafkaListener(topics = "swap-inner-trader-set-back-length", containerFactory = "kafkaListenerContainerFactory")
    public void innerTraderBackLengthSet(List<ConsumerRecord<String, String>> records) throws ParseException, InterruptedException {
        log.info("【开仓前移】Kafka消费者接收到消息, topic=swap-inner-trader-set-back-length, records.size={}", records.size());
        for (int i = 0; i < records.size(); i++) {
            ConsumerRecord<String, String> record = records.get(i);
            log.info("【开仓前移】处理Kafka消息, partition={}, offset={}, value={}", record.partition(), record.offset(), record.value());
            try {
                JSONObject jsonObject = JSON.parseObject(record.value());
                if (jsonObject == null) {
                    log.error("【开仓前移】JSON解析失败, value为null或格式错误, value={}", record.value());
                    continue;
                }
                Integer backLength = jsonObject.getInteger("backLength");
                if (backLength == null) {
                    log.error("【开仓前移】backLength字段为空, jsonObject={}", jsonObject);
                    continue;
                }
                log.info("【开仓前移】准备保存到Redis, backLength={}", backLength);
                redisManager.saveBackLength(backLength);
                log.info("【开仓前移】保存到Redis成功, backLength={}", backLength);
            } catch (Exception e) {
                log.error("【开仓前移】处理消息异常, value={}, error={}", record.value(), e.getMessage(), e);
            }
        }
        log.info("【开仓前移】Kafka消息处理完成, records.size={}", records.size());
    }


    @KafkaListener(topics = "swap-trader-order-close-done", containerFactory = "kafkaListenerContainerFactory")
    public void followSwapTraderOrderClose(List<ConsumerRecord<String, String>> records) throws ParseException {
        for (int i = 0; i < records.size(); i++) {
            ConsumerRecord<String, String> record = records.get(i);
            log.info("【平仓订单】接收交易员平仓订单>>topic={},value={},size={}", record.topic(), record.value(), records.size());
            ContractOrderEntrust order = JSON.parseObject(record.value(), ContractOrderEntrust.class);
            if (order == null) {
                log.info("平仓订单转化失败");
                return;
            }

            ContractCoin contractCoin = contractCoinService.findOne(order.getContractId());
            if (contractCoin == null) {
                continue;
            }

            while (redisLock.hasFollowPosition(order.getPositionId())) {
                try {
                    log.info("still in opening process, wait 5s for close");
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }

            ContractCoinMatch contractCoinMatch = contractCoinMatchFactory.getContractCoinMatch(order.getSymbol());

            List<MemberContractPosition> positions = memberContractPositionService.findPositionsByFollowPositionId(order.getPositionId());
            log.info("positions={}", positions);
            for (MemberContractPosition position : positions) {
                //BigDecimal orderAmount = order.getPrincipalAmount().multiply(BigDecimal.valueOf(order.getLeverage()));
                ThreadUtil.executeInThread(new Callback() {
                    @Override
                    public void execute(Object obj) {
                        ContractOrderEntrust followOrder = new ContractOrderEntrust();
                        BeanUtils.copyProperties(order, followOrder);

                        BigDecimal volume;
                        if (order.getVolume().compareTo(BigDecimal.ONE) == 0)
                            volume = position.getCurrentPosition();
                        else
                            volume = position.getCurrentPosition().multiply(order.getVolume()).setScale(order.getDecimalScale(), BigDecimal.ROUND_DOWN);
                        if (volume.compareTo(BigDecimal.ZERO) == 0) {
                            log.info("跟单平仓订单, 数据为0, positionId={}", position.getId());
                            return;
                        }
                        followOrder.setVolume(volume);
                        followOrder.setTradedVolume(BigDecimal.ZERO);
                        followOrder.setMemberId(position.getMemberId());
                        followOrder.setPrincipalAmount(volume.multiply(order.getTradedPrice()).divide(position.getLeverage(), 8, BigDecimal.ROUND_HALF_UP));
                        followOrder.setCloseFee(BigDecimal.ZERO);
                        followOrder.setId(null);
                        followOrder.setFollowId(followOrder.getPositionId());
                        followOrder.setPositionId(position.getId());
                        followOrder.setContractOrderEntrustId(GeneratorUtil.getOrderId("CE"));
                        followOrder.setStatus(ContractOrderEntrustStatus.ENTRUST_ING);
                        followOrder.setIsSpecial(position.getIsSpecial());
                        followOrder.setType(ContractOrderType.MARKET_PRICE);
                        if (followOrder.getCloseType() == CloseType.BLAST) {
                            followOrder.setCloseType(CloseType.MARKET_CLOSE);
                        }
                        followOrder.setProfitAndLoss(BigDecimal.ZERO);

                        int successCount = memberContractPositionService.freezePosition(position.getId(), volume);
                        if (successCount == 1) {
                            contractOrderEntrustService.save(followOrder);

                            log.info("followOrder={}", followOrder);

                            // 加入处理引擎
                            contractCoinMatch.trade(followOrder);
                        } else {
                            log.info("freezePosition failed followOrder={}", followOrder);
                            kafkaTemplate.send("swap-follow-order-close-fail", JSON.toJSONString(followOrder));
                        }
                    }
                });
            }
        }
    }

    @KafkaListener(topics = "swap-follow-position-open", containerFactory = "kafkaListenerContainerFactory")
    public void followPositionOpen(List<ConsumerRecord<String, String>> records) throws ParseException {
        for (int i = 0; i < records.size(); i++) {
            ConsumerRecord<String, String> record = records.get(i);
            MemberContractPosition position = JSON.parseObject(record.value(), MemberContractPosition.class);
            if (position == null) {
                log.info("仓位转化失败");
                return;
            }

            //followPositionMonitor.addPosition(position);
        }
    }

    @KafkaListener(topics = "swap-follow-position-close", containerFactory = "kafkaListenerContainerFactory")
    public void followPositionClose(List<ConsumerRecord<String, String>> records) throws ParseException {
        for (int i = 0; i < records.size(); i++) {
            ConsumerRecord<String, String> record = records.get(i);
            MemberContractPosition position = JSON.parseObject(record.value(), MemberContractPosition.class);
            if (position == null) {
                log.info("仓位转化失败");
                return;
            }

            //followPositionMonitor.removePosition(position);
        }
    }

    @KafkaListener(topics = "swap-follow-position-force-close", containerFactory = "kafkaListenerContainerFactory")
    public void followPositionForceClose(List<ConsumerRecord<String, String>> records) throws ParseException {
        for (int i = 0; i < records.size(); i++) {
            ConsumerRecord<String, String> record = records.get(i);
            JSONObject object = JSON.parseObject(record.value());
            if (object == null) {
                log.info("仓位转化失败");
                return;
            }

            Long positionId = object.getLong("positionId");
            log.info("swap-follow-position-force-close, positionId={}", positionId);

            MemberContractPosition position = memberContractPositionService.findOne(positionId);
            BigDecimal profitOrLoss = object.getBigDecimal("profitOrLoss");
            if (profitOrLoss.compareTo(BigDecimal.ZERO) > 0)
                position.setCloseType(CloseType.FOLLOW_POSITION_PROFIT);
            else
                position.setCloseType(CloseType.FOLLOW_POSITION_LOSS);
            contractCoinMatchFactory.getContractCoinMatch(position.getCoin().getSymbol()).dealAllCloseOrder(position);
        }
    }

    @KafkaListener(topics = "follow-account-blast", containerFactory = "kafkaListenerContainerFactory")
    public void onFollowAccountBlast(List<ConsumerRecord<String, String>> records) {
        int marginRate = sysConfigService.get().getContractMarginRate();
        for (int i = 0; i < records.size(); i++) {
            ConsumerRecord<String, String> record = records.get(i);
            FollowAccount account = JSON.parseObject(record.value(), FollowAccount.class);
            if (account == null) {
                log.info("跟单账户转化失败");
                continue;
            }

            if (!redisLock.lockFollowAccount(account.getId()))
                continue;

            try {
                //刷新数据
                FollowAccount refreshedTradingAccount = followWalletService.findOneVO(account.getId());
                refreshedTradingAccount.setCurrentPriceMap(account.getCurrentPriceMap());

                //刷新数据后再次检查
                BigDecimal currentCapital = refreshedTradingAccount.getCurrentCapital();
                double currentMarginRate = currentCapital.divide(refreshedTradingAccount.getCurrentMargin(), 4, BigDecimal.ROUND_DOWN).doubleValue() * 100;
                if (currentMarginRate >= marginRate)
                    continue;

                log.info("blasted memberID={}", refreshedTradingAccount.getId());
                BigDecimal[] marketValueArray = account.getMaketValue();
                BigDecimal totalMarketValue = marketValueArray[0].add(marketValueArray[1]);
                if (totalMarketValue.compareTo(BigDecimal.ZERO) == 0) {
                    log.info("blasted memberID={}, totalMarketValue=0", refreshedTradingAccount.getId());
                    continue;
                }
                Set<MemberContractPosition> currentPositionList = refreshedTradingAccount.getCurrentPositionList();
                int positionIndex = 0;
                for (MemberContractPosition position : currentPositionList) {
                    position.setCloseType(CloseType.BLAST);
                    //position.setBlastedAmount(tradingAccount.getStaticBalance());
                    positionIndex += 1;
                    if (positionIndex == currentPositionList.size()) {
                        BigDecimal lastBalance = followWalletService.getBalanceById(refreshedTradingAccount.getId());
                        position.setBlastedAmount(BigDecimal.ZERO.subtract(lastBalance));
                    } else {
                        position.setBlastedAmount(position.getCurrentProfit().subtract(position.getCurrentMarketValue().multiply(currentCapital).divide(totalMarketValue, 8, BigDecimal.ROUND_DOWN)));
                    }
                    log.info("blasted amount={}", position.getBlastedAmount());
                    contractCoinMatchFactory.getContractCoinMatch(position.getCoin().getSymbol()).dealAllCloseOrder(position);
                    //kafkaTemplate.send("swap-order-close-all", JSON.toJSONString(position));

                }
                //BigDecimal blastedLoss = contractOrderEntrustService.sumBlastedLossByPositionIds(positionIds);
                //userPurseService.zeroBalance(refreshedTradingAccount.getId());
                sendBlastEmail(refreshedTradingAccount);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            } finally {
                redisLock.unlockFollowAccount(account.getId());
            }

        }
    }

    private void sendBlastEmail(FollowAccount account) {
        net.sf.json.JSONObject jsonObject = new net.sf.json.JSONObject();
        net.sf.json.JSONArray positionJsonArray = new net.sf.json.JSONArray();
        for (MemberContractPosition position : account.getCurrentPositionList()) {
            net.sf.json.JSONObject positionJsonObject = new net.sf.json.JSONObject();
            positionJsonObject.put("volume", position.getCurrentPosition());
            positionJsonObject.put("leverage", position.getLeverage());
            positionJsonObject.put("direction", position.getDirection().ordinal());
            positionJsonObject.put("contractName", position.getCoin().getSymbol());
            positionJsonArray.add(positionJsonObject);
        }
        jsonObject.put("positions", positionJsonArray);

        log.info("jsonObject={}", jsonObject.toString());
        memberService.notifyByEmail(account.getId(), NotifyType.FOLLOW_BLAST, jsonObject);
    }

    @KafkaListener(topics = {"follow-account-percent-change"})
    public void followAccountPercentChange(List<ConsumerRecord<String, String>> records) {
        for (int i = 0; i < records.size(); i++) {
            ConsumerRecord<String, String> record = records.get(i);
            JSONObject object = JSON.parseObject(record.value());
            log.info("follow-account-percentage-change={}", record.value());
            if (object == null) {
                log.info("仓位转化失败");
                return;
            }
            Long memberId = object.getLong("memberId");
            BigDecimal profitPercent = object.getBigDecimal("profitPercent");
            BigDecimal lossPercent = object.getBigDecimal("lossPercent");
            followWalletService.updatePercent(memberId, profitPercent, lossPercent);

            followAccountMonitor.updateAccount(memberId);
        }
    }

    @KafkaListener(topics = {"follow-account-info-change"})
    public void followAccountInfoChange(List<ConsumerRecord<String, String>> records) {
        for (int i = 0; i < records.size(); i++) {
            ConsumerRecord<String, String> record = records.get(i);
            JSONObject object = JSON.parseObject(record.value());
            log.info("follow-account-info-change={}", record.value());
            if (object == null) {
                log.info("内容转化失败");
                return;
            }
            Long memberId = object.getLong("memberId");
            String avatar = object.getString("avatar");
            String nickname = object.getString("nickname");
            memberService.updateAvatarAndNickname(memberId, avatar, nickname);

        }
    }

    @KafkaListener(topics = {"follow-account-change"})
    public void followAccountChange(String content) {
        if (StringUtils.isEmpty(content)) {
            return;
        }
        if (content.indexOf(",") > 0) {
            String[] ids = content.split(",");
            List<String> list = new ArrayList<>();
            for (String id : ids) {
                if (!list.contains(id)) {
                    followAccountMonitor.updateAccount(Long.parseLong(id));
                    list.add(id);
                }
            }
        } else {
            followAccountMonitor.updateAccount(Long.parseLong(content));
        }
    }

    /**
     * 批量跟单开仓：接收 copy-trade 发送的全量跟单员开仓委托列表，逐条直接开仓，不走匹配引擎。
     * 每条独立 try-catch，单条失败不影响批次内其他跟单员。
     */
    @KafkaListener(topics = "follow-batch-open", containerFactory = "kafkaListenerContainerFactory")
    public void batchFollowOpen(List<ConsumerRecord<String, String>> records) {
        log.info("批量开仓接收数据records：{}",records.toString());
        for (ConsumerRecord<String, String> record : records) {
            List<ContractOrderEntrust> orders = JSON.parseArray(record.value(), ContractOrderEntrust.class);
            log.info("批量开仓接收数据orders：{}",orders);
            log.info("【批量开仓】接收消息, value.length={}", orders.size());
            if (orders == null || orders.isEmpty()) continue;

            // 同批次所有 order 共享同一个 traderPositionId，加锁一次，整批处理完后统一释放
            Long traderPositionId = orders.get(0).getPositionId();
            if (traderPositionId == null) {
                log.warn("【批量开仓】traderPositionId 为空，跳过整批");
                continue;
            }
            redisLock.holdFollowPosition(traderPositionId);

            AtomicInteger successCount = new AtomicInteger(0);
            List<CompletableFuture<Void>> openFutures = new ArrayList<>();
            long batchStart = System.currentTimeMillis();
            try {
                // 同批次所有 order 共享同一个 contractId，提前查询一次避免线程内重复查库
                ContractCoin batchContractCoin = followAccountMonitor.getCoin(orders.get(0).getContractId());

                // isPercentage=1 时线程内会调 genVolumeForOrder，预先批量加载所有成员账户到 accountMap，
                // 避免每个线程各自触发 N 次 DB 查询（findOneVOFromRedis 内部无 Redis 缓存）
                List<Long> memberIds = new ArrayList<>();
                for (ContractOrderEntrust o : orders) { memberIds.add(o.getMemberId()); }
                followAccountMonitor.preloadAccounts(memberIds);

                long t2 = System.currentTimeMillis();
                for (ContractOrderEntrust order : orders) {
                    CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                        long orderStart = System.currentTimeMillis();
                        try {
                            // 字段改写：followId = 交易员 positionId，清空 positionId 留给 batchDirectOpen 填充
                            order.setFollowId(traderPositionId);
                            order.setPositionId(null);
                            order.setPositionType(ContractPositionType.INTEGRAL); // 批量跟单强制全仓

                            if (batchContractCoin == null) {
                                log.warn("【批量开仓】合约不存在, contractId={}", order.getContractId());
                                kafkaTemplate.send("swap-follow-order-open-fail", JSON.toJSONString(order));
                                return;
                            }

                            // 计算开仓手续费
                            BigDecimal feeRate = batchContractCoin.getFollowFee() != null
                                    ? batchContractCoin.getFollowFee() : batchContractCoin.getOpenFee();

                            // isPercentage=1 时 volume 由 followAccountMonitor 生成，否则直接使用 copy-trade 传入值
                            if (order.getIsPercentage() != null && order.getIsPercentage() == 1) {
                                boolean gen = followAccountMonitor.genVolumeForOrder(order, order.getMemberId(), false);
                                if (!gen || order.getVolume() == null || order.getVolume().compareTo(BigDecimal.ZERO) <= 0) {
                                    log.warn("【批量开仓】genVolume 失败, memberId={}", order.getMemberId());
                                    kafkaTemplate.send("swap-follow-order-open-fail", JSON.toJSONString(order));
                                    return;
                                }
                            } else {
                                if (order.getVolume() != null && order.getDecimalScale() != null) {
                                    BigDecimal volume = order.getVolume().setScale(order.getDecimalScale(), BigDecimal.ROUND_DOWN);
                                    if (volume.compareTo(BigDecimal.ZERO) <= 0) return;
                                    order.setVolume(volume);
                                }
                                if (order.getVolume() != null && order.getPrincipalAmount() == null) {
                                    BigDecimal orderAmount = order.getVolume().multiply(order.getTradedPrice());
                                    order.setPrincipalAmount(orderAmount.divide(BigDecimal.valueOf(order.getLeverage()), 8, BigDecimal.ROUND_DOWN));
                                }
                            }

                            BigDecimal openFee = order.getVolume().multiply(order.getTradedPrice()).multiply(feeRate);
                            order.setOpenFee(openFee);
                            order.setTradedVolume(BigDecimal.ZERO);
                            order.setContractOrderEntrustId(GeneratorUtil.getOrderId("CE"));

                            // 传入预缓存的 batchContractCoin，batchDirectOpen 内部不再重复查库
                            memberContractPositionService.batchDirectOpen(order, false, batchContractCoin);
                            successCount.incrementAndGet();
                            log.info("【批量开仓】单笔完成, memberId={}, 耗时={}ms", order.getMemberId(), System.currentTimeMillis() - orderStart);
                            // 刷新跟单账户内存缓存
                            kafkaTemplate.send("follow-account-change", order.getMemberId() + "");

                        } catch (Exception e) {
                            log.error("【批量开仓】处理异常, memberId={}, 耗时={}ms, error={}", order.getMemberId(), System.currentTimeMillis() - orderStart, e.getMessage(), e);
                            kafkaTemplate.send("swap-follow-order-open-fail", JSON.toJSONString(order));
                        }
                    }, batchExecutor);
                    openFutures.add(future);
                }
                log.info("【批量开仓】提交 {} 个异步任务耗时={}ms, traderPositionId={}", openFutures.size(), System.currentTimeMillis() - t2, traderPositionId);
                // 等待本批所有开仓线程完成后再执行 finally
                CompletableFuture.allOf(openFutures.toArray(new CompletableFuture[0])).join();
                log.info("【批量开仓】allOf.join 完成, 并行阶段耗时={}ms, 总耗时={}ms, traderPositionId={}", System.currentTimeMillis() - t2, System.currentTimeMillis() - batchStart, traderPositionId);
            } finally {
                // 记录本批次成功开仓数量，供 followTraderCloseNotify 等待数据库写入完成
                redisLock.saveFollowBatchOpenCount(traderPositionId, successCount.get());
                long totalMs = System.currentTimeMillis() - batchStart;
                log.info("【批量开仓】全批完成, traderPositionId={}, total={}, success={}, 总耗时={}ms",
                        traderPositionId, openFutures.size(), successCount.get(), totalMs);
                // 整批处理完成后统一释放锁，batchFollowClose 等待的 while 才会退出
                redisLock.releaseFollowPosition(traderPositionId);
                redisLock.unlockFollowBatchOpen(traderPositionId);
            }
        }
    }

    /**
     * 批量跟单平仓：接收 copy-trade 发送的全量跟单员平仓委托列表，逐条直接平仓，不走匹配引擎。
     * 每条独立 try-catch，单条失败不影响批次内其他跟单员。
     */
    @KafkaListener(topics = "follow-batch-close", containerFactory = "kafkaListenerContainerFactory")
    public void batchFollowClose(List<ConsumerRecord<String, String>> records) {
        for (ConsumerRecord<String, String> record : records) {
            log.info("【批量平仓】接收消息, value.length={}", record.value().length());
            List<ContractOrderEntrust> orders = JSON.parseArray(record.value(), ContractOrderEntrust.class);
            if (orders == null || orders.isEmpty()) continue;

            for (ContractOrderEntrust order : orders) {
                CompletableFuture.runAsync(() -> {
                    try {
                        // 查询跟单员持仓
                        MemberContractPosition position = memberContractPositionService.findOne(order.getPositionId());
                        if (position == null) {
                            log.warn("【批量平仓】持仓不存在, positionId={}", order.getPositionId());
                            return;
                        }
                        if (position.getStatus() == ContractPositionStatus.DONE) {
                            log.info("【批量平仓】持仓已全部平仓, positionId={}", order.getPositionId());
                            return;
                        }

                        // copy-trade 发送时可能未携带 decimalScale，从本地持仓补齐
                        if (order.getDecimalScale() == null) {
                            order.setDecimalScale(position.getDecimalScale() != null ? position.getDecimalScale() : 1);
                        }

                        // closeType 功能已移至 batchDirectClose；冻结持仓、保存委托单均在同一事务内完成
                        memberContractPositionService.batchDirectClose(order);

                    } catch (Exception e) {
                        log.error("【批量平仓】处理异常, positionId={}, error={}", order.getPositionId(), e.getMessage(), e);
                        kafkaTemplate.send("swap-follow-order-close-fail", JSON.toJSONString(order));
                    }
                }, batchExecutor);
            }
        }
    }

    /**
     * 带单员平仓通知：接收 follow-trader-close-notify，在 doge 侧查询跟单员仓位，
     * 按比例构建平仓委托，发送到 follow-batch-close 由 batchFollowClose 消费执行。
     */
    @KafkaListener(topics = "follow-trader-close-notify", containerFactory = "kafkaListenerContainerFactory")
    public void followTraderCloseNotify(List<ConsumerRecord<String, String>> records) {
        for (ConsumerRecord<String, String> record : records) {
            try {
                ContractOrderEntrust traderOrder = JSON.parseObject(record.value(), ContractOrderEntrust.class);
                if (traderOrder == null || traderOrder.getPositionId() == null) continue;

                Long traderPositionId = traderOrder.getPositionId();
                log.info("【跟单平仓通知】收到带单员平仓, traderPositionId={}", traderPositionId);

                // 等待 follow-batch-open 处理完成，最多等待 60 秒
                while (redisLock.hasFollowBatchOpen(traderPositionId)) {
                    log.info("【跟单平仓通知】batchOpen 处理中，等待 2000ms, traderPositionId={}", traderPositionId);
                    try { Thread.sleep(2000); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); break; }
                }

                // 等待批量开仓完成（CT_FOLLOW_BATCH_SIZE 与实际仓位数量对齐）
                List<MemberContractPosition> followPositions =
                        memberContractPositionService.findPositionsByFollowPositionId(traderPositionId);

                // 若 batchFollowOpen 已记录成功开仓数量，等待 DB 写入追上该数量（最多等 2 分钟直到 key 过期）
                while (redisLock.hasFollowBatchOpenCount(traderPositionId)) {
                    Integer expectedCount = redisLock.getFollowBatchOpenCount(traderPositionId);
                    if (expectedCount == null || followPositions == null || followPositions.size() >= expectedCount) {
                        break;
                    }
                    log.info("【跟单平仓通知】等待跟单仓位写入完成, 当前={}, 期望={}, traderPositionId={}",
                            followPositions == null ? 0 : followPositions.size(), expectedCount, traderPositionId);
                    try { Thread.sleep(2000); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); break; }
                    followPositions = memberContractPositionService.findPositionsByFollowPositionId(traderPositionId);
                }
                if (followPositions == null || followPositions.isEmpty()) {
                    log.info("【跟单平仓通知】无跟单员仓位, traderPositionId={}", traderPositionId);
                    continue;
                } else {
                    log.info("【跟单平仓通知】跟单员仓位, 需要通知={}", followPositions.size());
                }
                // 计算平仓比例
                MemberContractPosition traderPosition = memberContractPositionService.findOne(traderPositionId);
                BigDecimal preAvailable = traderOrder.getVolume();
                if (traderPosition != null) {
                    BigDecimal calc = traderPosition.getAvailablePosition().add(traderOrder.getVolume());
                    if (calc.compareTo(BigDecimal.ZERO) > 0) preAvailable = calc;
                }
                BigDecimal proportion = traderOrder.getVolume().divide(preAvailable, 8, BigDecimal.ROUND_DOWN);
                boolean isFullClose = proportion.compareTo(BigDecimal.ONE) >= 0;

                // 批量查询每个跟单仓位对应的开仓订单 followSystemId，避免循环内单条查库
                List<Long> followPositionIds = new ArrayList<>();
                for (MemberContractPosition fp : followPositions) { followPositionIds.add(fp.getId()); }
                Map<Long, Long> positionFollowSystemIdMap = contractOrderEntrustService.queryFollowSystemIdsByPositionIds(followPositionIds);

                List<ContractOrderEntrust> batchCloseList = new ArrayList<>();
                for (MemberContractPosition followPos : followPositions) {
                    if (followPos.getStatus() == ContractPositionStatus.DONE) continue;

                    BigDecimal positionVolume;
                    if (isFullClose) {
                        positionVolume = followPos.getCurrentPosition();
                    } else {
                        if (followPos.getAvailablePosition() == null
                                || followPos.getAvailablePosition().compareTo(BigDecimal.ZERO) <= 0) continue;
                        positionVolume = followPos.getAvailablePosition().multiply(proportion);
                    }
                    if (positionVolume == null || positionVolume.compareTo(BigDecimal.ZERO) <= 0) continue;

                    int decimalScale = followPos.getDecimalScale() != null ? followPos.getDecimalScale()
                            : (traderOrder.getDecimalScale() != null ? traderOrder.getDecimalScale() : 1);
                    positionVolume = positionVolume.setScale(decimalScale, BigDecimal.ROUND_DOWN);
                    if (positionVolume.compareTo(BigDecimal.ZERO) <= 0) continue;

                    ContractOrderEntrust closeOrder = new ContractOrderEntrust();
                    closeOrder.setPositionId(followPos.getId());
                    closeOrder.setMemberId(followPos.getMemberId());
                    closeOrder.setContractId(traderOrder.getContractId());
                    closeOrder.setSymbol(traderOrder.getSymbol());
                    closeOrder.setCoinSymbol(traderOrder.getCoinSymbol());
                    closeOrder.setBaseSymbol(traderOrder.getBaseSymbol());
                    closeOrder.setPrincipalUnit(traderOrder.getPrincipalUnit());
                    closeOrder.setDirection(followPos.getDirection());
                    closeOrder.setVolume(positionVolume);
                    closeOrder.setTradedPrice(traderOrder.getTradedPrice());
                    closeOrder.setEntrustPrice(traderOrder.getEntrustPrice());
                    closeOrder.setDealTime(traderOrder.getDealTime());
                    closeOrder.setCloseType(traderOrder.getCloseType());
                    closeOrder.setFollowId(traderPositionId);
                    closeOrder.setFollowSystemId(positionFollowSystemIdMap.get(followPos.getId()));
                    closeOrder.setDividendPercent(traderOrder.getDividendPercent());
                    closeOrder.setLeverage(followPos.getLeverage().intValue());
                    closeOrder.setPatterns(traderOrder.getPatterns());
                    closeOrder.setPositionType(followPos.getType());
                    closeOrder.setShareNumber(traderOrder.getShareNumber());
                    closeOrder.setIsPercentage(traderOrder.getIsPercentage());
                    closeOrder.setEntrustType(ContractOrderEntrustType.CLOSE);
                    closeOrder.setDecimalScale(decimalScale);
                    closeOrder.setMoneyType(followPos.getMoneyType() != null ? followPos.getMoneyType() : traderOrder.getMoneyType());
                    batchCloseList.add(closeOrder);
                }

                if (!batchCloseList.isEmpty()) {
                    kafkaTemplate.send("follow-batch-close", JSON.toJSONString(batchCloseList));
                    log.info("【跟单平仓通知】发送 follow-batch-close, traderPositionId={}, count={}",
                            traderPositionId, batchCloseList.size());
                }
            } catch (Exception e) {
                log.error("【跟单平仓通知】处理异常", e);
            }
        }
    }
}
