package com.nq.utils;
|
|
import com.nq.utils.stock.GetStayDays;
|
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.time.LocalTime;
|
import java.time.ZoneId;
|
import java.time.temporal.ChronoUnit;
|
import java.util.Date;
|
|
public final class StayFeeUtil {
|
|
private static final BigDecimal WAN_UNIT = new BigDecimal("10000");
|
|
public static final LocalTime CHARGE_TIME = LocalTime.of(14, 50);
|
|
private StayFeeUtil() {
|
}
|
|
/**
|
* 递延费计费基数:不足1万按1万;超过1万按每1万向上取整(如5999→10000,35999→40000)。
|
*/
|
public static BigDecimal calcBillingMarketValue(BigDecimal marketValue) {
|
BigDecimal mv = marketValue == null ? BigDecimal.ZERO : marketValue;
|
if (mv.compareTo(BigDecimal.ZERO) <= 0) {
|
return WAN_UNIT;
|
}
|
return mv.divide(WAN_UNIT, 0, RoundingMode.CEILING).multiply(WAN_UNIT);
|
}
|
|
public static BigDecimal calcDailyStayFee(BigDecimal marketValue, BigDecimal stayFeeRate) {
|
BigDecimal base = calcBillingMarketValue(marketValue);
|
BigDecimal rate = stayFeeRate == null ? BigDecimal.ZERO : stayFeeRate;
|
return base.multiply(rate).setScale(2, RoundingMode.HALF_UP);
|
}
|
|
public static LocalDate toLocalDate(Date date) {
|
if (date == null) {
|
return LocalDate.now();
|
}
|
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
}
|
|
/** 自然日:买入日不计,自次日起每个自然日计 1 天(含周末、法定假日本身) */
|
public static int getCalendarDaysSinceBuy(Date buyOrderTime) {
|
LocalDate buy = toLocalDate(GetStayDays.getBeginDate(buyOrderTime));
|
LocalDate today = LocalDate.now();
|
if (!buy.isBefore(today)) {
|
return 0;
|
}
|
return (int) ChronoUnit.DAYS.between(buy, today);
|
}
|
|
/** 交易日:买入日不计,仅统计非周末且非法定假日本身的工作日 */
|
public static int getTradingDaysSinceBuy(Date buyOrderTime) {
|
LocalDate buy = toLocalDate(GetStayDays.getBeginDate(buyOrderTime));
|
LocalDate today = LocalDate.now();
|
if (!buy.isBefore(today)) {
|
return 0;
|
}
|
int count = 0;
|
LocalDate d = buy.plusDays(1);
|
while (!d.isAfter(today)) {
|
if (TradingDayUtil.isTradingDay(d)) {
|
count++;
|
}
|
d = d.plusDays(1);
|
}
|
return count;
|
}
|
|
/**
|
* 应计费天数(买入日不计):
|
* 节假日休市开 → 仅交易日;关 → 每个自然日(不管节假日)。
|
*/
|
public static int getChargeableDaysSinceBuy(Date buyOrderTime, boolean respectHoliday) {
|
return respectHoliday ? getTradingDaysSinceBuy(buyOrderTime) : getCalendarDaysSinceBuy(buyOrderTime);
|
}
|
|
/**
|
* 本次定时任务应扣天数(最多 1 天,不补扣历史):
|
* 开 → 仅交易日扣 1 天;关 → 每个自然日扣 1 天(节假日也扣,不补扣周末/假日前遗漏的天数)。
|
*/
|
public static int getDaysToChargeThisRun(Date buyOrderTime, Integer orderStayDays, boolean respectHoliday) {
|
int chargeableDays = getChargeableDaysSinceBuy(buyOrderTime, respectHoliday);
|
int billed = orderStayDays == null ? 0 : orderStayDays;
|
if (chargeableDays < 1 || billed >= chargeableDays) {
|
return 0;
|
}
|
if (respectHoliday && !TradingDayUtil.isTradingDay(LocalDate.now())) {
|
return 0;
|
}
|
return 1;
|
}
|
|
public static boolean isEligibleForCharge(Date buyOrderTime, Integer orderStayDays, boolean respectHoliday) {
|
return getDaysToChargeThisRun(buyOrderTime, orderStayDays, respectHoliday) > 0;
|
}
|
|
public static long getNextStayFeeTimestamp(Date buyOrderTime, Integer orderStayDays, boolean respectHoliday) {
|
LocalDate buy = toLocalDate(GetStayDays.getBeginDate(buyOrderTime));
|
LocalDate today = LocalDate.now();
|
LocalDateTime now = LocalDateTime.now();
|
int chargeableDays = getChargeableDaysSinceBuy(buyOrderTime, respectHoliday);
|
int billed = orderStayDays == null ? 0 : orderStayDays;
|
|
LocalDate targetDate;
|
if (respectHoliday) {
|
if (chargeableDays < 1) {
|
targetDate = firstTradingDayOnOrAfter(buy.plusDays(1));
|
} else if (billed < chargeableDays
|
&& TradingDayUtil.isTradingDay(today)
|
&& now.toLocalTime().isBefore(CHARGE_TIME)) {
|
targetDate = today;
|
} else {
|
targetDate = firstTradingDayOnOrAfter(today.plusDays(1));
|
}
|
} else {
|
if (chargeableDays < 1) {
|
targetDate = buy.plusDays(1);
|
} else if (billed < chargeableDays && now.toLocalTime().isBefore(CHARGE_TIME)) {
|
targetDate = today;
|
} else {
|
targetDate = today.plusDays(1);
|
}
|
}
|
LocalDateTime next = LocalDateTime.of(targetDate, CHARGE_TIME);
|
return next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
}
|
|
public static long getStayFeeCountdownSec(long nextStayFeeTimestamp) {
|
long diff = nextStayFeeTimestamp - System.currentTimeMillis();
|
return diff <= 0 ? 0 : diff / 1000;
|
}
|
|
/** 节假日休市开:非交易日不跑递延费任务 */
|
public static boolean shouldSkipStayFeeTaskToday(boolean respectHoliday) {
|
return respectHoliday && !TradingDayUtil.isTradingDay(LocalDate.now());
|
}
|
|
private static LocalDate firstTradingDayOnOrAfter(LocalDate from) {
|
LocalDate d = from;
|
while (!TradingDayUtil.isTradingDay(d)) {
|
d = d.plusDays(1);
|
}
|
return d;
|
}
|
}
|