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 910cb89..9f263e2 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 @@ -55,4 +55,20 @@ public class Daily extends SimpleEntity { @JoinColumn(nullable = false) @ToString.Exclude private Stock stock; + + public Double getHfqOpen() { + return open * factor; + } + + public Double getHfqClose() { + return close * factor; + } + + public Double getHfqHigh() { + return high * factor; + } + + public Double getHfqLow() { + return low * factor; + } } diff --git a/leopard-server/src/test/resources/tushare.http b/leopard-server/src/test/resources/tushare.http index f7be39c..2a0ac03 100644 --- a/leopard-server/src/test/resources/tushare.http +++ b/leopard-server/src/test/resources/tushare.http @@ -126,16 +126,15 @@ Content-Type: application/json "api_name": "stk_factor_pro", "token": "{{api_key}}", "params": { - "ts_code": "000001.SZ", - "trade_date": "20250225" + "ts_code": "000002.SZ", + "trade_date": "20250102" }, "fields": [ "ts_code", "trade_date", "close", "close_qfq", - "close_hfq", - "ema_hfq_5" + "close_hfq" ] } 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 75c6bfb..592354a 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 @@ -1,8 +1,9 @@ package com.lanyuanxiaoyao.leopard.strategy; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.ObjectUtil; -import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator; +import cn.hutool.core.lang.Tuple; +import com.lanyuanxiaoyao.leopard.core.entity.Daily; +import com.lanyuanxiaoyao.leopard.core.entity.Daily_; +import com.lanyuanxiaoyao.leopard.core.entity.QDaily; import com.lanyuanxiaoyao.leopard.core.entity.Stock; import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository; import com.lanyuanxiaoyao.leopard.core.repository.StockRepository; @@ -15,6 +16,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; +import org.springframework.data.domain.Sort; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @Slf4j @@ -33,185 +35,31 @@ public class StrategyApplication { @Transactional(rollbackOn = Throwable.class) @EventListener(ApplicationReadyEvent.class) public void test() { - var stocks = stockRepository.findAllByIndicatorsSizeGreaterThanEqual(5); - var stocksMap = stocks.stream().collect(Collectors.toMap(Stock::getCode, stock -> stock)); - var scores = stocks.stream().map(Stock::getCode).collect(Collectors.toMap(code -> code, code -> 0)); - for (Stock stock : stocks) { - var recentIndicators = stock.getIndicators() - .stream() - .sorted((a, b) -> b.getYear() - a.getYear()) - .limit(5) - .toList(); - var latestIndicator = recentIndicators.getFirst(); - - var roeScore = 0; - if (recentIndicators.stream().noneMatch(indicator -> indicator.getReturnOnEquity() == null || indicator.getReturnOnEquity() < 0)) { - var averageRoe = recentIndicators.stream() - .map(FinanceIndicator::getReturnOnEquity) - .map(item -> ObjectUtil.defaultIfNull(item, 0.0)) - .mapToDouble(Double::doubleValue) - .average() - .orElse(0.0); - if (averageRoe >= 35) { - roeScore = 550; - } else if (averageRoe >= 30) { - roeScore = 500; - } else if (averageRoe >= 25) { - roeScore = 450; - } else if (averageRoe >= 20) { - roeScore = 400; - } else if (averageRoe >= 15) { - roeScore = 350; - } else if (averageRoe >= 10) { - roeScore = 300; - } - } - scores.put(stock.getCode(), scores.get(stock.getCode()) + roeScore); - - var roaScore = 0; - if (recentIndicators.stream().noneMatch(indicator -> indicator.getReturnOnAssets() == null)) { - var averageRoa = recentIndicators.stream() - .map(FinanceIndicator::getReturnOnAssets) - .mapToDouble(Double::doubleValue) - .average() - .orElse(0.0); - if (averageRoa >= 15) { - roaScore = 100; - } else if (averageRoa >= 11) { - roaScore = 80; - } else if (averageRoa >= 7) { - roaScore = 50; - } - } - scores.put(stock.getCode(), scores.get(stock.getCode()) + roaScore); - - var netProfitScore = 0; - if (recentIndicators.stream().noneMatch(indicator -> indicator.getNetProfit() == null)) { - var averageNetProfit = recentIndicators.stream() - .map(FinanceIndicator::getNetProfit) - .mapToDouble(Double::doubleValue) - .average() - .orElse(0.0); - if (averageNetProfit >= 10000.0 * 10000000) { - netProfitScore = 150; - } else if (averageNetProfit >= 1000.0 * 10000000) { - netProfitScore = 100; - } - } - scores.put(stock.getCode(), scores.get(stock.getCode()) + netProfitScore); - - var cashScore = 0; - if ( - ArrayUtil.isAllNotNull(latestIndicator.getTotalAssetsTurnover(), latestIndicator.getCashAndCashEquivalentsToTotalAssetsRatio()) - && ( - latestIndicator.getTotalAssetsTurnover() > 0.8 && latestIndicator.getCashAndCashEquivalentsToTotalAssetsRatio() >= 0.1 - || latestIndicator.getTotalAssetsTurnover() <= 0.8 && latestIndicator.getCashAndCashEquivalentsToTotalAssetsRatio() >= 0.2 - ) - ) { - cashScore = 50; - } - scores.put(stock.getCode(), scores.get(stock.getCode()) + cashScore); - - if (ObjectUtil.isNotNull(latestIndicator.getDaysAccountsReceivableTurnover()) && latestIndicator.getDaysAccountsReceivableTurnover() <= 30) { - scores.put(stock.getCode(), scores.get(stock.getCode()) + 20); - } - if (ObjectUtil.isNotNull(latestIndicator.getDaysInventoryTurnover()) && latestIndicator.getDaysInventoryTurnover() <= 30) { - scores.put(stock.getCode(), scores.get(stock.getCode()) + 20); - } - if (ArrayUtil.isAllNotNull(latestIndicator.getDaysAccountsReceivableTurnover(), latestIndicator.getDaysInventoryTurnover())) { - if (latestIndicator.getDaysAccountsReceivableTurnover() + latestIndicator.getDaysInventoryTurnover() <= 40) { - scores.put(stock.getCode(), scores.get(stock.getCode()) + 20); - } else if (latestIndicator.getDaysAccountsReceivableTurnover() + latestIndicator.getDaysInventoryTurnover() <= 60) { - scores.put(stock.getCode(), scores.get(stock.getCode()) + 10); - } - } - - if (recentIndicators.stream().noneMatch(indicator -> indicator.getOperatingGrossProfitMargin() == null)) { - var stat = new DescriptiveStatistics(); - recentIndicators.stream() - .map(FinanceIndicator::getOperatingGrossProfitMargin) - .mapToDouble(Double::doubleValue) - .forEach(stat::addValue); - if (stat.getStandardDeviation() <= 0.3) { - scores.put(stock.getCode(), scores.get(stock.getCode()) + 50); - } - } - - var operatingSafeMarginScore = 0; - if (ObjectUtil.isNotNull(latestIndicator.getOperatingSafetyMarginRatio())) { - if (latestIndicator.getOperatingSafetyMarginRatio() >= 70) { - operatingSafeMarginScore = 50; - } else if (latestIndicator.getOperatingSafetyMarginRatio() >= 50) { - operatingSafeMarginScore = 30; - } else if (latestIndicator.getOperatingSafetyMarginRatio() >= 30) { - operatingSafeMarginScore = 10; - } - } - scores.put(stock.getCode(), scores.get(stock.getCode()) + operatingSafeMarginScore); - - var netProfitAscendingScore = 0; - if (recentIndicators.stream().noneMatch(indicator -> indicator.getNetProfit() == null)) { - if (recentIndicators.get(0).getNetProfit() > recentIndicators.get(1).getNetProfit()) { - netProfitAscendingScore += 30; - } else { - netProfitAscendingScore -= 30; - } - if (recentIndicators.get(1).getNetProfit() > recentIndicators.get(2).getNetProfit()) { - netProfitAscendingScore += 25; - } else { - netProfitAscendingScore -= 25; - } - - if (recentIndicators.get(2).getNetProfit() > recentIndicators.get(3).getNetProfit()) { - netProfitAscendingScore += 20; - } else { - netProfitAscendingScore -= 20; - } - - if (recentIndicators.get(3).getNetProfit() > recentIndicators.get(4).getNetProfit()) { - netProfitAscendingScore += 15; - } else { - netProfitAscendingScore -= 15; - } - } - scores.put(stock.getCode(), scores.get(stock.getCode()) + netProfitAscendingScore); - - var cashAscendingScore = 0; - if (recentIndicators.stream().noneMatch(indicator -> indicator.getCashAndCashEquivalents() == null)) { - if (recentIndicators.get(0).getCashAndCashEquivalents() > recentIndicators.get(1).getCashAndCashEquivalents()) { - cashAscendingScore += 30; - } else { - cashAscendingScore -= 30; - } - - if (recentIndicators.get(1).getCashAndCashEquivalents() > recentIndicators.get(2).getCashAndCashEquivalents()) { - cashAscendingScore += 25; - } else { - cashAscendingScore -= 25; - } - - if (recentIndicators.get(2).getCashAndCashEquivalents() > recentIndicators.get(3).getCashAndCashEquivalents()) { - cashAscendingScore += 20; - } else { - cashAscendingScore -= 20; - } - - if (recentIndicators.get(3).getCashAndCashEquivalents() > recentIndicators.get(4).getCashAndCashEquivalents()) { - cashAscendingScore += 15; - } else { - cashAscendingScore -= 15; - } - } - scores.put(stock.getCode(), scores.get(stock.getCode()) + cashAscendingScore); - } - scores.entrySet() + var dailies = dailyRepository.findAll( + QDaily.daily.tradeDate.year().eq(2025), + Sort.by(Daily_.TRADE_DATE) + ); + // log.info("OpenDate: {}, CloseDate: {}", dailies.getFirst().getTradeDate(), dailies.getLast().getTradeDate()); + // var closes = dailies.stream().map(Daily::getHfqClose).toList(); + // var statistics = new DescriptiveStatistics(); + // closes.forEach(statistics::addValue); + // log.info("Ascending: {}", (closes.getLast() - closes.getFirst()) * 100.0 / closes.getFirst()); + // log.info("STD: {}", statistics.getStandardDeviation()); + dailies.stream() + .collect(Collectors.groupingBy(Daily::getStock)) + .entrySet() .stream() - .sorted((e1, e2) -> e2.getValue() - e1.getValue()) - .map(entry -> new StockAndScore(stocksMap.get(entry.getKey()), entry.getValue())) - .limit(50) - .forEach(ss -> log.info("{} {}", ss.stock.getName(), ss.score)); - } - - public record StockAndScore(Stock stock, int score) { + .map(entry -> { + var stock = entry.getKey(); + var statistics = new DescriptiveStatistics(); + entry.getValue() + .stream() + .map(Daily::getHfqClose) + .forEach(statistics::addValue); + var std = statistics.getStandardDeviation(); + return new Tuple(stock, std); + }) + .sorted((t1, t2) -> Double.compare(t2.get(1), t1.get(1))) + .forEachOrdered(tuple -> log.info("Stock: {}, STD: {}", ((Stock) tuple.get(0)).getCode(), tuple.get(1))); } }