From 8c4e3baacbb6b6f1fa4b31b85d226596197fdd14 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Tue, 14 Oct 2025 23:26:35 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=B9=B4=E7=BA=BF?= =?UTF-8?q?=E5=92=8C=E5=91=A8=E7=BA=BF=E7=9A=84=E8=AE=A1=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/inspectionProfiles/Project_Default.xml | 15 ++ .../leopard/core/entity/Daily.java | 9 +- .../leopard/core/entity/Stock.java | 4 - .../leopard/core/entity/dto/Monthly.java | 18 ++ .../leopard/core/entity/dto/Weekly.java | 18 ++ .../leopard/core/entity/dto/Yearly.java | 17 ++ .../core/repository/DailyRepository.java | 4 +- .../leopard/core/service/StockService.java | 186 +++++++++++++++++- .../leopard/strategy/StrategyApplication.java | 17 +- 9 files changed, 267 insertions(+), 21 deletions(-) create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/entity/dto/Monthly.java create mode 100644 leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/entity/dto/Weekly.java create mode 100644 leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/entity/dto/Yearly.java diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..786a0ea --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/entity/Daily.java b/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/entity/Daily.java index 9a74566..0b87536 100644 --- a/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/entity/Daily.java +++ b/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/entity/Daily.java @@ -1,5 +1,6 @@ package com.lanyuanxiaoyao.leopard.core.entity; +import cn.hutool.core.util.ObjectUtil; import com.lanyuanxiaoyao.leopard.core.Constants; import com.lanyuanxiaoyao.service.template.entity.SimpleEntity; import jakarta.persistence.Column; @@ -58,18 +59,18 @@ public class Daily extends SimpleEntity { private Stock stock; public Double getHfqOpen() { - return open * factor; + return open * ObjectUtil.defaultIfNull(factor, 1.0); } public Double getHfqClose() { - return close * factor; + return close * ObjectUtil.defaultIfNull(factor, 1.0); } public Double getHfqHigh() { - return high * factor; + return high * ObjectUtil.defaultIfNull(factor, 1.0); } public Double getHfqLow() { - return low * factor; + return low * ObjectUtil.defaultIfNull(factor, 1.0); } } diff --git a/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/entity/Stock.java b/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/entity/Stock.java index 076b6e2..50a4707 100644 --- a/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/entity/Stock.java +++ b/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/entity/Stock.java @@ -61,10 +61,6 @@ public class Stock extends SimpleEntity { @ToString.Exclude private Set dailies; - @OneToMany(mappedBy = "stock", cascade = CascadeType.REMOVE) - @ToString.Exclude - private Set yearlies; - @OneToMany(mappedBy = "stock", cascade = CascadeType.REMOVE) @ToString.Exclude private Set indicators; diff --git a/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/entity/dto/Monthly.java b/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/entity/dto/Monthly.java new file mode 100644 index 0000000..1569ae6 --- /dev/null +++ b/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/entity/dto/Monthly.java @@ -0,0 +1,18 @@ +package com.lanyuanxiaoyao.leopard.core.entity.dto; + +import java.time.LocalDate; + +public record Monthly( + LocalDate tradeDate, + int year, + int month, + Double open, + Double high, + Double low, + Double close, + Double priceChangeAmount, + Double priceFluctuationRange, + Double volume, + Double turnover +) { +} diff --git a/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/entity/dto/Weekly.java b/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/entity/dto/Weekly.java new file mode 100644 index 0000000..ae8ee0c --- /dev/null +++ b/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/entity/dto/Weekly.java @@ -0,0 +1,18 @@ +package com.lanyuanxiaoyao.leopard.core.entity.dto; + +import java.time.LocalDate; + +public record Weekly( + LocalDate tradeDate, + int year, + int week, + Double open, + Double high, + Double low, + Double close, + Double priceChangeAmount, + Double priceFluctuationRange, + Double volume, + Double turnover +) { +} diff --git a/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/entity/dto/Yearly.java b/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/entity/dto/Yearly.java new file mode 100644 index 0000000..3458458 --- /dev/null +++ b/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/entity/dto/Yearly.java @@ -0,0 +1,17 @@ +package com.lanyuanxiaoyao.leopard.core.entity.dto; + +import java.time.LocalDate; + +public record Yearly( + LocalDate tradeDate, + int year, + Double open, + Double high, + Double low, + Double close, + Double priceChangeAmount, + Double priceFluctuationRange, + Double volume, + Double turnover +) { +} diff --git a/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/repository/DailyRepository.java b/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/repository/DailyRepository.java index b1fd0fa..d61a52f 100644 --- a/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/repository/DailyRepository.java +++ b/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/repository/DailyRepository.java @@ -2,12 +2,12 @@ package com.lanyuanxiaoyao.leopard.core.repository; import com.lanyuanxiaoyao.leopard.core.entity.Daily; import com.lanyuanxiaoyao.service.template.repository.SimpleRepository; +import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Predicate; import java.time.LocalDate; import java.util.List; import java.util.Optional; import java.util.Set; -import org.springframework.data.domain.Sort; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; @@ -32,5 +32,5 @@ public interface DailyRepository extends SimpleRepository { @EntityGraph(attributePaths = {"stock"}) @Override - List findAll(Predicate predicate, Sort sort); + List findAll(Predicate predicate, OrderSpecifier... orders); } diff --git a/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/service/StockService.java b/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/service/StockService.java index cbc72dd..712c575 100644 --- a/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/service/StockService.java +++ b/leopard-core/src/main/java/com/lanyuanxiaoyao/leopard/core/service/StockService.java @@ -1,22 +1,28 @@ package com.lanyuanxiaoyao.leopard.core.service; +import cn.hutool.core.util.ObjectUtil; import com.lanyuanxiaoyao.leopard.core.entity.Daily; -import com.lanyuanxiaoyao.leopard.core.entity.Daily_; import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator; -import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator_; import com.lanyuanxiaoyao.leopard.core.entity.QDaily; import com.lanyuanxiaoyao.leopard.core.entity.QFinanceIndicator; import com.lanyuanxiaoyao.leopard.core.entity.Stock; +import com.lanyuanxiaoyao.leopard.core.entity.dto.Monthly; +import com.lanyuanxiaoyao.leopard.core.entity.dto.Weekly; +import com.lanyuanxiaoyao.leopard.core.entity.dto.Yearly; import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository; import com.lanyuanxiaoyao.leopard.core.repository.FinanceIndicatorRepository; import com.lanyuanxiaoyao.leopard.core.repository.StockRepository; import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport; import java.time.LocalDate; +import java.time.temporal.ChronoField; +import java.time.temporal.WeekFields; +import java.util.Comparator; import java.util.List; import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.Cacheable; -import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; /** @@ -49,7 +55,7 @@ public class StockService extends SimpleServiceSupport { return financeIndicatorRepository.findAll( QFinanceIndicator.financeIndicator.stock.id.eq(stockId) .and(QFinanceIndicator.financeIndicator.year.between(current.minusYears(years).getYear(), current.getYear())), - Sort.by(Sort.Direction.ASC, FinanceIndicator_.YEAR) + QDaily.daily.tradeDate.asc() ); } @@ -59,7 +65,7 @@ public class StockService extends SimpleServiceSupport { return dailyRepository.findAll( QDaily.daily.stock.id.eq(stockId) .and(QDaily.daily.tradeDate.between(current.minusDays(days), current)), - Sort.by(Sort.Direction.ASC, Daily_.TRADE_DATE) + QDaily.daily.tradeDate.asc() ); } @@ -67,4 +73,174 @@ public class StockService extends SimpleServiceSupport { public Optional findDailyLatest(Long stockId) { return dailyRepository.findLatest(stockId); } + + @Cacheable(value = "long-cache", sync = true) + public List findYearlyRecent(Long stockId, int years) { + var current = LocalDate.now().withMonth(1).withDayOfMonth(1); + var start = current.minusYears(years).getYear(); + var end = current.getYear(); + return dailyRepository + .findAll( + QDaily.daily.stock.id.eq(stockId) + .and(QDaily.daily.tradeDate.year().gt(start)) + .and(QDaily.daily.tradeDate.year().loe(end)), + QDaily.daily.tradeDate.asc() + ) + .stream() + .collect(Collectors.groupingBy(daily -> daily.getTradeDate().getYear())) + .entrySet() + .stream() + .map(entry -> { + var year = entry.getKey(); + var dailies = entry.getValue(); + var open = dailies.getFirst().getHfqOpen(); + var close = dailies.getLast().getHfqClose(); + return new Yearly( + LocalDate.of(year, 1, 1), + year, + open, + maxFromDaily(dailies, Daily::getHfqHigh), + minFromDaily(dailies, Daily::getHfqLow), + close, + close - open, + (close - open) / open * 100, + sumFromDaily(dailies, Daily::getVolume), + sumFromDaily(dailies, Daily::getTurnover) + ); + }) + .sorted(Comparator.comparingInt(Yearly::year)) + .toList(); + } + + @Cacheable(value = "long-cache", sync = true) + public List findMonthlyRecent(Long stockId, int months) { + var end = LocalDate.now().withDayOfMonth(1); + var start = end.minusMonths(months); + return dailyRepository + .findAll( + QDaily.daily.stock.id.eq(stockId) + .and( + QDaily.daily.tradeDate.year().gt(start.getYear()) + .or( + QDaily.daily.tradeDate.year().eq(start.getYear()) + .and(QDaily.daily.tradeDate.month().gt(start.getMonthValue())) + ) + ) + .and( + QDaily.daily.tradeDate.year().lt(end.getYear()) + .or( + QDaily.daily.tradeDate.year().eq(end.getYear()) + .and(QDaily.daily.tradeDate.month().loe(end.getMonthValue())) + ) + ), + QDaily.daily.tradeDate.asc() + ) + .stream() + .collect(Collectors.groupingBy(daily -> new YearAndMonth(daily.getTradeDate().getYear(), daily.getTradeDate().getMonthValue()))) + .entrySet() + .stream() + .map(entry -> { + var yearAndMonth = entry.getKey(); + var dailies = entry.getValue(); + var open = dailies.getFirst().getHfqOpen(); + var close = dailies.getLast().getHfqClose(); + return new Monthly( + LocalDate.of(yearAndMonth.year, yearAndMonth.month, 1), + yearAndMonth.year, + yearAndMonth.month, + open, + maxFromDaily(dailies, Daily::getHfqHigh), + minFromDaily(dailies, Daily::getHfqLow), + close, + close - open, + (close - open) / open * 100, + sumFromDaily(dailies, Daily::getVolume), + sumFromDaily(dailies, Daily::getTurnover) + ); + }) + .sorted(Comparator.comparingInt(monthly -> monthly.year() * 100 + monthly.month())) + .toList(); + } + + @Cacheable(value = "long-cache", sync = true) + public List findWeeklyRecent(Long stockId, int weeks) { + var end = LocalDate.now().with(ChronoField.DAY_OF_WEEK, 1); + var start = end.minusWeeks(weeks); + return dailyRepository + .findAll( + QDaily.daily.stock.id.eq(stockId) + .and( + QDaily.daily.tradeDate.year().gt(start.getYear()) + .or( + QDaily.daily.tradeDate.year().eq(start.getYear()) + .and(QDaily.daily.tradeDate.week().gt(start.get(WeekFields.ISO.weekOfYear()))) + ) + ) + .and( + QDaily.daily.tradeDate.year().lt(end.getYear()) + .or( + QDaily.daily.tradeDate.year().eq(end.getYear()) + .and(QDaily.daily.tradeDate.month().loe(end.get(WeekFields.ISO.weekOfYear()))) + ) + ), + QDaily.daily.tradeDate.asc() + ) + .stream() + .collect(Collectors.groupingBy(daily -> new YearAndWeek(daily.getTradeDate().getYear(), daily.getTradeDate().get(WeekFields.ISO.weekOfYear())))) + .entrySet() + .stream() + .map(entry -> { + var yearAndWeek = entry.getKey(); + var dailies = entry.getValue(); + var open = dailies.getFirst().getHfqOpen(); + var close = dailies.getLast().getHfqClose(); + return new Weekly( + LocalDate.of(yearAndWeek.year, 1, 1).with(WeekFields.ISO.weekOfYear(), yearAndWeek.week), + yearAndWeek.year, + yearAndWeek.week, + open, + maxFromDaily(dailies, Daily::getHfqHigh), + minFromDaily(dailies, Daily::getHfqLow), + close, + close - open, + (close - open) / open * 100, + sumFromDaily(dailies, Daily::getVolume), + sumFromDaily(dailies, Daily::getTurnover) + ); + }) + .sorted(Comparator.comparingInt(weekly -> weekly.year() * 100 + weekly.week())) + .toList(); + } + + private Double maxFromDaily(List dailies, Function function) { + return dailies.stream() + .map(function) + .filter(ObjectUtil::isNotNull) + .mapToDouble(Double::doubleValue) + .max() + .orElse(0); + } + + private Double minFromDaily(List dailies, Function function) { + return dailies.stream() + .map(function) + .filter(ObjectUtil::isNotNull) + .mapToDouble(Double::doubleValue) + .min() + .orElse(0); + } + + private Double sumFromDaily(List dailies, Function function) { + return dailies.stream() + .map(function) + .filter(ObjectUtil::isNotNull) + .mapToDouble(Double::doubleValue) + .sum(); + } + + private record YearAndMonth(int year, int month) { + } + + private record YearAndWeek(int year, int week) { + } } diff --git a/leopard-strategy/src/main/java/com/lanyuanxiaoyao/leopard/strategy/StrategyApplication.java b/leopard-strategy/src/main/java/com/lanyuanxiaoyao/leopard/strategy/StrategyApplication.java index 2309b50..b362679 100644 --- a/leopard-strategy/src/main/java/com/lanyuanxiaoyao/leopard/strategy/StrategyApplication.java +++ b/leopard-strategy/src/main/java/com/lanyuanxiaoyao/leopard/strategy/StrategyApplication.java @@ -2,9 +2,11 @@ package com.lanyuanxiaoyao.leopard.strategy; import cn.hutool.core.util.StrUtil; import com.fasterxml.jackson.databind.ObjectMapper; -import com.lanyuanxiaoyao.leopard.core.entity.QDaily; +import com.lanyuanxiaoyao.leopard.core.entity.QStock; import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository; +import com.lanyuanxiaoyao.leopard.core.repository.StockRepository; import com.lanyuanxiaoyao.leopard.core.service.AssessmentService; +import com.lanyuanxiaoyao.leopard.core.service.StockService; import com.lanyuanxiaoyao.leopard.core.service.TuShareService; import com.lanyuanxiaoyao.leopard.core.service.selector.PyramidStockSelector; import jakarta.annotation.Resource; @@ -33,6 +35,10 @@ public class StrategyApplication { private TuShareService tuShareService; @Resource private DailyRepository dailyRepository; + @Resource + private StockRepository stockRepository; + @Resource + private StockService stockService; public static void main(String[] args) { SpringApplication.run(StrategyApplication.class, args); @@ -272,11 +278,10 @@ public class StrategyApplication { @Transactional(readOnly = true) @EventListener(ApplicationReadyEvent.class) public void test() { - var dailies = dailyRepository.findAll(QDaily.daily.factor.isNull(), QDaily.daily.tradeDate.asc()); - for (var daily : dailies) { - log.info("{} {} {}", daily.getStock().getCode(), daily.getStock().getName(), daily.getTradeDate()); - // var response = tuShareService.factorList(daily.getTradeDate(), daily.getStock().getCode()); - // var factor = response.data().items().getFirst().get(2); + var stock = stockRepository.findOne(QStock.stock.code.eq("000001.SZ")).orElseThrow(); + var weeklies = stockService.findWeeklyRecent(stock.getId(), 2); + for (var weekly : weeklies) { + log.info("{}", weekly); } } }