feat: 增加金字塔选股策略
This commit is contained in:
@@ -5,6 +5,7 @@ import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.EntityGraph;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
@@ -24,4 +25,8 @@ public interface StockRepository extends SimpleRepository<Stock> {
|
||||
@Modifying
|
||||
@Transactional(rollbackOn = Throwable.class)
|
||||
void deleteAllByCodeIn(Collection<String> code);
|
||||
|
||||
@EntityGraph(attributePaths = {"indicators"})
|
||||
@Query("from Stock stock where size(stock.indicators) >= ?1")
|
||||
List<Stock> findAllByIndicatorsSizeGreaterThanEqual(int count);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package com.lanyuanxiaoyao.leopard.strategy;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
@@ -27,5 +32,114 @@ public class StrategyApplication {
|
||||
@Transactional(rollbackOn = Throwable.class)
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void test() {
|
||||
var stocks = stockRepository.findAllByIndicatorsSizeGreaterThanEqual(6);
|
||||
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.get(0);
|
||||
|
||||
int 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);
|
||||
|
||||
int roaScore = 0;
|
||||
if (recentIndicators.stream().noneMatch(indicator -> indicator.getReturnOnAssets() == null)) {
|
||||
var averageRoa = recentIndicators.stream()
|
||||
.map(FinanceIndicator::getReturnOnAssets)
|
||||
.map(item -> ObjectUtil.defaultIfNull(item, 0.0))
|
||||
.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);
|
||||
|
||||
int netProfitScore = 0;
|
||||
if (recentIndicators.stream().noneMatch(indicator -> indicator.getNetProfit() == null)) {
|
||||
var averageNetProfit = recentIndicators.stream()
|
||||
.map(FinanceIndicator::getNetProfit)
|
||||
.map(item -> ObjectUtil.defaultIfNull(item, 0.0))
|
||||
.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);
|
||||
|
||||
int cashScore = 0;
|
||||
if (ObjectUtil.isNotNull(latestIndicator.getTotalAssetsTurnover()) && ObjectUtil.isNotNull(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 (ObjectUtil.isNotNull(latestIndicator.getDaysAccountsReceivableTurnover()) && ObjectUtil.isNotNull(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
scores.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) {
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user