package com.yami.trading.service.trader.impl;
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.yami.trading.bean.model.MoneyLog;
|
import com.yami.trading.bean.model.Wallet;
|
import com.yami.trading.bean.trader.FollowCommissionType;
|
import com.yami.trading.bean.trader.domain.Trader;
|
import com.yami.trading.bean.trader.domain.TraderFollowDailyPnl;
|
import com.yami.trading.bean.trader.domain.TraderFollowUser;
|
import com.yami.trading.common.constants.Constants;
|
import com.yami.trading.common.exception.BusinessException;
|
import com.yami.trading.common.util.ApplicationUtil;
|
import com.yami.trading.common.util.Arith;
|
import com.yami.trading.common.util.StringUtils;
|
import com.yami.trading.dao.trader.TraderFollowDailyPnlMapper;
|
import com.yami.trading.service.MoneyLogService;
|
import com.yami.trading.service.WalletService;
|
import com.yami.trading.service.trader.FollowCommissionService;
|
import com.yami.trading.service.trader.TraderService;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import javax.annotation.Resource;
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.time.Instant;
|
import java.time.LocalDate;
|
import java.time.YearMonth;
|
import java.time.ZoneId;
|
import java.util.Date;
|
import java.util.List;
|
|
@Service
|
@Slf4j
|
public class FollowCommissionServiceImpl implements FollowCommissionService {
|
|
@Resource
|
private TraderFollowDailyPnlMapper traderFollowDailyPnlMapper;
|
@Resource
|
private TraderService traderService;
|
@Resource
|
private WalletService walletService;
|
@Resource
|
private MoneyLogService moneyLogService;
|
|
@Override
|
public void accumulateDailyRealizedPnl(String followerPartyId, String traderPartyId, BigDecimal realizedProfit,
|
long closeEpochSec) {
|
if (realizedProfit == null) {
|
return;
|
}
|
long ts = closeEpochSec > 0 ? closeEpochSec : Instant.now().getEpochSecond();
|
LocalDate day = Instant.ofEpochSecond(ts).atZone(ZoneId.systemDefault()).toLocalDate();
|
Date pnlDate = Date.from(day.atStartOfDay(ZoneId.systemDefault()).toInstant());
|
|
TraderFollowDailyPnl row = traderFollowDailyPnlMapper.selectOne(Wrappers.<TraderFollowDailyPnl>lambdaQuery()
|
.eq(TraderFollowDailyPnl::getPartyId, followerPartyId)
|
.eq(TraderFollowDailyPnl::getTraderPartyId, traderPartyId)
|
.eq(TraderFollowDailyPnl::getPnlDate, pnlDate));
|
if (row == null) {
|
TraderFollowDailyPnl insert = new TraderFollowDailyPnl();
|
insert.setUuid(ApplicationUtil.getCurrentTimeUUID());
|
insert.setPartyId(followerPartyId);
|
insert.setTraderPartyId(traderPartyId);
|
insert.setPnlDate(pnlDate);
|
insert.setRealizedProfitSum(realizedProfit);
|
insert.setSettled(0);
|
insert.setCommissionAmount(BigDecimal.ZERO);
|
traderFollowDailyPnlMapper.insert(insert);
|
return;
|
}
|
if (row.getSettled() != null && row.getSettled() == 1) {
|
log.warn("accumulateDailyRealizedPnl skip: row already settled follower={} trader={} day={}", followerPartyId,
|
traderPartyId, day);
|
return;
|
}
|
BigDecimal sum = row.getRealizedProfitSum() == null ? BigDecimal.ZERO : row.getRealizedProfitSum();
|
row.setRealizedProfitSum(sum.add(realizedProfit));
|
traderFollowDailyPnlMapper.updateById(row);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public int settleDailyPnlForDate(LocalDate pnlDate) {
|
Date d = Date.from(pnlDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
|
List<TraderFollowDailyPnl> list = traderFollowDailyPnlMapper.selectList(Wrappers.<TraderFollowDailyPnl>lambdaQuery()
|
.eq(TraderFollowDailyPnl::getPnlDate, d)
|
.eq(TraderFollowDailyPnl::getSettled, 0));
|
int n = 0;
|
for (TraderFollowDailyPnl row : list) {
|
if (settleOneRow(row, pnlDate)) {
|
n++;
|
}
|
}
|
return n;
|
}
|
|
private boolean settleOneRow(TraderFollowDailyPnl row, LocalDate pnlDate) {
|
TraderFollowDailyPnl fresh = traderFollowDailyPnlMapper.selectById(row.getUuid());
|
if (fresh == null || (fresh.getSettled() != null && fresh.getSettled() == 1)) {
|
return false;
|
}
|
Trader trader = traderService.findByPartyId(fresh.getTraderPartyId());
|
if (trader == null || !FollowCommissionType.isDailyProfitPct(FollowCommissionType.normalizeOrLegacy(trader.getFollowCommissionType()))) {
|
fresh.setSettled(1);
|
fresh.setCommissionAmount(BigDecimal.ZERO);
|
traderFollowDailyPnlMapper.updateById(fresh);
|
return true;
|
}
|
BigDecimal sum = fresh.getRealizedProfitSum() == null ? BigDecimal.ZERO : fresh.getRealizedProfitSum();
|
BigDecimal fee = BigDecimal.ZERO;
|
if (sum.compareTo(BigDecimal.ZERO) > 0 && trader.getFollowCommissionDailyPct() > 0) {
|
fee = sum.multiply(BigDecimal.valueOf(trader.getFollowCommissionDailyPct())).setScale(8, RoundingMode.DOWN);
|
if (fee.compareTo(BigDecimal.ZERO) > 0) {
|
String traderLabel = StringUtils.isEmptyString(trader.getName()) ? fresh.getTraderPartyId() : trader.getName().trim();
|
String sumStr = sum.stripTrailingZeros().toPlainString();
|
String feeStr = fee.stripTrailingZeros().toPlainString();
|
double pctDisp = Arith.mul(trader.getFollowCommissionDailyPct(), 100D);
|
String businessMemo = String.format(
|
"[跟单佣金-按日利润提成]账目日%s|当日已实现盈亏合计:USDT %s|带单员日提成比例:%.4f%%|本次提成:USDT %s|带单员:%s",
|
pnlDate, sumStr, pctDisp, feeStr, traderLabel);
|
if (!tryTransferFollowerToTrader(fresh.getPartyId(), fresh.getTraderPartyId(), fee, businessMemo)) {
|
return false;
|
}
|
}
|
}
|
fresh.setCommissionAmount(fee);
|
fresh.setSettled(1);
|
traderFollowDailyPnlMapper.updateById(fresh);
|
return true;
|
}
|
|
private boolean tryTransferFollowerToTrader(String followerPartyId, String traderPartyId, BigDecimal fee, String businessMemo) {
|
double feeD = fee.doubleValue();
|
Wallet walletFollower = walletService.saveWalletByPartyId(followerPartyId);
|
double beforeF = walletFollower.getMoney().doubleValue();
|
if (beforeF + 1e-12 < feeD) {
|
log.warn("tryTransferFollowerToTrader insufficient follower={} need={} has={}", followerPartyId, feeD, beforeF);
|
return false;
|
}
|
walletService.update(followerPartyId, Arith.sub(0, feeD));
|
MoneyLog logF = new MoneyLog();
|
logF.setCategory(Constants.MONEYLOG_CATEGORY_CONTRACT);
|
logF.setAmountBefore(new BigDecimal(beforeF));
|
logF.setAmount(BigDecimal.valueOf(Arith.sub(0, feeD)));
|
logF.setAmountAfter(BigDecimal.valueOf(Arith.sub(beforeF, feeD)));
|
logF.setLog(businessMemo + "|账变:跟随者主钱包扣款");
|
logF.setUserId(followerPartyId);
|
logF.setWalletType(Constants.WALLET);
|
logF.setSymbol(Constants.WALLET_USDT);
|
logF.setContentType(Constants.MONEYLOG_CONTENT_FOLLOW_UP_FEE);
|
moneyLogService.save(logF);
|
|
Wallet walletTrader = walletService.saveWalletByPartyId(traderPartyId);
|
double beforeT = walletTrader.getMoney().doubleValue();
|
walletService.update(traderPartyId, feeD);
|
MoneyLog logT = new MoneyLog();
|
logT.setCategory(Constants.MONEYLOG_CATEGORY_CONTRACT);
|
logT.setAmountBefore(new BigDecimal(beforeT));
|
logT.setAmount(BigDecimal.valueOf(feeD));
|
logT.setAmountAfter(BigDecimal.valueOf(Arith.add(beforeT, feeD)));
|
logT.setLog(businessMemo + "|账变:带单员主钱包入账");
|
logT.setUserId(traderPartyId);
|
logT.setWalletType(Constants.WALLET);
|
logT.setSymbol(Constants.WALLET_USDT);
|
logT.setContentType(Constants.MONEYLOG_CONTENT_FOLLOW_UP_FEE);
|
moneyLogService.save(logT);
|
return true;
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void applyMonthlyFeeIfNeeded(Trader trader, TraderFollowUser existing, TraderFollowUser outEntity) {
|
String type = FollowCommissionType.normalizeOrLegacy(trader.getFollowCommissionType());
|
if (!FollowCommissionType.isMonthlyFixed(type)) {
|
return;
|
}
|
BigDecimal amount = trader.getFollowCommissionMonthlyAmount();
|
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
|
throw new BusinessException(1, "该交易员月跟单费未正确配置");
|
}
|
String ym = YearMonth.now(ZoneId.systemDefault()).toString();
|
if (existing != null && ym.equals(existing.getMonthlyFeePaidPeriod())) {
|
outEntity.setMonthlyFeePaidPeriod(existing.getMonthlyFeePaidPeriod());
|
return;
|
}
|
Wallet wallet = walletService.saveWalletByPartyId(outEntity.getPartyId());
|
if (wallet.getMoney().compareTo(amount) < 0) {
|
throw new BusinessException(1, "主钱包余额不足,无法缴纳月跟单费");
|
}
|
String traderLabel = StringUtils.isEmptyString(trader.getName()) ? trader.getPartyId() : trader.getName().trim();
|
String followerLabel = StringUtils.isEmptyString(outEntity.getUsername()) ? outEntity.getPartyId()
|
: outEntity.getUsername().trim();
|
String amtStr = amount.stripTrailingZeros().toPlainString();
|
String businessMemo = String.format("[跟单佣金-月固定费]账单月%s|跟随用户:%s|带单员:%s|应付:USDT %s", ym, followerLabel,
|
traderLabel, amtStr);
|
if (!tryTransferFollowerToTrader(outEntity.getPartyId(), trader.getPartyId(), amount, businessMemo)) {
|
throw new BusinessException(1, "主钱包余额不足,无法缴纳月跟单费");
|
}
|
outEntity.setMonthlyFeePaidPeriod(ym);
|
}
|
}
|