package com.yami.trading.service.trader.impl;
|
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.yami.trading.bean.trader.domain.Trader;
|
import com.yami.trading.bean.trader.domain.TraderFollowUser;
|
import com.yami.trading.bean.trader.domain.TraderFollowUserOrder;
|
import com.yami.trading.common.constants.Constants;
|
import com.yami.trading.common.exception.BusinessException;
|
import com.yami.trading.common.util.Arith;
|
import com.yami.trading.common.util.StringUtils;
|
import com.yami.trading.dao.trader.TraderFollowUserMapper;
|
import com.yami.trading.dao.trader.TraderFollowUserOrderMapper;
|
import com.yami.trading.service.FollowWalletService;
|
import com.yami.trading.service.WalletService;
|
import com.yami.trading.service.trader.FollowCommissionService;
|
import com.yami.trading.service.trader.TraderFollowUserOrderService;
|
import com.yami.trading.service.trader.TraderFollowUserService;
|
import com.yami.trading.service.trader.TraderService;
|
import com.yami.trading.service.trader.TraderUserService;
|
import org.springframework.context.annotation.Lazy;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import javax.annotation.Resource;
|
import java.math.BigDecimal;
|
import java.time.Instant;
|
import java.math.RoundingMode;
|
import java.text.DecimalFormat;
|
import java.text.SimpleDateFormat;
|
import java.util.*;
|
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.Executors;
|
|
@Service
|
public class TraderFollowUserServiceImpl implements TraderFollowUserService {
|
private static final ExecutorService STOP_FOLLOW_EXECUTOR = Executors.newFixedThreadPool(4);
|
|
private final Set<String> stoppingTasks = ConcurrentHashMap.newKeySet();
|
|
@Resource
|
private TraderService traderService;
|
@Resource
|
private TraderUserService traderUserService;
|
|
@Resource
|
private TraderFollowUserMapper traderFollowUserMapper;
|
|
@Resource
|
private TraderFollowUserOrderMapper traderFollowUserOrderMapper;
|
@Resource
|
private FollowWalletService followWalletService;
|
@Resource
|
private WalletService walletService;
|
|
@Resource
|
private FollowCommissionService followCommissionService;
|
|
@Lazy
|
@Resource
|
private TraderFollowUserOrderService traderFollowUserOrderService;
|
|
public List<Map<String, Object>> getPaged(Page pageparam, String partyId, String profit) {
|
Page<TraderFollowUser> page = traderFollowUserMapper.selectPage(pageparam,
|
Wrappers.<TraderFollowUser>lambdaQuery()
|
.eq(TraderFollowUser::getTraderPartyId, partyId)
|
.orderByDesc(TraderFollowUser::getCreateTime)
|
.orderByDesc(TraderFollowUser::getUuid));
|
List<Map<String, Object>> data = this.bulidData(page.getRecords());
|
return data;
|
}
|
|
private List<Map<String, Object>> bulidData(List<TraderFollowUser> traderFollowUsers) {
|
List<Map<String, Object>> result_traders = new ArrayList();
|
DecimalFormat df2 = new DecimalFormat("#.##");
|
df2.setRoundingMode(RoundingMode.FLOOR);// 向下取整
|
SimpleDateFormat tsFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
if (traderFollowUsers == null) {
|
return result_traders;
|
}
|
for (int i = 0; i < traderFollowUsers.size(); i++) {
|
Map<String, Object> map = new HashMap<String, Object>();
|
TraderFollowUser entity = traderFollowUsers.get(i);
|
map.put("id", entity.getUuid());
|
map.put("name", entity.getUsername());
|
map.put("profit", df2.format(entity.getProfit()));
|
map.put("amount_sum", df2.format(entity.getAmountSum()));
|
map.put("followState", entity.getState());
|
map.put("symbol", entity.getSymbol());
|
map.put("volume", df2.format(entity.getVolume()));
|
map.put("volumeMax", df2.format(entity.getVolumeMax()));
|
map.put("lever_rate", entity.getLeverRate() > 0 ? df2.format(entity.getLeverRate()) : "");
|
map.put("followFailReason", entity.getFailReason() != null ? entity.getFailReason() : "");
|
if (entity.getCreateTime() != null) {
|
map.put("follow_start_time", tsFmt.format(entity.getCreateTime()));
|
} else {
|
map.put("follow_start_time", "");
|
}
|
map.put("follow_stop_time", formatEpochSecond(entity.getStopFinishTime(), tsFmt));
|
map.put("follow_fail_time", formatEpochSecond(entity.getLastFailTime(), tsFmt));
|
|
result_traders.add(map);
|
}
|
|
return result_traders;
|
|
}
|
|
private static String formatEpochSecond(Long sec, SimpleDateFormat tsFmt) {
|
if (sec == null || sec <= 0L) {
|
return "";
|
}
|
return tsFmt.format(new Date(sec * 1000L));
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void save(TraderFollowUser entity, String trader_id) {
|
validateFollowConfig(entity);
|
Trader trader = this.traderService.findById(trader_id);
|
if (trader == null) {
|
throw new BusinessException(1, "交易员不存在");
|
}
|
if ("0".equals(trader.getState())) {
|
throw new BusinessException(1, "交易员未开启带单");
|
}
|
if (trader.getChecked() != 1) {
|
throw new BusinessException(1, "交易员审核未通过");
|
}
|
if (entity.getPartyId().equals(trader.getPartyId())) {
|
throw new BusinessException(1, "交易员不能跟随自己");
|
}
|
validateFollowSymbol(entity.getSymbol(), trader.getSymbols());
|
Trader trader_user = this.traderService.findByPartyId(entity.getPartyId());
|
if (trader_user != null && trader_user.getChecked() == 1) {
|
throw new BusinessException(1, "交易员无法跟随另一个交易员");
|
}
|
BigDecimal followMin = trader.getFollowVolumnMin();
|
if (followMin != null && followMin.compareTo(BigDecimal.ZERO) > 0) {
|
double minVol = followMin.doubleValue();
|
if (entity.getVolume() < minVol || entity.getVolumeMax() < minVol) {
|
throw new BusinessException(1, "跟单币数量不能低于交易员设置的最小值");
|
}
|
}
|
|
TraderFollowUser latest = findByPartyIdAndTrader_partyId(entity.getPartyId(), trader.getPartyId());
|
if (latest != null
|
&& (TraderFollowUser.STATE_FOLLOWING.equals(latest.getState())
|
|| TraderFollowUser.STATE_STOPPING.equals(latest.getState()))) {
|
throw new BusinessException(1, "用户已跟随交易员");
|
}
|
try {
|
followCommissionService.applyMonthlyFeeIfNeeded(trader, latest, entity);
|
} catch (BusinessException e) {
|
if (isInsufficientBalanceError(e)) {
|
recordFollowFailed(entity, trader, e.getMessage(), latest);
|
}
|
throw e;
|
}
|
|
entity.setTraderPartyId(trader.getPartyId());
|
entity.setCreateTime(new Date());
|
entity.setFollowType(TraderFollowUser.FOLLOW_TYPE_FIXED);
|
entity.setState(TraderFollowUser.STATE_FOLLOWING);
|
entity.setInvestAmount(BigDecimal.valueOf(entity.getVolume()));
|
entity.setStopRequestTime(null);
|
entity.setStopFinishTime(null);
|
entity.setFailReason(null);
|
entity.setLastFailTime(null);
|
|
long priorSessions = traderFollowUserMapper.selectCount(Wrappers.<TraderFollowUser>lambdaQuery()
|
.eq(TraderFollowUser::getTraderPartyId, trader.getPartyId())
|
.eq(TraderFollowUser::getPartyId, entity.getPartyId())
|
.in(TraderFollowUser::getState, TraderFollowUser.STATE_FOLLOWING, TraderFollowUser.STATE_STOPPING,
|
TraderFollowUser.STATE_STOPPED));
|
if (priorSessions == 0L) {
|
trader.setFollowerSum((int) Arith.add(trader.getFollowerSum(), 1));
|
}
|
trader.setFollowerNow((int) Arith.add(trader.getFollowerNow(), 1));
|
traderService.update(trader);
|
/**
|
* 创建累计用户跟随累计表
|
*/
|
traderUserService.saveTraderUserByPartyId(entity.getPartyId());
|
|
if (latest != null) {
|
entity.setProfit(latest.getProfit());
|
entity.setAmountSum(latest.getAmountSum());
|
if (entity.getMonthlyFeePaidPeriod() == null) {
|
entity.setMonthlyFeePaidPeriod(latest.getMonthlyFeePaidPeriod());
|
}
|
}
|
entity.setUuid(null);
|
traderFollowUserMapper.insert(entity);
|
/**
|
* 纠正历史脏数据:手动停止跟单时跟单方 saveClose 未同步 T_TRADER_FOLLOW_USER_ORDER 状态,
|
* 再次跟单前应把「合约已平仓仍为 submitted」的映射置为 created,否则会占满 volumeMax。
|
*/
|
traderFollowUserOrderService.reconcileStaleSubmittedMappings(entity.getPartyId(), entity.getTraderPartyId());
|
|
}
|
|
@Override
|
public void save(TraderFollowUser entity) {
|
if (entity.getFollowType() == null) {
|
entity.setFollowType(TraderFollowUser.FOLLOW_TYPE_FIXED);
|
}
|
if (entity.getState() == null) {
|
entity.setState(TraderFollowUser.STATE_FOLLOWING);
|
}
|
traderFollowUserMapper.insert(entity);
|
}
|
|
@Override
|
public void update(TraderFollowUser entity) {
|
validateFollowConfig(entity);
|
entity.setFollowType(TraderFollowUser.FOLLOW_TYPE_FIXED);
|
entity.setInvestAmount(BigDecimal.valueOf(entity.getVolume()));
|
TraderFollowUser old = traderFollowUserMapper.selectById(entity.getUuid());
|
if (old != null) {
|
Trader trader = this.traderService.findByPartyId(old.getTraderPartyId());
|
if (trader != null) {
|
validateFollowSymbol(entity.getSymbol(), trader.getSymbols());
|
}
|
}
|
|
// ApplicationUtil.executeUpdate(entity);
|
traderFollowUserMapper.updateById(entity);
|
}
|
|
@Override
|
public void deleteCancel(String id) {
|
TraderFollowUser entity = findById(id);
|
if (entity == null) {
|
return;
|
}
|
/**
|
* 将旧的交易员跟随用户-1
|
*/
|
Trader trader_before = this.traderService.findByPartyId(entity.getTraderPartyId().toString());
|
trader_before.setFollowerNow((int) Arith.sub(trader_before.getFollowerNow(), 1));
|
this.traderService.update(trader_before);
|
|
if (entity != null) {
|
// ApplicationUtil.executeDelete(entity);
|
traderFollowUserMapper.deleteById(entity.getUuid());
|
}
|
|
}
|
|
@Override
|
public void cancelFollowAsync(String id, com.yami.trading.service.contract.ContractOrderService contractOrderService) {
|
TraderFollowUser entity = findById(id);
|
if (entity == null || TraderFollowUser.STATE_STOPPED.equals(entity.getState())) {
|
return;
|
}
|
if (TraderFollowUser.STATE_STOPPING.equals(entity.getState()) || !stoppingTasks.add(entity.getUuid())) {
|
return;
|
}
|
entity.setState(TraderFollowUser.STATE_STOPPING);
|
entity.setStopRequestTime(Instant.now().getEpochSecond());
|
traderFollowUserMapper.updateById(entity);
|
STOP_FOLLOW_EXECUTOR.submit(() -> {
|
try {
|
List<TraderFollowUserOrder> openOrders = traderFollowUserOrderMapper.selectList(
|
Wrappers.<TraderFollowUserOrder>lambdaQuery()
|
.eq(TraderFollowUserOrder::getPartyId, entity.getPartyId())
|
.eq(TraderFollowUserOrder::getTraderPartyId, entity.getTraderPartyId())
|
.eq(TraderFollowUserOrder::getState, TraderFollowUserOrder.STATE_SUBMITTED));
|
if (openOrders != null) {
|
for (TraderFollowUserOrder openOrder : openOrders) {
|
contractOrderService.saveClose(entity.getPartyId(), openOrder.getUserOrderNo());
|
}
|
}
|
TraderFollowUser latest = findById(id);
|
if (latest != null) {
|
latest.setState(TraderFollowUser.STATE_STOPPED);
|
latest.setStopFinishTime(Instant.now().getEpochSecond());
|
traderFollowUserMapper.updateById(latest);
|
refundFollowWalletToMainWallet(latest.getPartyId());
|
Trader trader = this.traderService.findByPartyId(latest.getTraderPartyId());
|
if (trader != null && trader.getFollowerNow() > 0) {
|
trader.setFollowerNow((int) Arith.sub(trader.getFollowerNow(), 1));
|
this.traderService.update(trader);
|
}
|
}
|
} finally {
|
stoppingTasks.remove(entity.getUuid());
|
}
|
});
|
}
|
|
public List<TraderFollowUser> findByStateAndPartyId(String partyId, String trader_partyId, String state) {
|
List<TraderFollowUser> list = traderFollowUserMapper.selectList(Wrappers.<TraderFollowUser>lambdaQuery().eq(TraderFollowUser::getPartyId, partyId).eq(TraderFollowUser::getTraderPartyId, trader_partyId).eq(TraderFollowUser::getState, state));
|
// List<TraderFollowUser> list = ApplicationUtil.executeSelect(TraderFollowUser.class,
|
// " WHERE PARTY_ID = ? AND TRADER_PARTY_ID = ? AND STATE = ? ",
|
// new Object[] { partyId, trader_partyId, state });
|
if (list.size() > 0)
|
return list;
|
return null;
|
}
|
|
public List<TraderFollowUser> findByTrader_partyId(String trader_partyId) {
|
List<TraderFollowUser> list = traderFollowUserMapper.selectList(Wrappers.<TraderFollowUser>lambdaQuery().eq(TraderFollowUser::getTraderPartyId, trader_partyId));
|
// List<TraderFollowUser> list = ApplicationUtil.executeSelect(TraderFollowUser.class, " WHERE TRADER_PARTY_ID = ? ",
|
// new Object[] { trader_partyId });
|
if (list.size() > 0)
|
return list;
|
return null;
|
}
|
|
@Override
|
public List<TraderFollowUser> findActiveByTraderPartyId(String trader_partyId) {
|
List<TraderFollowUser> list = traderFollowUserMapper.selectList(Wrappers.<TraderFollowUser>lambdaQuery()
|
.eq(TraderFollowUser::getTraderPartyId, trader_partyId)
|
.eq(TraderFollowUser::getState, TraderFollowUser.STATE_FOLLOWING));
|
if (list.size() > 0) {
|
return list;
|
}
|
return null;
|
}
|
|
public List<TraderFollowUser> findByPartyId(String partyId) {
|
List<TraderFollowUser> list = traderFollowUserMapper.selectList(Wrappers.<TraderFollowUser>lambdaQuery().eq(TraderFollowUser::getPartyId, partyId));
|
// List<TraderFollowUser> list = ApplicationUtil.executeSelect(TraderFollowUser.class, " WHERE PARTY_ID = ? ",
|
// new Object[] { partyId });
|
if (list.size() > 0)
|
return list;
|
return null;
|
}
|
|
@Override
|
public long countByPartyId(String partyId) {
|
if (StringUtils.isNullOrEmpty(partyId)) {
|
return 0L;
|
}
|
Long c = traderFollowUserMapper.selectCount(Wrappers.<TraderFollowUser>lambdaQuery()
|
.eq(TraderFollowUser::getPartyId, partyId));
|
return c == null ? 0L : c.longValue();
|
}
|
|
@Override
|
public IPage<TraderFollowUser> pageByPartyId(Page<TraderFollowUser> page, String partyId) {
|
if (page == null || StringUtils.isNullOrEmpty(partyId)) {
|
return page;
|
}
|
return traderFollowUserMapper.selectPage(page, Wrappers.<TraderFollowUser>lambdaQuery()
|
.eq(TraderFollowUser::getPartyId, partyId)
|
.orderByDesc(TraderFollowUser::getUpdateTime)
|
.orderByDesc(TraderFollowUser::getCreateTime));
|
}
|
|
public TraderFollowUser findByPartyIdAndTrader_partyId(String partyId, String trader_partyId) {
|
List<TraderFollowUser> list = traderFollowUserMapper.selectList(Wrappers.<TraderFollowUser>lambdaQuery()
|
.eq(TraderFollowUser::getPartyId, partyId)
|
.eq(TraderFollowUser::getTraderPartyId, trader_partyId)
|
.orderByDesc(TraderFollowUser::getCreateTime)
|
.orderByDesc(TraderFollowUser::getUuid));
|
// List<TraderFollowUser> list = ApplicationUtil.executeSelect(TraderFollowUser.class,
|
// " WHERE PARTY_ID= ? and TRADER_PARTY_ID = ? ",
|
// new Object[] { partyId, trader_partyId });
|
if (list.size() > 0)
|
return list.get(0);
|
return null;
|
}
|
|
private boolean isInsufficientBalanceError(BusinessException e) {
|
if (e == null || e.getMessage() == null) {
|
return false;
|
}
|
return e.getMessage().contains("余额不足");
|
}
|
|
private void recordFollowFailed(TraderFollowUser entity, Trader trader, String reason, TraderFollowUser latest) {
|
TraderFollowUser failed = new TraderFollowUser();
|
failed.setPartyId(entity.getPartyId());
|
failed.setUsername(entity.getUsername());
|
failed.setTraderPartyId(trader.getPartyId());
|
failed.setSymbol(entity.getSymbol());
|
failed.setFollowType(TraderFollowUser.FOLLOW_TYPE_FIXED);
|
failed.setVolume(entity.getVolume());
|
failed.setVolumeMax(entity.getVolumeMax());
|
failed.setInvestAmount(BigDecimal.valueOf(entity.getVolume()));
|
failed.setStopLoss(0D);
|
failed.setStopProfit(0D);
|
failed.setState(TraderFollowUser.STATE_FAILED);
|
failed.setFailReason(reason);
|
failed.setLastFailTime(Instant.now().getEpochSecond());
|
failed.setCreateTime(new Date());
|
if (latest != null && TraderFollowUser.STATE_FAILED.equals(latest.getState())) {
|
failed.setUuid(latest.getUuid());
|
failed.setProfit(latest.getProfit());
|
failed.setAmountSum(latest.getAmountSum());
|
failed.setMonthlyFeePaidPeriod(latest.getMonthlyFeePaidPeriod());
|
traderFollowUserMapper.updateById(failed);
|
return;
|
}
|
traderFollowUserMapper.insert(failed);
|
}
|
|
public TraderFollowUser findById(String id) {
|
// return ApplicationUtil.executeGet(id, TraderFollowUser.class);
|
TraderFollowUser traderFollowUser = traderFollowUserMapper.selectById(id);
|
return traderFollowUser;
|
}
|
|
private void validateFollowConfig(TraderFollowUser entity) {
|
entity.setFollowType(TraderFollowUser.FOLLOW_TYPE_FIXED);
|
if (entity.getVolume() <= 0 || entity.getVolumeMax() <= 0) {
|
throw new BusinessException(1, "跟单参数输入错误");
|
}
|
if (entity.getVolumeMax() < entity.getVolume()) {
|
throw new BusinessException(1, "最大跟单币数量不能小于最小跟单币数量");
|
}
|
if (entity.getStopLoss() < 0 || entity.getStopProfit() < 0) {
|
throw new BusinessException(1, "止盈止损参数输入错误");
|
}
|
if (entity.getLeverRate() <= 0) {
|
throw new BusinessException(1, "杠杆倍数必须大于0");
|
}
|
}
|
|
private void validateFollowSymbol(String followSymbol, String traderSymbolsRaw) {
|
if (followSymbol == null || followSymbol.trim().isEmpty()) {
|
throw new BusinessException(1, "请选择跟单币种");
|
}
|
String follow = followSymbol.trim();
|
String raw = traderSymbolsRaw == null ? "" : traderSymbolsRaw.trim();
|
if (raw.isEmpty()) {
|
throw new BusinessException(1, "交易员未配置带单币种");
|
}
|
String[] arr = raw.split("[;;,,]+");
|
for (String one : arr) {
|
if (follow.equalsIgnoreCase(one == null ? "" : one.trim())) {
|
return;
|
}
|
}
|
throw new BusinessException(1, "只能选择交易员带单币种中的一种进行跟单");
|
}
|
|
private void refundFollowWalletToMainWallet(String partyId) {
|
if (partyId == null || partyId.trim().isEmpty()) {
|
return;
|
}
|
com.yami.trading.bean.model.FollowWallet followWallet = followWalletService.saveWalletByPartyId(partyId);
|
if (followWallet == null || followWallet.getMoney() == null || followWallet.getMoney().compareTo(BigDecimal.ZERO) <= 0) {
|
return;
|
}
|
BigDecimal refund = followWallet.getMoney();
|
walletService.updateMoney("USDT", partyId, refund, BigDecimal.ZERO,
|
Constants.MONEYLOG_CATEGORY_CONTRACT, Constants.WALLET_USDT, Constants.MONEYLOG_CONTENT_CONTRACT_CLOSE,
|
"停止跟单返还独立跟单账户资金");
|
followWalletService.updateMoney("USDT", partyId, refund.negate(), BigDecimal.ZERO,
|
Constants.MONEYLOG_CATEGORY_CONTRACT, Constants.WALLET_USDT, Constants.MONEYLOG_CONTENT_CONTRACT_CLOSE,
|
"停止跟单划转资金到主钱包");
|
}
|
|
@Override
|
public void markFollowOpenFailed(String partyId, String traderPartyId, String reason) {
|
if (partyId == null || partyId.trim().isEmpty() || traderPartyId == null || traderPartyId.trim().isEmpty()) {
|
return;
|
}
|
List<TraderFollowUser> list = traderFollowUserMapper.selectList(Wrappers.<TraderFollowUser>lambdaQuery()
|
.eq(TraderFollowUser::getPartyId, partyId)
|
.eq(TraderFollowUser::getTraderPartyId, traderPartyId)
|
.eq(TraderFollowUser::getState, TraderFollowUser.STATE_FOLLOWING)
|
.orderByDesc(TraderFollowUser::getCreateTime)
|
.last("LIMIT 1"));
|
if (list == null || list.isEmpty()) {
|
return;
|
}
|
TraderFollowUser u = list.get(0);
|
String msg = reason == null ? "" : reason.trim();
|
if (msg.length() > 900) {
|
msg = msg.substring(0, 900) + "…";
|
}
|
long nowSec = Instant.now().getEpochSecond();
|
u.setState(TraderFollowUser.STATE_FAILED);
|
u.setFailReason(msg);
|
u.setLastFailTime(nowSec);
|
u.setStopFinishTime(nowSec);
|
traderFollowUserMapper.updateById(u);
|
Trader trader = this.traderService.findByPartyId(traderPartyId);
|
if (trader != null && trader.getFollowerNow() > 0) {
|
trader.setFollowerNow((int) Arith.sub(trader.getFollowerNow(), 1));
|
this.traderService.update(trader);
|
}
|
}
|
|
}
|