| | |
| | | package com.yami.trading.service.contract; |
| | | |
| | | import cn.hutool.core.bean.BeanUtil; |
| | | import cn.hutool.core.collection.CollectionUtil; |
| | | import cn.hutool.core.convert.Convert; |
| | | import cn.hutool.core.date.DateUtil; |
| | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.yami.trading.bean.contract.domain.ContractApplyOrder; |
| | | import com.yami.trading.bean.contract.domain.ContractOrderProfit; |
| | | import com.yami.trading.bean.syspara.domain.Syspara; |
| | | import com.yami.trading.bean.contract.dto.ContractApplyOrderDTO; |
| | | import com.yami.trading.bean.contract.dto.ContractOrderDTO; |
| | | import com.yami.trading.bean.contract.query.ContractApplyOrderQuery; |
| | |
| | | import com.yami.trading.service.user.UserService; |
| | | import com.yami.trading.service.WalletService; |
| | | import com.yami.trading.service.item.ItemService; |
| | | import com.yami.trading.service.syspara.SysparaService; |
| | | import com.yami.trading.util.ConverterUtil; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.collections.CollectionUtils; |
| | | import org.apache.commons.lang3.ObjectUtils; |
| | | import org.jetbrains.annotations.NotNull; |
| | |
| | | import java.math.RoundingMode; |
| | | import java.text.DecimalFormat; |
| | | import java.text.SimpleDateFormat; |
| | | import java.time.Instant; |
| | | import java.time.ZoneId; |
| | | import java.time.ZonedDateTime; |
| | | import java.time.temporal.ChronoUnit; |
| | | import java.util.*; |
| | | import java.util.concurrent.*; |
| | | import java.util.stream.Collectors; |
| | |
| | | * @author lucas |
| | | * @version 2023-03-29 |
| | | */ |
| | | @Slf4j |
| | | @Service |
| | | @Transactional |
| | | public class ContractOrderService extends ServiceImpl<ContractOrderMapper, ContractOrder> { |
| | | private static final int FUNDING_SETTLEMENT_INTERVAL_MINUTES = 240; |
| | | private final ConcurrentMap<String, ContractOrder> map = new ConcurrentHashMap<>(); |
| | | private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); |
| | | |
| | |
| | | private ContractApplyOrderService contractApplyOrderService; |
| | | |
| | | @Autowired |
| | | @Lazy |
| | | private ContractOrderCalculationService contractOrderCalculationService; |
| | | |
| | | @Autowired |
| | | private StrongLevelCalculationService strongLevelCalculationService; |
| | | |
| | | @Autowired |
| | | MoneyLogService moneyLogService; |
| | | |
| | | @Autowired |
| | | private SysparaService sysparaService; |
| | | |
| | | public IPage<ContractOrderDTO> listRecord(Page page, ContractOrderQuery query) { |
| | | return baseMapper.listRecord(page, query); |
| | |
| | | return null; |
| | | } |
| | | |
| | | contractOrderCalculationService.refreshMarkPriceProfit(order); |
| | | |
| | | /** |
| | | * 收益 |
| | | */ |
| | |
| | | */ |
| | | @Transactional(propagation = Propagation.NOT_SUPPORTED) |
| | | public void updateByIdBuffer(ContractOrder entity) { |
| | | updateProfit(entity); |
| | | map.put(entity.getUuid(), entity); |
| | | } |
| | | |
| | | public void wrapProfit(ContractOrder contractOrder) { |
| | | if (ContractOrder.STATE_SUBMITTED.equalsIgnoreCase(contractOrder.getState())) { |
| | | ContractOrderProfit cacheProfit = getCacheProfit(contractOrder.getUuid()); |
| | | if (cacheProfit != null) { |
| | | contractOrder.setProfit(cacheProfit.getProfit()); |
| | | contractOrder.setCloseAvgPrice(cacheProfit.getCloseAvgPrice()); |
| | | contractOrder.setForceClosePrice(cacheProfit.getForceClosePrice()); |
| | | } else { |
| | | contractOrder.setProfit(BigDecimal.ZERO); |
| | | } |
| | | } |
| | | } |
| | | |
| | | public ContractOrderProfit getCacheProfit(String uuid) { |
| | | return RedisUtil.get(ContractRedisKeys.CONTRACT_PROFIT_V1 + uuid); |
| | | } |
| | | |
| | | public void updateProfit(ContractOrder order) { |
| | | if (ContractOrder.STATE_SUBMITTED.equals(order.getState())) { |
| | | RedisUtil.set(ContractRedisKeys.CONTRACT_PROFIT_V1 + order.getUuid(), BeanUtil.copyProperties(order, ContractOrderProfit.class)); |
| | | Map<String, ContractOrder> submittedMap = |
| | | RedisUtil.get(ContractRedisKeys.CONTRACT_SUBMITTED_ORDER_PARTY_ID + order.getPartyId()); |
| | | if (null == submittedMap) { |
| | | submittedMap = new ConcurrentHashMap<>(); |
| | | } |
| | | |
| | | ContractOrder orderOld = submittedMap.get(order.getOrderNo()); |
| | | if (orderOld == null) { |
| | | return; |
| | | } |
| | | orderOld.setCloseAvgPrice(order.getCloseAvgPrice()); |
| | | orderOld.setProfit(order.getProfit()); |
| | | orderOld.setForceClosePrice(order.getForceClosePrice()); |
| | | submittedMap.put(order.getOrderNo(), orderOld); |
| | | |
| | | RedisUtil.set(ContractRedisKeys.CONTRACT_SUBMITTED_ORDER_PARTY_ID + order.getPartyId(), submittedMap); |
| | | |
| | | Map<String, BigDecimal> contractAssetsOrder = this.walletService.getMoneyContractByOrder(order); |
| | | Map<String, BigDecimal> contractAssetsOrderOld = this.walletService.getMoneyContractByOrder(orderOld); |
| | | |
| | | BigDecimal contractAssets = RedisUtil.get(ContractRedisKeys.CONTRACT_ASSETS_PARTY_ID + order.getPartyId().toString()); |
| | | if (contractAssets == null) { |
| | | contractAssets = BigDecimal.ZERO; |
| | | } |
| | | BigDecimal contractAssetsDeposit = RedisUtil.get(ContractRedisKeys.CONTRACT_ASSETS_DEPOSIT_PARTY_ID + order.getPartyId().toString()); |
| | | if (contractAssetsDeposit == null) { |
| | | contractAssetsDeposit = BigDecimal.ZERO; |
| | | } |
| | | BigDecimal contractAssetsProfit = RedisUtil.get(ContractRedisKeys.CONTRACT_ASSETS_PROFIT_PARTY_ID + order.getPartyId().toString()); |
| | | if (contractAssetsProfit == null) { |
| | | contractAssetsProfit = BigDecimal.ZERO; |
| | | } |
| | | |
| | | RedisUtil.set(ContractRedisKeys.CONTRACT_ASSETS_PARTY_ID + order.getPartyId().toString(), |
| | | contractAssets.add(contractAssetsOrder.get("money_contract")).subtract(contractAssetsOrderOld.get("money_contract"))); |
| | | RedisUtil.set(ContractRedisKeys.CONTRACT_ASSETS_DEPOSIT_PARTY_ID + order.getPartyId().toString(), |
| | | contractAssetsDeposit.add(contractAssetsOrder.get("money_contract_deposit")).subtract(contractAssetsOrderOld.get("money_contract_deposit"))); |
| | | RedisUtil.set(ContractRedisKeys.CONTRACT_ASSETS_PROFIT_PARTY_ID + order.getPartyId().toString(), |
| | | contractAssetsProfit.add(contractAssetsOrder.get("money_contract_profit")).subtract(contractAssetsOrderOld.get("money_contract_profit"))); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 累计资金费带来的盈亏(正数表示用户获利):多仓为 -合约价值×费率×次数,空仓为 +合约价值×费率×次数。 |
| | | */ |
| | | public BigDecimal calculateAccruedFundingPnl(ContractOrder order, BigDecimal markPrice, Date asOf) { |
| | | if (order == null || markPrice == null || asOf == null || order.getCreateTime() == null) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | BigDecimal vol = defaultZero(order.getVolume()); |
| | | if (vol.compareTo(BigDecimal.ZERO) <= 0 || markPrice.compareTo(BigDecimal.ZERO) <= 0) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | long periods = countFundingSettlementPoints( |
| | | order.getCreateTime().toInstant(), |
| | | asOf.toInstant(), |
| | | ZoneId.systemDefault(), |
| | | FUNDING_SETTLEMENT_INTERVAL_MINUTES); |
| | | if (periods <= 0) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | Item item = itemService.findBySymbol(order.getSymbol()); |
| | | BigDecimal faceValue = item != null && item.getFaceValue() > 0 |
| | | ? BigDecimal.valueOf(item.getFaceValue()) : new BigDecimal("0.01"); |
| | | BigDecimal notional = markPrice.multiply(vol).multiply(faceValue); |
| | | BigDecimal rate = getContractFundingRate(); |
| | | BigDecimal signed = ContractOrder.DIRECTION_BUY.equalsIgnoreCase(order.getDirection()) |
| | | ? rate.negate() : rate; |
| | | return notional.multiply(signed).multiply(BigDecimal.valueOf(periods)).setScale(8, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | public BigDecimal getContractFundingRate() { |
| | | try { |
| | | Syspara p = sysparaService.find("funding_fee"); |
| | | if (p == null || StringUtils.isEmptyString(p.getSvalue())) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | return new BigDecimal(p.getSvalue().trim()); |
| | | } catch (Exception e) { |
| | | log.warn("parse funding_fee syspara failed", e); |
| | | return BigDecimal.ZERO; |
| | | } |
| | | } |
| | | |
| | | private static ZonedDateTime nextFundingSettlementStrictlyAfter(ZonedDateTime t, int intervalMinutes) { |
| | | int step = intervalMinutes <= 0 ? 240 : intervalMinutes; |
| | | ZonedDateTime tTrunc = t.withSecond(0).withNano(0); |
| | | ZonedDateTime dayStart = tTrunc.toLocalDate().atStartOfDay(tTrunc.getZone()); |
| | | long minutesFromDayStart = ChronoUnit.MINUTES.between(dayStart, tTrunc); |
| | | long slotIndex = minutesFromDayStart / step; |
| | | ZonedDateTime slotStart = dayStart.plusMinutes(slotIndex * step); |
| | | ZonedDateTime next = slotStart; |
| | | while (!next.isAfter(t)) { |
| | | next = next.plusMinutes(step); |
| | | } |
| | | return next; |
| | | } |
| | | |
| | | private static long countFundingSettlementPoints(Instant open, Instant end, ZoneId zone, int intervalMinutes) { |
| | | if (open == null || end == null || !end.isAfter(open)) { |
| | | return 0; |
| | | } |
| | | int step = intervalMinutes <= 0 ? 240 : intervalMinutes; |
| | | ZonedDateTime zEnd = end.atZone(zone); |
| | | ZonedDateTime cursor = nextFundingSettlementStrictlyAfter(open.atZone(zone), step); |
| | | long cnt = 0; |
| | | while (!cursor.isAfter(zEnd)) { |
| | | cnt++; |
| | | cursor = cursor.plusMinutes(step); |
| | | } |
| | | return cnt; |
| | | } |
| | | |
| | | private BigDecimal defaultZero(BigDecimal value) { |
| | | return value == null ? BigDecimal.ZERO : value; |
| | | } |
| | | |
| | | public void update(ContractOrder order) { |
| | |
| | | |
| | | |
| | | /** |
| | | * 价差盈亏(与持仓定时计算一致):(平仓价 - 开仓价) × 平仓张数 × 面值,空仓取反 |
| | | */ |
| | | private BigDecimal calculatePricePnl(ContractOrder order, BigDecimal closePrice, BigDecimal sheets) { |
| | | BigDecimal tradeAvgPrice = order.getTradeAvgPrice() == null ? BigDecimal.ZERO : order.getTradeAvgPrice(); |
| | | if (tradeAvgPrice.compareTo(BigDecimal.ZERO) <= 0 || sheets == null || sheets.compareTo(BigDecimal.ZERO) <= 0) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | Item item = itemService.findBySymbol(order.getSymbol()); |
| | | BigDecimal faceValue = item != null && item.getFaceValue() > 0 |
| | | ? BigDecimal.valueOf(item.getFaceValue()) : new BigDecimal("0.01"); |
| | | BigDecimal point = closePrice.subtract(tradeAvgPrice); |
| | | BigDecimal pnl = point.multiply(faceValue).multiply(sheets).setScale(4, RoundingMode.DOWN); |
| | | if (!ContractOrder.DIRECTION_BUY.equals(order.getDirection())) { |
| | | pnl = pnl.negate(); |
| | | } |
| | | return pnl; |
| | | } |
| | | |
| | | /** |
| | | * 收益结算,平仓时计算 |
| | | * |
| | | * @param closevolume 平仓的张数 |
| | | * @param volume 平仓的张数 |
| | | */ |
| | | public BigDecimal settle(ContractOrder order, BigDecimal volume) { |
| | | /** |
| | | * 偏差点位 |
| | | */ |
| | | List<Realtime> list = this.dataService.realtime(order.getSymbol()); |
| | | if (list.size() == 0) { |
| | | order.getProfit(); |
| | | ContractOrderProfit cacheProfit = getCacheProfit(order.getUuid()); |
| | | BigDecimal currentVolume = order.getVolume() == null ? BigDecimal.ZERO : order.getVolume(); |
| | | if (currentVolume.compareTo(BigDecimal.ZERO) <= 0) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | Realtime realtime = list.get(0); |
| | | BigDecimal close = realtime.getClose(); |
| | | BigDecimal point = close.subtract(order.getTradeAvgPrice()); |
| | | BigDecimal profit = point.multiply(new BigDecimal("0.01")).multiply(order.getVolumeOpen()).setScale(4, BigDecimal.ROUND_DOWN);; |
| | | if (order.getForceClosePrice() != null && !order.getForceClosePrice().isEmpty()) { //达到强平价 |
| | | if ((ContractOrder.DIRECTION_BUY.equals(order.getDirection()) && close.compareTo(new BigDecimal(order.getForceClosePrice())) <= 0) |
| | | || (ContractOrder.DIRECTION_SELL.equals(order.getDirection()) && close.compareTo(new BigDecimal(order.getForceClosePrice())) >= 0)) { |
| | | point = new BigDecimal(order.getForceClosePrice()).subtract(order.getTradeAvgPrice()); |
| | | profit = point.multiply(new BigDecimal("0.01")).multiply(order.getVolumeOpen()).setScale(4, BigDecimal.ROUND_DOWN);; |
| | | System.out.println("------------------point-------------:"+point); |
| | | System.out.println("------------------profit------------:"+profit); |
| | | profit = order.getDepositOpen().add(order.getAddDepositOpen()).negate(); |
| | | System.out.println("------------------扣除保证金------------:"+profit); |
| | | if (order.getLocationType() == 1) { |
| | | Wallet wallet = this.walletService.findByUserId(order.getPartyId()); |
| | | System.out.println("------------------扣除余额------------:"+wallet.getMoney()); |
| | | profit = profit.subtract(wallet.getMoney()); |
| | | } |
| | | |
| | | BigDecimal closeRatio = volume.divide(currentVolume, 10, RoundingMode.HALF_UP); |
| | | BigDecimal originProfit = BigDecimal.ZERO; |
| | | if (cacheProfit != null) { |
| | | originProfit = cacheProfit.getProfit(); |
| | | if (cacheProfit.getCloseAvgPrice() != null) { |
| | | order.setCloseAvgPrice(cacheProfit.getCloseAvgPrice()); |
| | | } |
| | | } else { |
| | | List<Realtime> list = this.dataService.realtime(order.getSymbol()); |
| | | if (!list.isEmpty()) { |
| | | originProfit = calculatePricePnl(order, list.get(0).getClose(), currentVolume); |
| | | } |
| | | } |
| | | |
| | | if(null != order.getProfitLossRatio() && order.getProfitLossRatio() > 0){ |
| | | profit = order.getDepositOpen().multiply(new BigDecimal((order.getProfitLossRatio()/100))).setScale(2, RoundingMode.DOWN); |
| | | BigDecimal profit = originProfit.multiply(closeRatio).setScale(4, RoundingMode.DOWN); |
| | | |
| | | if (null != order.getProfitLossRatio() && order.getProfitLossRatio() > 0) { |
| | | profit = order.getDepositOpen().multiply(new BigDecimal((order.getProfitLossRatio() / 100))).setScale(2, RoundingMode.DOWN); |
| | | } |
| | | if (ContractOrder.DIRECTION_BUY.equals(order.getDirection())) { |
| | | order.setProfit(profit); |
| | | } else{ |
| | | order.setProfit(profit.negate()); |
| | | } |
| | | |
| | | order.setProfit(profit); |
| | | BigDecimal rentalProfit = order.getDepositOpen().add(order.getProfit()).add(order.getAddDepositOpen()); |
| | | System.out.println("------------------rentalProfit------------:"+rentalProfit); |
| | | BigDecimal rate = volume.divide(order.getVolumeOpen(), 2, RoundingMode.HALF_UP); |
| | | order.setAmountClose(order.getAmountClose().add(profit)); |
| | | order.setVolume(order.getVolume().subtract(volume)); |
| | |
| | | order.setState(ContractOrder.STATE_CREATED); |
| | | order.setCloseTime(DateUtil.currentSeconds()); |
| | | order.setCloseTimeTs(DateUtil.currentSeconds()); |
| | | |
| | | } |
| | | return rentalProfit; |
| | | } |
| | |
| | | ); |
| | | } |
| | | //计算强平价格 |
| | | getStrongPrice(f,item); |
| | | getStrongPrice(f); |
| | | update(f); |
| | | refreshOrder(applyOrder, f); |
| | | }else{ |
| | |
| | | fee = fee.setScale(4, RoundingMode.DOWN); // 保留两位小数 |
| | | order.setFee(fee); |
| | | } |
| | | double number = strongLevelCalculationService.countSheets(order.getDepositOpen().doubleValue(), order.getLeverRate().intValue(), 0.01, applyOrder.getPrice().doubleValue()); |
| | | double number = strongLevelCalculationService.countSheets(order.getDepositOpen().doubleValue(), order.getLeverRate().intValue(), 0.01, realtime.getClose().doubleValue()); |
| | | order.setVolume(new BigDecimal(number)); |
| | | order.setVolumeOpen(new BigDecimal(number)); |
| | | order.setTradeAvgPrice(realtime.getClose()); |
| | | |
| | | walletService.updateMoney(order.getSymbol(), order.getPartyId(), BigDecimal.ZERO.subtract(order.getDeposit()), BigDecimal.ZERO |
| | | , Constants.MONEYLOG_CATEGORY_CONTRACT, Constants.WALLET_USDT, Constants.MONEYLOG_CONTENT_CONTRACT_OPEN, "委托单,订单号[" + order.getOrderNo() + "]" |
| | |
| | | ); |
| | | } |
| | | //计算强平价格 |
| | | getStrongPrice(order,item); |
| | | getStrongPrice(order); |
| | | save(order); |
| | | refreshOrder(applyOrder, order); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | private void getStrongPrice(ContractOrder order, Item item) { |
| | | BigDecimal earnings = BigDecimal.ZERO; |
| | | if(order.getLocationType() == 1){//全仓 |
| | | // 获取当前账户余额并加到收益中 |
| | | //Map<String, Object> moneyAll = walletService.getMoneyAll(order.getPartyId()); |
| | | //earnings = order.getDepositOpen().add(new BigDecimal(moneyAll.get("money_wallet").toString())); |
| | | Wallet wallet = walletService.saveWalletByPartyId(order.getPartyId()); |
| | | earnings = order.getDepositOpen().add(earnings.add(wallet.getMoney())); |
| | | }else{ |
| | | earnings = order.getDepositOpen().add(order.getAddDepositOpen()); |
| | | } |
| | | double faceValue = 0.01; // 合约面值(固定面值不能调整) |
| | | double maintenanceMarginRate = 0.004; // 维持保证金率(固定不变) |
| | | //"buy":买(多) "sell":卖(空) |
| | | if(order.getDirection().equals("buy")){ |
| | | double forceClosePrice = strongLevelCalculationService.calculateLiquidationPrice(earnings.doubleValue(), |
| | | faceValue, order.getVolumeOpen().doubleValue(), order.getTradeAvgPrice().doubleValue() |
| | | , maintenanceMarginRate, item.getUnitFee().doubleValue()); |
| | | order.setForceClosePrice(BigDecimal.valueOf(forceClosePrice).toString()); |
| | | }else{ |
| | | double forceClosePrice = strongLevelCalculationService.calculateEmptyLiquidationPrice(earnings.doubleValue(), |
| | | faceValue, order.getVolumeOpen().doubleValue(), order.getTradeAvgPrice().doubleValue() |
| | | , maintenanceMarginRate, item.getUnitFee().doubleValue()); |
| | | order.setForceClosePrice(BigDecimal.valueOf(forceClosePrice).toString()); |
| | | } |
| | | private void getStrongPrice(ContractOrder order) { |
| | | order.setForceClosePrice(contractOrderCalculationService.calculateForceClosePriceForOrder(order)); |
| | | } |
| | | |
| | | public ContractApplyOrder saveClose(ContractApplyOrder applyOrder, Realtime realtime, String order_no) { |