package com.yami.trading.service.etf; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.lang.Tuple; import cn.hutool.core.util.NumberUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.yami.trading.bean.data.domain.Kline; import com.yami.trading.bean.data.domain.RobotModel; import com.yami.trading.bean.etf.domain.EtfKLine; import com.yami.trading.bean.etf.domain.EtfMinuteKLine; import com.yami.trading.bean.etf.domain.EtfSecKLine; import com.yami.trading.bean.etf.domain.KlineConfig; import com.yami.trading.bean.etf.dto.KlineConfigDTO; import com.yami.trading.bean.etf.mapstruct.EtfMinuteKLineWrapper; import com.yami.trading.bean.etf.mapstruct.EtfSecKLineWrapper; import com.yami.trading.bean.item.domain.Item; import com.yami.trading.bean.robot.domain.RobotOrder; import com.yami.trading.common.exception.YamiShopBindException; import com.yami.trading.dao.data.RobotModelMapper; import com.yami.trading.dao.etf.mapper.KlineConfigMapper; import com.yami.trading.service.item.ItemService; import com.yami.trading.service.robot.RobotOrderService; import org.jetbrains.annotations.NotNull; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.*; import java.util.stream.Collectors; /** * etfK线图配置表Service * * @author lucas * @version 2023-05-03 */ @Service @Transactional public class KlineConfigService extends ServiceImpl { @Autowired private RobotModelMapper robotModelMapper; @Autowired private ItemService itemService; @Autowired EtfSecKLineService etfSecKLineService; @Autowired EtfSecKLineWrapper etfSecKLineWrapper; @Autowired RobotOrderService robotOrderService; @Autowired EtfMinuteKLineService etfMinuteKLineService; @Autowired EtfMinuteKLineWrapper etfMinuteKLineWrapper; @Lazy @Autowired MarketService marketService; @Transactional(rollbackFor = Exception.class) public void deleteByRobotModelId(List ids) { long currentTimeMillis = System.currentTimeMillis(); for (String id : ids) { KlineConfig config = this.getBaseMapper().selectById(id); if (config != null) { if (config.getOpenTimeTs() <= currentTimeMillis && config.getCloseTimeTs() >= currentTimeMillis) { throw new YamiShopBindException("当前为开市时间,不能删除"); } removeById(id); etfSecKLineService.remove(new QueryWrapper().eq("symbol", config.getSymbol()).between("ts", config.getOpenTimeTs(), config.getCloseTimeTs())); //etfMinuteKLineService 根据config的开市时间,结束时间,symbol去删除 etfMinuteKLineService.remove(new QueryWrapper().eq("symbol", config.getSymbol()).between("ts", config.getOpenTimeTs(), config.getCloseTimeTs())); marketService.getCacheKline().put(config.getSymbol(), new LinkedHashMap<>()); } } } public EtfKLine queryKLine(KlineConfigDTO klineConfigDTO) { BigDecimal divide = klineConfigDTO.getClosePrice().subtract(klineConfigDTO.getOpenPrice()).divide(klineConfigDTO.getOpenPrice(), 2, BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100)); List robotModels = new LambdaQueryChainWrapper<>(robotModelMapper).between(RobotModel::getPercent, divide.subtract(new BigDecimal(0.5)), divide.add(new BigDecimal(0.5))).list(); if (CollectionUtil.isEmpty(robotModels)) { throw new YamiShopBindException("没有满足条件的趋势图"); } // 解析数据库满足条件涨幅的k线图数据 // 随机获取一个,获取完整一天的分钟k线图 RobotModel robotModel = robotModels.get(new Random().nextInt(robotModels.size())); List randomKLine = parseHistoryKLine(robotModel); // 获取完整一天的k线图 randomKLine.sort(Comparator.comparing(Kline::getTs)); // 生成分钟图返回 List klines = accrodingSecOrign(klineConfigDTO, robotModel, randomKLine, 60); // printResult(klines); return new EtfKLine(robotModel.getUuid(),klines); } public List generateSecKLine(KlineConfigDTO klineConfigDTO) { RobotModel robotModel = robotModelMapper.selectById(klineConfigDTO.getRobot_model_uuid()); // 假图的分钟图 List klines = parseHistoryKLine(robotModel); // 生成分钟图 List minute = accrodingSecOrign(klineConfigDTO, robotModel, klines, 60); etfMinuteKLineService.saveBatch(etfMinuteKLineWrapper.toDTO(minute),500); saveRobotOrder(klineConfigDTO, minute); // 生成的秒图 return getRecentWholwSecKline(klineConfigDTO.getSymbol(), minute); } private void saveRobotOrder(KlineConfigDTO klineConfigDTO, List minute) { Random random = new Random(); Item item = itemService.findBySymbol(klineConfigDTO.getSymbol()); List robotOrders = new ArrayList<>(minute.size()); for (Kline kline : minute) { RobotOrder robotOrder = new RobotOrder(); robotOrder.setSymbol(klineConfigDTO.getSymbol()); robotOrder.setPrice(NumberUtil.roundDown(randomBigDecimal(kline.getHigh(), kline.getLow(),random),item.getDecimals()).doubleValue()); // 设置订单状态挂单中 robotOrder.setStatus(1); robotOrder.setOrderType(random.nextInt(2) + 1); robotOrder.setDirection(random.nextInt(2) + 1); robotOrder.setOrderQuantity(NumberUtil.roundDown(randomBigDecimal(kline.getVolume(), kline.getVolume().multiply(new BigDecimal(0.8)), random),item.getDecimals()).doubleValue()); robotOrder.setTurnover(0d); robotOrder.setTs(kline.getTs()); robotOrder.setUuid(UUID.randomUUID().toString().replaceAll("-", "")); robotOrder.setUid(UUID.randomUUID().toString().replaceAll("-", "")); robotOrders.add(robotOrder); } robotOrderService.saveBatch(robotOrders); } // 生成秒级的假图 private List accrodingSecOrign(KlineConfigDTO klineConfigDTO,RobotModel robotModel, List originalSecKline, int sec) { Item item = itemService.findBySymbol(klineConfigDTO.getSymbol()); BigDecimal todayVolume = BigDecimal.valueOf(randomBigDecimal(klineConfigDTO.getTurnoverHigh(), klineConfigDTO.getTurnoverLow(), new Random())); BigDecimal todayAmount = todayVolume.divide(klineConfigDTO.getOpenPrice().add(klineConfigDTO.getClosePrice()).divide(BigDecimal.valueOf(2)), item.getDecimals(), BigDecimal.ROUND_HALF_UP); double totalVolume = originalSecKline.stream() .mapToDouble(klien -> klien.getVolume().doubleValue()) .sum(); Tuple startAndEnd = getStartAndEnd(klineConfigDTO.getOpenTimeTs()); klineConfigDTO.setOpenTimeTs(startAndEnd.get(0)); klineConfigDTO.setCloseTimeTs(startAndEnd.get(1)); Kline finalSecondKline; BigDecimal open = klineConfigDTO.getOpenPrice(); BigDecimal secHigh; BigDecimal secLow; BigDecimal secVolume; BigDecimal secAmount; boolean isOver = false; List finalResult = new ArrayList<>(originalSecKline.size()); Random random = new Random(); for (int i = 0; i < originalSecKline.size(); i++) { Kline kline = originalSecKline.get(i); finalSecondKline = new Kline(); finalSecondKline.setTs(klineConfigDTO.getOpenTimeTs() + i * sec * 1000); finalSecondKline.setSymbol(item.getSymbol()); // 设置开仓价 finalSecondKline.setOpen(open); // 分钟级最高价 model.high / model.open * 分钟的open secHigh = kline.getHigh().divide(kline.getOpen(), 10, BigDecimal.ROUND_HALF_UP).multiply(finalSecondKline.getOpen()).setScale(item.getDecimals(), BigDecimal.ROUND_HALF_UP); finalSecondKline.setHigh(secHigh); // 分钟级最低价 secLow = kline.getLow().divide(kline.getOpen(), 10, BigDecimal.ROUND_HALF_UP).multiply(finalSecondKline.getOpen()).setScale(item.getDecimals(), BigDecimal.ROUND_HALF_UP); finalSecondKline.setLow(secLow); // 分钟级成交量 if (kline.getVolume() == null ||kline.getVolume().longValue() == 0) { secVolume = BigDecimal.ZERO; } else { secVolume = kline.getVolume().divide(BigDecimal.valueOf(totalVolume),10, BigDecimal.ROUND_HALF_UP).multiply(todayVolume).setScale(item.getDecimals(), BigDecimal.ROUND_HALF_UP); } finalSecondKline.setVolume(secVolume); if (kline.getAmount() == null || kline.getAmount().longValue() == 0) { secAmount = BigDecimal.ZERO; } else { // 分钟级成交手数 secAmount = kline.getAmount().divide(BigDecimal.valueOf(robotModel.getAmount()),10, BigDecimal.ROUND_HALF_UP).multiply(todayAmount).setScale(item.getDecimals(), BigDecimal.ROUND_HALF_UP); } finalSecondKline.setAmount(secAmount); // 收盘价 if (finalSecondKline.getTs() > klineConfigDTO.getCloseTimeTs() || i == originalSecKline.size() - 1) { finalSecondKline.setClose(klineConfigDTO.getClosePrice().setScale(item.getDecimals(), BigDecimal.ROUND_HALF_UP)); isOver = true; } else { double close = randomBigDecimal(secHigh, secLow, random); finalSecondKline.setClose(BigDecimal.valueOf(close).setScale(item.getDecimals(), BigDecimal.ROUND_HALF_UP)); } open = finalSecondKline.getClose(); finalResult.add(finalSecondKline); if (isOver) { break; } } return finalResult; } @NotNull private List getRecentWholwSecKline(String symbol, List collect) { Item bySymbol = itemService.findBySymbol(symbol); List originalSecKline = new ArrayList<>(collect.size()); Kline originalSecondKline = null; Random random = new Random(); for (Kline kline : collect) { // 将这一分钟成交量拆分成60份 List volumes = splitData(kline.getVolume().doubleValue(), 60, 30); List amounts = splitData(kline.getAmount().doubleValue(), 60, 30); BigDecimal open = originalSecondKline == null ? kline.getOpen() : originalSecondKline.getClose(); for (int i = 0; i < 60; i++) { originalSecondKline = new Kline(); originalSecondKline.setSymbol(symbol); originalSecondKline.setTs(kline.getTs() + i * 1000); originalSecondKline.setOpen(open.setScale(bySymbol.getDecimals(), BigDecimal.ROUND_HALF_UP)); double close = 0; if (i == 59) { originalSecondKline.setClose(kline.getClose().setScale(bySymbol.getDecimals(), BigDecimal.ROUND_HALF_UP)); } else { close = randomBigDecimal(kline.getOpen(), kline.getClose(), random); originalSecondKline.setClose(BigDecimal.valueOf(close).setScale(bySymbol.getDecimals(), BigDecimal.ROUND_HALF_UP)); } double high = randomBigDecimal(kline.getLow(), kline.getHigh(), random); originalSecondKline.setHigh(BigDecimal.valueOf(high).setScale(bySymbol.getDecimals(), BigDecimal.ROUND_HALF_UP)); double low = randomBigDecimal(kline.getLow(), originalSecondKline.getClose(), random); originalSecondKline.setLow(BigDecimal.valueOf(low).min(originalSecondKline.getOpen()).setScale(bySymbol.getDecimals(), BigDecimal.ROUND_HALF_UP)); originalSecondKline.setVolume(volumes.get(i).setScale(bySymbol.getDecimals(), BigDecimal.ROUND_HALF_UP)); originalSecondKline.setAmount(amounts.get(i).setScale(bySymbol.getDecimals(), BigDecimal.ROUND_HALF_UP)); // 重新调整K线图数据 adjustKline(originalSecondKline); // 重新打散 while (originalSecondKline.getHigh().doubleValue() == originalSecondKline.getLow().doubleValue()) { // 收盘价在最高和最低之间 当最高价最低价相同时, close = random ( high * random(1,1.01) , low * random(1,1.01) ) // 随机最高价 为当前分钟最高价的1.005倍 BigDecimal tempHigh = kline.getHigh().multiply(getRandomBigDecimal(1)); // 随机最低价 为当前分钟最低价的0.995倍 BigDecimal tempLow = kline.getLow().multiply(getRandomBigDecimal(-1)); originalSecondKline.setClose(BigDecimal.valueOf(randomBigDecimal(tempHigh,tempLow, random)).setScale(bySymbol.getDecimals(), BigDecimal.ROUND_HALF_UP)); // 最高价在收盘价和这一分钟的最高价之间 originalSecondKline.setHigh(BigDecimal.valueOf(randomBigDecimal(originalSecondKline.getClose(), kline.getHigh(), random)).setScale(bySymbol.getDecimals(), BigDecimal.ROUND_HALF_UP)); // 最低价在收盘价和这一分钟的最低价之间 originalSecondKline.setLow(BigDecimal.valueOf(randomBigDecimal(kline.getLow(), originalSecondKline.getClose(), random)).setScale(bySymbol.getDecimals(), BigDecimal.ROUND_HALF_UP)); adjustKline(originalSecondKline); } open = originalSecondKline.getClose(); originalSecKline.add(originalSecondKline); } } return originalSecKline; } public static Kline adjustKline(Kline kline) { BigDecimal[] values = {kline.getHigh(), kline.getOpen(), kline.getClose(), kline.getLow()}; Arrays.sort(values); kline.setHigh(values[3]); kline.setLow(values[0]); return kline; } public static BigDecimal getRandomBigDecimal(int input) { BigDecimal lowerBound; BigDecimal upperBound; if (input == 1) { lowerBound = BigDecimal.ONE; upperBound = new BigDecimal("1.005"); } else if (input == -1) { lowerBound = new BigDecimal("0.995"); upperBound = BigDecimal.ONE; } else { throw new IllegalArgumentException("Invalid input value. Expected 1 or -1."); } BigDecimal range = upperBound.subtract(lowerBound); BigDecimal randomValue = range.multiply(BigDecimal.valueOf(Math.random())).add(lowerBound); return randomValue.setScale(3, RoundingMode.HALF_UP); } private void printResult(List originalSecKline) { // 输出到echarts List> result = new ArrayList<>(); List time = new ArrayList<>(); // 格式化时间 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss"); for (Kline kline : originalSecKline) { List tmp = new ArrayList<>(); time.add(simpleDateFormat.format(new Date(kline.getTs()))); tmp.add(kline.getOpen().setScale(5, BigDecimal.ROUND_HALF_UP)); tmp.add(kline.getClose().setScale(5, BigDecimal.ROUND_HALF_UP)); tmp.add(kline.getLow().setScale(5, BigDecimal.ROUND_HALF_UP)); tmp.add(kline.getHigh().setScale(5, BigDecimal.ROUND_HALF_UP)); // tmp.add(kline.getVolume().setScale(5, BigDecimal.ROUND_HALF_UP)); result.add(tmp); } System.err.println(JSON.toJSONString(time)); System.err.println(JSON.toJSONString(result)); } /** * 传入时间字符串获取当天第一个时间戳 */ private Long getTodayFirstTimestamp(String time) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); Date date = null; try { date = dateFormat.parse(time); } catch (ParseException e) { e.printStackTrace(); } Calendar calendar = Calendar.getInstance(); calendar.setTime(date); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); return calendar.getTimeInMillis(); } /** * 传入时间字符串获取当天最后一个时间戳 */ private Long getTodayLastTimestamp(String time) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); Date date = null; try { date = dateFormat.parse(time); } catch (ParseException e) { e.printStackTrace(); } Calendar calendar = Calendar.getInstance(); calendar.setTime(date); calendar.set(Calendar.HOUR_OF_DAY, 23); calendar.set(Calendar.MINUTE, 59); calendar.set(Calendar.SECOND, 59); return calendar.getTimeInMillis(); } @NotNull private List parseHistoryKLine(RobotModel robotModel) { JSONArray jsonArray = JSON.parseArray(robotModel.getKLineData()); List klines = new ArrayList<>(jsonArray.size()); for (int i = 0; i < jsonArray.size(); i++) { JSONObject item = jsonArray.getJSONObject(i); Kline kline = new Kline(); kline.setTs(item.getLong("ts")); kline.setVolume(item.getBigDecimal("volume")); kline.setAmount(item.getBigDecimal("amount")); kline.setOpen(item.getBigDecimal("open")); kline.setHigh(item.getBigDecimal("high")); kline.setLow(item.getBigDecimal("low")); kline.setClose(item.getBigDecimal("close")); klines.add(kline); } return klines; } public static double randomBigDecimal(BigDecimal a, BigDecimal b, Random random) { double diff = b.doubleValue() - a.doubleValue(); double randNum = diff * random.nextDouble(); return a.doubleValue() + randNum; } // public static List splitBigDecimal(BigDecimal value, int numOfParts, double maxDifferencePercentage) { // List parts = new ArrayList<>(numOfParts); // Random random = new Random(); // // BigDecimal remaining = value; // BigDecimal minAllowedValue = value.multiply(BigDecimal.valueOf(1 - maxDifferencePercentage / 100)).divide(BigDecimal.valueOf(numOfParts), value.scale(), BigDecimal.ROUND_HALF_UP); // BigDecimal maxAllowedValue = value.multiply(BigDecimal.valueOf(1 + maxDifferencePercentage / 100)).divide(BigDecimal.valueOf(numOfParts), value.scale(), BigDecimal.ROUND_HALF_UP); // // for (int i = 0; i < numOfParts - 1; i++) { // BigDecimal part = getRandomBigDecimal(minAllowedValue, maxAllowedValue, random); // while (remaining.subtract(part).compareTo(minAllowedValue.multiply(BigDecimal.valueOf(numOfParts - i - 1))) < 0) { // part = getRandomBigDecimal(minAllowedValue, maxAllowedValue, random); // } // parts.add(part); // remaining = remaining.subtract(part); // } // // parts.add(remaining); // return parts; // } public static List splitData(double data, int n, double maxDeviationPercent) { // 计算每份的理论平均值 double avg = data / n; // 计算允许的最大偏差 double maxDeviation = avg * maxDeviationPercent / 100; // 创建一个存储每份数据的列表 List splits = new ArrayList<>(); Random random = new Random(); double remainingData = data; // 按照N-1份进行随机拆分 for (int i = 0; i < n - 1; i++) { // 生成一个随机数,表示当前份数据的比例(0到1之间) double ratio = random.nextDouble(); // 计算当前份数据的大小 double currentSplit = avg + (ratio - 0.5) * 2 * maxDeviation; // 将当前份数据加入列表 splits.add(currentSplit); // 更新剩余数据量 remainingData -= currentSplit; } // 最后一份数据即为剩余数据量 splits.add(remainingData); // 打乱列表中数据的顺序 Collections.shuffle(splits); return splits.stream().map(BigDecimal::valueOf).collect(Collectors.toList()); } private static BigDecimal getRandomBigDecimal(BigDecimal min, BigDecimal max, Random random) { double range = max.doubleValue() - (min.doubleValue()); double randomValue = range * random.nextDouble(); return BigDecimal.valueOf(min.doubleValue() + randomValue).setScale(min.scale(), BigDecimal.ROUND_HALF_UP); } /** * 查询秒级K线图 * * @param symbol * @return */ public List querySecKlineBySymbol(String symbol) { Tuple startAndEnd = getStartAndEnd(); List list = etfSecKLineService.list(new QueryWrapper().eq("symbol", symbol).between("ts", startAndEnd.get(0), startAndEnd.get(1)).orderByAsc("ts")); return etfSecKLineWrapper.toEntity(list); } public Tuple getStartAndEnd() { // 获取当前时间的时间戳(毫秒级) long currentTimestamp = System.currentTimeMillis(); // 转换为ZonedDateTime对象,使用系统默认时区 ZonedDateTime currentDateTime = Instant.ofEpochMilli(currentTimestamp) .atZone(ZoneId.systemDefault()); // 获取当天的21:30时间点 ZonedDateTime targetDateTime; if (currentDateTime.getHour() >= 4 && currentDateTime.getHour() <= 23) { // 当前时间在4点到21点之间,获取当天的21:30时间点 targetDateTime = currentDateTime.withHour(23).withMinute(59).withSecond(59).withNano(0); } else { // 当前时间在0点到4点之间,获取前一天的21:30时间点 targetDateTime = currentDateTime.minusDays(1).withHour(21).withMinute(30).withSecond(0).withNano(0); } // 获取第二天凌晨4点时间点 ZonedDateTime nextDayDateTime = targetDateTime.plusDays(1).withHour(4).withMinute(0); // 转换为毫秒时间戳 long start = targetDateTime.toInstant().toEpochMilli(); long end = nextDayDateTime.toInstant().toEpochMilli(); return new Tuple(start, end); } public Tuple getStartAndEnd(long ts) { // 转换为ZonedDateTime对象,使用系统默认时区 ZonedDateTime inputDateTime = Instant.ofEpochMilli(ts) .atZone(ZoneId.systemDefault()); // 获取当天的21:30时间点 ZonedDateTime targetDateTime = inputDateTime.withHour(21).withMinute(30).withSecond(0).withNano(0); // 获取第二天的4:00时间点 ZonedDateTime nextDayDateTime = targetDateTime.plusDays(1).withHour(4).withMinute(0); // 转换为毫秒时间戳 long start = targetDateTime.toInstant().toEpochMilli(); long end = nextDayDateTime.toInstant().toEpochMilli(); return new Tuple(start, end); } /** * 查询所有秒级K线图 * * @param symbol * @return */ public List querySecKlineBySymbolAllData(String symbol) { long currentTime = System.currentTimeMillis(); // QueryWrapper queryWrapper = new QueryWrapper<>(); // queryWrapper.eq("symbol", symbol).le("open_time_ts", currentTime).ge("close_time_ts", currentTime); // List klineConfigs = this.baseMapper.selectList(queryWrapper); // // if (CollectionUtils.isEmpty(klineConfigs)) { // throw new YamiShopBindException("未查询到当天配置的秒级K线图"); // } // // if (klineConfigs.size() > 1) { // throw new YamiShopBindException("查询到多个当天配置的秒级K线图"); // } // // KlineConfig klineConfig = klineConfigs.get(0); List list = etfSecKLineService.list(new QueryWrapper().eq("symbol", symbol).orderBy(true, true, "ts")); return etfSecKLineWrapper.toEntity(list); } }