新版仿ok交易所-后端
1
zj
2025-10-30 14c1946eae86a86f8d1edee6cf3bdaf7572fc966
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
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<KlineConfigMapper, KlineConfig> {
 
    @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<String> 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<EtfSecKLine>().eq("symbol", config.getSymbol()).between("ts", config.getOpenTimeTs(), config.getCloseTimeTs()));
 
                //etfMinuteKLineService 根据config的开市时间,结束时间,symbol去删除
                etfMinuteKLineService.remove(new QueryWrapper<EtfMinuteKLine>().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<RobotModel> 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<Kline> randomKLine = parseHistoryKLine(robotModel);
 
        // 获取完整一天的k线图
        randomKLine.sort(Comparator.comparing(Kline::getTs));
 
        // 生成分钟图返回
        List<Kline> klines = accrodingSecOrign(klineConfigDTO, robotModel, randomKLine, 60);
 
//        printResult(klines);
 
        return new EtfKLine(robotModel.getUuid(),klines);
 
    }
 
    public List<Kline> generateSecKLine(KlineConfigDTO klineConfigDTO) {
 
 
        RobotModel robotModel = robotModelMapper.selectById(klineConfigDTO.getRobot_model_uuid());
 
        // 假图的分钟图
        List<Kline> klines = parseHistoryKLine(robotModel);
 
        // 生成分钟图
        List<Kline> 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<Kline> minute) {
        Random random = new Random();
        Item item = itemService.findBySymbol(klineConfigDTO.getSymbol());
 
        List<RobotOrder> 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<Kline> accrodingSecOrign(KlineConfigDTO klineConfigDTO,RobotModel robotModel, List<Kline> 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<Kline> 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<Kline> getRecentWholwSecKline(String symbol, List<Kline> collect) {
        Item bySymbol = itemService.findBySymbol(symbol);
 
        List<Kline> originalSecKline = new ArrayList<>(collect.size());
        Kline originalSecondKline = null;
        Random random = new Random();
        for (Kline kline : collect) {
 
            // 将这一分钟成交量拆分成60份
            List<BigDecimal> volumes = splitData(kline.getVolume().doubleValue(), 60, 30);
            List<BigDecimal> 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.DOWN);
    }
 
    private void printResult(List<Kline> originalSecKline) {
        // 输出到echarts
        List<List<Object>> result = new ArrayList<>();
        List<String> time = new ArrayList<>();
        // 格式化时间
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss");
 
        for (Kline kline : originalSecKline) {
            List<Object> 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<Kline> parseHistoryKLine(RobotModel robotModel) {
        JSONArray jsonArray = JSON.parseArray(robotModel.getKLineData());
        List<Kline> 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<BigDecimal> splitBigDecimal(BigDecimal value, int numOfParts, double maxDifferencePercentage) {
//        List<BigDecimal> 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<BigDecimal> splitData(double data, int n, double maxDeviationPercent) {
        // 计算每份的理论平均值
        double avg = data / n;
        // 计算允许的最大偏差
        double maxDeviation = avg * maxDeviationPercent / 100;
 
        // 创建一个存储每份数据的列表
        List<Double> 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<Kline> querySecKlineBySymbol(String symbol) {
 
        Tuple startAndEnd = getStartAndEnd();
 
        List<EtfSecKLine> list = etfSecKLineService.list(new QueryWrapper<EtfSecKLine>().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<Kline> querySecKlineBySymbolAllData(String symbol) {
        long currentTime = System.currentTimeMillis();
 
//        QueryWrapper<KlineConfig> queryWrapper = new QueryWrapper<>();
//        queryWrapper.eq("symbol", symbol).le("open_time_ts", currentTime).ge("close_time_ts", currentTime);
//        List<KlineConfig> 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<EtfSecKLine> list = etfSecKLineService.list(new QueryWrapper<EtfSecKLine>().eq("symbol", symbol).orderBy(true, true, "ts"));
 
        return etfSecKLineWrapper.toEntity(list);
    }
}