package com.yami.trading.service.contract;
|
|
import com.yami.trading.bean.contract.domain.ContractOrder;
|
import com.yami.trading.bean.data.domain.Realtime;
|
import com.yami.trading.bean.item.domain.Item;
|
import com.yami.trading.bean.model.Wallet;
|
import com.yami.trading.bean.syspara.domain.Syspara;
|
import com.yami.trading.common.constants.ContractRedisKeys;
|
import com.yami.trading.common.util.RedisUtil;
|
import com.yami.trading.common.util.ThreadUtils;
|
import com.yami.trading.service.WalletService;
|
import com.yami.trading.service.data.DataService;
|
import com.yami.trading.service.item.ItemService;
|
import com.yami.trading.service.syspara.SysparaService;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.Logger;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.context.annotation.Lazy;
|
import org.springframework.stereotype.Service;
|
|
import javax.annotation.Resource;
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.time.Duration;
|
import java.time.Instant;
|
import java.time.LocalDate;
|
import java.time.ZoneId;
|
import java.util.List;
|
|
@Slf4j
|
@Service
|
public class ContractOrderCalculationServiceImpl implements ContractOrderCalculationService {
|
@Autowired
|
private ItemService itemService;
|
/**
|
* 平仓线 110%(订金价值 /收益=110%)
|
*/
|
public BigDecimal order_close_line = new BigDecimal("1.1");
|
/**
|
* 平仓方式 1全仓 2单个持仓
|
*/
|
public int order_close_line_type = 1;
|
@Autowired
|
private ContractOrderService contractOrderService;
|
@Qualifier("dataService")
|
@Autowired
|
@Lazy
|
private DataService dataService;
|
@Autowired
|
private WalletService walletService;
|
@Resource
|
private SysparaService sysparaService;
|
private Logger logger = LogManager.getLogger(ContractOrderCalculationServiceImpl.class);
|
|
private BigDecimal defaultZero(BigDecimal value) {
|
return value == null ? BigDecimal.ZERO : value;
|
}
|
|
private BigDecimal estimateAccruedFundingFee(ContractOrder order) {
|
if (order == null || order.getCreateTime() == null) {
|
return BigDecimal.ZERO;
|
}
|
BigDecimal borrowedAmount = defaultZero(order.getBorrowedAmount());
|
if (borrowedAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
return BigDecimal.ZERO;
|
}
|
long holdHours = Duration.between(order.getCreateTime().toInstant(), Instant.now()).toHours();
|
long settlementPeriods = holdHours / 4;
|
if (settlementPeriods <= 0) {
|
return BigDecimal.ZERO;
|
}
|
BigDecimal conservativeRate = new BigDecimal("0.001");
|
return borrowedAmount.multiply(conservativeRate)
|
.multiply(BigDecimal.valueOf(settlementPeriods))
|
.setScale(8, RoundingMode.HALF_UP);
|
}
|
|
private BigDecimal calculateType1ForceClosePrice(ContractOrder order, Wallet wallet) {
|
BigDecimal volume = defaultZero(order.getVolume());
|
BigDecimal tradeAvgPrice = defaultZero(order.getTradeAvgPrice());
|
if (volume.compareTo(BigDecimal.ZERO) <= 0) {
|
return BigDecimal.ZERO;
|
}
|
|
List<ContractOrder> list = contractOrderService.findSubmitted(order.getPartyId(), null, null, null, null, null);
|
BigDecimal otherEquity = BigDecimal.ZERO;
|
for (ContractOrder contractOrder : list) {
|
if (ContractOrder.STATE_SUBMITTED.equals(contractOrder.getState())) {
|
contractOrderService.wrapProfit(contractOrder);
|
}
|
if (order.getUuid().equals(contractOrder.getUuid())) {
|
continue;
|
}
|
otherEquity = otherEquity.add(defaultZero(contractOrder.getProfit()).add(defaultZero(contractOrder.getDeposit())));
|
}
|
|
BigDecimal accruedFundingFee = estimateAccruedFundingFee(order);
|
BigDecimal baseEquity = defaultZero(wallet.getMoney())
|
.add(otherEquity)
|
.add(defaultZero(order.getDeposit()))
|
.subtract(accruedFundingFee);
|
BigDecimal priceOffset = baseEquity.divide(volume, 10, RoundingMode.HALF_UP);
|
if (ContractOrder.DIRECTION_BUY.equalsIgnoreCase(order.getDirection())) {
|
return tradeAvgPrice.subtract(priceOffset);
|
}
|
return tradeAvgPrice.add(priceOffset);
|
}
|
|
private BigDecimal calculateType2ForceClosePrice(ContractOrder order) {
|
BigDecimal volume = defaultZero(order.getVolume());
|
BigDecimal tradeAvgPrice = defaultZero(order.getTradeAvgPrice());
|
if (volume.compareTo(BigDecimal.ZERO) <= 0) {
|
return BigDecimal.ZERO;
|
}
|
BigDecimal thresholdRatio = order_close_line.divide(new BigDecimal(100), 10, RoundingMode.HALF_UP);
|
if (thresholdRatio.compareTo(BigDecimal.ZERO) <= 0) {
|
return tradeAvgPrice;
|
}
|
BigDecimal availableDeposit = defaultZero(order.getDeposit()).subtract(estimateAccruedFundingFee(order));
|
if (availableDeposit.compareTo(BigDecimal.ZERO) <= 0) {
|
return tradeAvgPrice;
|
}
|
BigDecimal requiredLoss = availableDeposit.divide(thresholdRatio, 10, RoundingMode.HALF_UP);
|
BigDecimal priceOffset = requiredLoss.divide(volume, 10, RoundingMode.HALF_UP);
|
if (ContractOrder.DIRECTION_BUY.equalsIgnoreCase(order.getDirection())) {
|
return tradeAvgPrice.subtract(priceOffset);
|
}
|
return tradeAvgPrice.add(priceOffset);
|
}
|
|
@Override
|
public void saveCalculation(String order_no) {
|
try {
|
ContractOrder order = contractOrderService.findByOrderNoRedis(order_no);
|
if (order == null || !ContractOrder.STATE_SUBMITTED.equals(order.getState())) {
|
/**
|
* 状态已改变,退出处理
|
*/
|
return;
|
}
|
List<Realtime> list = this.dataService.realtime(order.getSymbol());
|
if (list.size() == 0) {
|
return;
|
}
|
Realtime realtime = list.get(0);
|
|
BigDecimal close = new BigDecimal(realtime.getClose());
|
settle(order, "watch", close);
|
|
BigDecimal add = order.getTradeAvgPrice().add(order.getPips());
|
BigDecimal subtract = order.getTradeAvgPrice().subtract(order.getPips());
|
if (ContractOrder.DIRECTION_BUY.equals(order.getDirection())) {
|
|
/*
|
* 0 买涨
|
*/
|
if (close.compareTo(add) >= 0) {
|
settle(order, "profit", close);
|
}
|
|
if (close.compareTo(subtract) <= 0) {
|
settle(order, "loss", close);
|
}
|
|
} else {
|
/*
|
* 1 买跌
|
*/
|
if (close.compareTo(subtract) <= 0) {
|
settle(order, "profit", close);
|
}
|
if (close.compareTo(add) >= 0) {
|
settle(order, "loss", close);
|
}
|
}
|
} catch (Throwable e) {
|
log.error("ContractOrderCalculatio run fail", e);
|
}
|
|
}
|
|
@Override
|
public BigDecimal calculateAllProfit(ContractOrder order) {
|
List<Realtime> list = this.dataService.realtime(order.getSymbol());
|
if (list.size() == 0) {
|
log.error("{} 没有获取到实时价格", order.getSymbol());
|
return BigDecimal.ZERO;
|
}
|
Realtime realtime = list.get(0);
|
BigDecimal close = BigDecimal.valueOf(realtime.getClose());
|
// 偏差点位
|
BigDecimal point = close.subtract(order.getTradeAvgPrice()).abs().divide(order.getPips(), 10, RoundingMode.HALF_UP);
|
// 根据偏 差点数和手数算出盈亏金额
|
BigDecimal amount = order.getPipsAmount().multiply(point).multiply(order.getVolume());
|
if (order.getDirection().equalsIgnoreCase(ContractOrder.DIRECTION_BUY)) {
|
return amount;
|
} else {
|
return amount.negate();
|
}
|
|
}
|
|
/**
|
* 先按照系统时间区
|
*
|
* @param order
|
* @return
|
*/
|
@Override
|
public BigDecimal calculateTodayProfit(ContractOrder order, ZoneId zoneId) {
|
List<Realtime> list = this.dataService.realtime(order.getSymbol());
|
if (list.size() == 0) {
|
log.error("{} 没有获取到实时价格", order.getSymbol());
|
return BigDecimal.ZERO;
|
}
|
Realtime realtime = list.get(0);
|
BigDecimal close = BigDecimal.valueOf(realtime.getClose());
|
// 偏差点位
|
BigDecimal point;
|
if (isTimestampFromToday(order.getCreateTimeTs(), zoneId)) {
|
point = close.subtract(order.getTradeAvgPrice()).abs().divide(order.getPips(), 10, RoundingMode.HALF_UP);
|
} else {
|
|
point = close.subtract(BigDecimal.valueOf(realtime.getOpen())).abs().divide(order.getPips(), 10, RoundingMode.HALF_UP);
|
}
|
/*
|
* 根据偏 差点数和手数算出盈亏金额
|
*/
|
BigDecimal amount = order.getPipsAmount().multiply(point).multiply(order.getVolume());
|
if (order.getDirection().equalsIgnoreCase(ContractOrder.DIRECTION_BUY)) {
|
return amount;
|
} else {
|
return amount.negate();
|
}
|
|
}
|
|
public static boolean isTimestampFromToday(long timestamp, ZoneId zoneId) {
|
Instant instant = Instant.ofEpochSecond(timestamp);
|
LocalDate timestampDate = instant.atZone(ZoneId.systemDefault()).toLocalDate();
|
LocalDate today = LocalDate.now();
|
|
return timestampDate.isEqual(today);
|
}
|
|
/**
|
* 盈亏计算
|
*
|
* @param profit_loss profit 盈 loss亏
|
* @param currentPrice 当前点位
|
*/
|
public void settle(ContractOrder order, String profit_loss, BigDecimal currentPrice) {
|
|
/**
|
* 偏差点位
|
*/
|
BigDecimal pips = order.getPips();
|
if(pips.doubleValue() <= 0.00){
|
pips = new BigDecimal("0.01");
|
}
|
|
/**
|
* 根据价格变化百分比和保证金计算盈亏金额
|
*/
|
BigDecimal priceChangeRatio = currentPrice.subtract(order.getTradeAvgPrice())
|
.divide(order.getTradeAvgPrice(), 6, RoundingMode.DOWN);
|
|
BigDecimal margin = order.getTradeAvgPrice().multiply(order.getVolume()); // 这是用户当前持仓对应的投资金额
|
|
BigDecimal profitAmount = margin.multiply(priceChangeRatio);
|
|
if (ContractOrder.DIRECTION_BUY.equals(order.getDirection())) {
|
order.setProfit(profitAmount.setScale(6,RoundingMode.DOWN));
|
} else{
|
order.setProfit(profitAmount.setScale(6,RoundingMode.DOWN).negate());
|
}
|
|
|
/**
|
* 多次平仓价格不对,后续修
|
*/
|
order.setCloseAvgPrice(currentPrice);
|
this.contractOrderService.updateByIdBuffer(order);
|
|
/**
|
* 止盈价
|
*/
|
BigDecimal profitStop = order.getStopPriceProfit();
|
if (profitStop != null && profitStop.compareTo(BigDecimal.ZERO) > 0 && ContractOrder.DIRECTION_BUY.equals(order.getDirection())) {
|
/*
|
* 买涨
|
*/
|
if (currentPrice.compareTo(profitStop) >= 0) {
|
this.contractOrderService.saveClose(order.getPartyId().toString(), order.getOrderNo());
|
return;
|
}
|
} else if (profitStop != null && profitStop.compareTo(BigDecimal.ZERO) > 0
|
&& ContractOrder.DIRECTION_SELL.equals(order.getDirection())) {
|
/**
|
* 买跌
|
*/
|
if (currentPrice.compareTo(profitStop) <= 0) {
|
this.contractOrderService.saveClose(order.getPartyId().toString(), order.getOrderNo());
|
return;
|
}
|
}
|
|
/**
|
* 止亏线
|
*/
|
BigDecimal loss_stop = order.getStopPriceLoss();
|
|
if (loss_stop != null && loss_stop.compareTo(BigDecimal.ZERO) > 0 && ContractOrder.DIRECTION_BUY.equals(order.getDirection())) {
|
/*
|
* 买涨
|
*/
|
if (currentPrice.compareTo(loss_stop) <= 0) {
|
this.contractOrderService.saveClose(order.getPartyId().toString(), order.getOrderNo());
|
return;
|
|
}
|
} else if (loss_stop != null && loss_stop.compareTo(BigDecimal.ZERO) > 0 && ContractOrder.DIRECTION_SELL.equals(order.getDirection())) {
|
/**
|
* 买跌
|
*/
|
|
if (currentPrice.compareTo(loss_stop) >= 0) {
|
this.contractOrderService.saveClose(order.getPartyId().toString(), order.getOrderNo());
|
return;
|
}
|
}
|
BigDecimal profit1 = contractOrderService.getCacheProfit(order.getUuid()).getProfit();
|
if (order_close_line_type == 1) {
|
Wallet wallet = this.walletService.findByUserId(order.getPartyId().toString());
|
Integer decimal = itemService.getDecimal(order.getSymbol());
|
BigDecimal forceClose = calculateType1ForceClosePrice(order, wallet).setScale(decimal, RoundingMode.HALF_UP);
|
if (forceClose.compareTo(BigDecimal.ZERO) < 0) {
|
forceClose = BigDecimal.ZERO;
|
}
|
order.setForceClosePrice(forceClose.toPlainString());
|
this.contractOrderService.updateByIdBuffer(order);
|
List<ContractOrder> list = contractOrderService.findSubmitted(order.getPartyId(), null, null, null, null, null);
|
BigDecimal totalEquity = defaultZero(wallet.getMoney());
|
for(ContractOrder contractOrder :list) {
|
if(ContractOrder.STATE_SUBMITTED.equals(contractOrder.getState())){
|
contractOrderService.wrapProfit(contractOrder);
|
}
|
totalEquity = totalEquity.add(defaultZero(contractOrder.getProfit()).add(defaultZero(contractOrder.getDeposit())));
|
}
|
|
if (totalEquity.compareTo(BigDecimal.ZERO) <= 0) {
|
/**
|
* 触发全仓强平
|
*/
|
this.contractOrderService.saveClose(order.getPartyId().toString(), order.getOrderNo());
|
|
}
|
} else {
|
Integer decimal = itemService.getDecimal(order.getSymbol());
|
BigDecimal forceClose = calculateType2ForceClosePrice(order).setScale(decimal, RoundingMode.HALF_UP);
|
if (forceClose.compareTo(BigDecimal.ZERO) < 0) {
|
forceClose = BigDecimal.ZERO;
|
}
|
order.setForceClosePrice(forceClose.toPlainString());
|
this.contractOrderService.updateByIdBuffer(order);
|
BigDecimal divide = order.getDeposit().divide(profit1.abs(), 10, RoundingMode.HALF_UP);
|
if (profit1.compareTo(BigDecimal.ZERO) < 0 && divide.compareTo(order_close_line.divide(new BigDecimal(100), 10, RoundingMode.HALF_UP)) <= 0) {
|
/**
|
* 低于系统默认平仓线,进行强平
|
*/
|
this.contractOrderService.saveClose(order.getPartyId().toString(), order.getOrderNo());
|
return;
|
}
|
}
|
}
|
|
|
|
@Override
|
public void setOrder_close_line(BigDecimal order_close_line) {
|
this.order_close_line = order_close_line;
|
}
|
|
@Override
|
public void setOrder_close_line_type(int order_close_line_type) {
|
this.order_close_line_type = order_close_line_type;
|
}
|
|
}
|