feat: 增加额外得分数据保存
This commit is contained in:
@@ -2,10 +2,13 @@ package com.lanyuanxiaoyao.leopard.core.entity;
|
|||||||
|
|
||||||
import com.lanyuanxiaoyao.leopard.core.Constants;
|
import com.lanyuanxiaoyao.leopard.core.Constants;
|
||||||
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
|
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
|
||||||
|
import jakarta.persistence.ElementCollection;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.EntityListeners;
|
import jakarta.persistence.EntityListeners;
|
||||||
|
import jakarta.persistence.JoinTable;
|
||||||
import jakarta.persistence.ManyToOne;
|
import jakarta.persistence.ManyToOne;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
|
import java.util.Map;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
@@ -28,5 +31,8 @@ public class StockScore extends SimpleEntity {
|
|||||||
private Stock stock;
|
private Stock stock;
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
private StockCollection collection;
|
private StockCollection collection;
|
||||||
|
@ElementCollection
|
||||||
|
@JoinTable(name = Constants.DATABASE_PREFIX + "stock_score_extra")
|
||||||
|
private Map<String, String> extra;
|
||||||
private Double score;
|
private Double score;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import cn.hutool.core.util.ArrayUtil;
|
|||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator;
|
import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator;
|
||||||
import com.lanyuanxiaoyao.leopard.core.entity.QStock;
|
import com.lanyuanxiaoyao.leopard.core.entity.QStock;
|
||||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
import com.lanyuanxiaoyao.leopard.core.helper.NumberHelper;
|
||||||
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -34,185 +36,202 @@ public class PyramidStockSelector implements StockSelector<PyramidStockSelector.
|
|||||||
public Set<Candidate> select(Request request) {
|
public Set<Candidate> select(Request request) {
|
||||||
// 选择至少有最近5年财报的股票
|
// 选择至少有最近5年财报的股票
|
||||||
// 有点奇怪,001400.SZ有近5年的财报但资料显示是2025年才上市的
|
// 有点奇怪,001400.SZ有近5年的财报但资料显示是2025年才上市的
|
||||||
var stocks = stockRepository.findAll(QStock.stock.listedDate.before(LocalDate.of(request.year(), 1, 1)));
|
return stockRepository.findAll(QStock.stock.listedDate.before(LocalDate.of(request.year(), 1, 1)))
|
||||||
var scores = stocks.stream().collect(Collectors.toMap(stock -> stock, code -> 0));
|
|
||||||
for (Stock stock : stocks) {
|
|
||||||
var recentIndicators = stock.getIndicators()
|
|
||||||
.stream()
|
|
||||||
.filter(indicator -> indicator.getYear() < request.year())
|
|
||||||
.sorted((a, b) -> b.getYear() - a.getYear())
|
|
||||||
.limit(5)
|
|
||||||
.toList();
|
|
||||||
if (recentIndicators.size() < 5) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
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, scores.get(stock) + 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, scores.get(stock) + 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, scores.get(stock) + 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, scores.get(stock) + cashScore);
|
|
||||||
|
|
||||||
if (ObjectUtil.isNotNull(latestIndicator.getDaysAccountsReceivableTurnover()) && latestIndicator.getDaysAccountsReceivableTurnover() <= 30) {
|
|
||||||
scores.put(stock, scores.get(stock) + 20);
|
|
||||||
}
|
|
||||||
if (ObjectUtil.isNotNull(latestIndicator.getDaysInventoryTurnover()) && latestIndicator.getDaysInventoryTurnover() <= 30) {
|
|
||||||
scores.put(stock, scores.get(stock) + 20);
|
|
||||||
}
|
|
||||||
if (ArrayUtil.isAllNotNull(latestIndicator.getDaysAccountsReceivableTurnover(), latestIndicator.getDaysInventoryTurnover())) {
|
|
||||||
if (latestIndicator.getDaysAccountsReceivableTurnover() + latestIndicator.getDaysInventoryTurnover() <= 40) {
|
|
||||||
scores.put(stock, scores.get(stock) + 20);
|
|
||||||
} else if (latestIndicator.getDaysAccountsReceivableTurnover() + latestIndicator.getDaysInventoryTurnover() <= 60) {
|
|
||||||
scores.put(stock, scores.get(stock) + 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, scores.get(stock) + 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, scores.get(stock) + 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, scores.get(stock) + 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, scores.get(stock) + cashAscendingScore);
|
|
||||||
}
|
|
||||||
return scores.entrySet()
|
|
||||||
.stream()
|
.stream()
|
||||||
.sorted((e1, e2) -> e2.getValue() - e1.getValue())
|
.map(stock -> {
|
||||||
.limit(request.limit())
|
var extra = new HashMap<String, String>();
|
||||||
.map(entry -> new Candidate(entry.getKey(), entry.getValue()))
|
var score = 0;
|
||||||
|
|
||||||
|
var recentIndicators = stock.getIndicators()
|
||||||
|
.stream()
|
||||||
|
.filter(indicator -> indicator.getYear() < request.year())
|
||||||
|
.sorted((a, b) -> b.getYear() - a.getYear())
|
||||||
|
.limit(5)
|
||||||
|
.toList();
|
||||||
|
if (recentIndicators.size() < 5) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
extra.put("平均ROE", NumberHelper.formatPriceDouble(averageRoe));
|
||||||
|
}
|
||||||
|
extra.put("平均ROE得分", NumberHelper.formatPriceDouble(roeScore));
|
||||||
|
score += 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;
|
||||||
|
}
|
||||||
|
extra.put("平均ROA", NumberHelper.formatPriceDouble(averageRoa));
|
||||||
|
}
|
||||||
|
extra.put("平均ROA得分", NumberHelper.formatPriceDouble(roaScore));
|
||||||
|
score += 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;
|
||||||
|
}
|
||||||
|
extra.put("平均净利润", NumberHelper.formatPriceDouble(averageNetProfit));
|
||||||
|
}
|
||||||
|
extra.put("平均净利润得分", NumberHelper.formatPriceDouble(netProfitScore));
|
||||||
|
score += 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;
|
||||||
|
}
|
||||||
|
extra.put("现金流得分", NumberHelper.formatPriceDouble(cashScore));
|
||||||
|
score += cashScore;
|
||||||
|
|
||||||
|
if (ObjectUtil.isNotNull(latestIndicator.getDaysAccountsReceivableTurnover()) && latestIndicator.getDaysAccountsReceivableTurnover() <= 30) {
|
||||||
|
extra.put("应收账款周转天数得分", "20");
|
||||||
|
score += 20;
|
||||||
|
}
|
||||||
|
if (ObjectUtil.isNotNull(latestIndicator.getDaysInventoryTurnover()) && latestIndicator.getDaysInventoryTurnover() <= 30) {
|
||||||
|
extra.put("存货周转天数得分", "20");
|
||||||
|
score += 20;
|
||||||
|
}
|
||||||
|
if (ArrayUtil.isAllNotNull(latestIndicator.getDaysAccountsReceivableTurnover(), latestIndicator.getDaysInventoryTurnover())) {
|
||||||
|
if (latestIndicator.getDaysAccountsReceivableTurnover() + latestIndicator.getDaysInventoryTurnover() <= 40) {
|
||||||
|
score += 20;
|
||||||
|
} else if (latestIndicator.getDaysAccountsReceivableTurnover() + latestIndicator.getDaysInventoryTurnover() <= 60) {
|
||||||
|
score += 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) {
|
||||||
|
extra.put("毛利率标准差得分", "50");
|
||||||
|
score += 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;
|
||||||
|
}
|
||||||
|
extra.put("安全边际比率", NumberHelper.formatPriceDouble(latestIndicator.getOperatingSafetyMarginRatio()));
|
||||||
|
}
|
||||||
|
extra.put("安全边际比率得分", NumberHelper.formatPriceDouble(operatingSafeMarginScore));
|
||||||
|
score += 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extra.put("近五年净利润得分", NumberHelper.formatPriceDouble(netProfitAscendingScore));
|
||||||
|
score += 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extra.put("近五年现金流得分", NumberHelper.formatPriceDouble(cashAscendingScore));
|
||||||
|
score += cashAscendingScore;
|
||||||
|
|
||||||
|
return new Candidate(stock, score, extra);
|
||||||
|
})
|
||||||
|
.filter(ObjectUtil::isNotNull)
|
||||||
|
.sorted(Comparator.comparingDouble(Candidate::score).reversed())
|
||||||
|
.limit(request.limit)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import java.util.Set;
|
|||||||
public interface StockSelector<T> {
|
public interface StockSelector<T> {
|
||||||
Set<Candidate> select(T request);
|
Set<Candidate> select(T request);
|
||||||
|
|
||||||
record Candidate(Stock stock, double score, Map<String, Object> extra) {
|
record Candidate(Stock stock, double score, Map<String, String> extra) {
|
||||||
public Candidate(Stock stock, double score) {
|
public Candidate(Stock stock, double score) {
|
||||||
this(stock, score, Map.of());
|
this(stock, score, Map.of());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ public class PyramidSelect extends TaskRunner {
|
|||||||
var score = new StockScore();
|
var score = new StockScore();
|
||||||
score.setStock(candidate.stock());
|
score.setStock(candidate.stock());
|
||||||
score.setScore(candidate.score());
|
score.setScore(candidate.score());
|
||||||
|
score.setExtra(candidate.extra());
|
||||||
score.setCollection(collection);
|
score.setCollection(collection);
|
||||||
return score;
|
return score;
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package com.lanyuanxiaoyao.leopard.server.entity;
|
|||||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||||
import com.lanyuanxiaoyao.leopard.core.entity.StockScore;
|
import com.lanyuanxiaoyao.leopard.core.entity.StockScore;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author lanyuanxiaoyao
|
* @author lanyuanxiaoyao
|
||||||
@@ -16,7 +18,8 @@ public record StockScoreVo(
|
|||||||
Stock.Market market,
|
Stock.Market market,
|
||||||
String industry,
|
String industry,
|
||||||
LocalDate listedDate,
|
LocalDate listedDate,
|
||||||
Double score
|
Double score,
|
||||||
|
String extra
|
||||||
) {
|
) {
|
||||||
public static StockScoreVo of(StockScore score) {
|
public static StockScoreVo of(StockScore score) {
|
||||||
return new StockScoreVo(
|
return new StockScoreVo(
|
||||||
@@ -27,7 +30,13 @@ public record StockScoreVo(
|
|||||||
score.getStock().getMarket(),
|
score.getStock().getMarket(),
|
||||||
score.getStock().getIndustry(),
|
score.getStock().getIndustry(),
|
||||||
score.getStock().getListedDate(),
|
score.getStock().getListedDate(),
|
||||||
score.getScore()
|
score.getScore(),
|
||||||
|
score.getExtra()
|
||||||
|
.entrySet()
|
||||||
|
.stream()
|
||||||
|
.sorted(Map.Entry.comparingByKey())
|
||||||
|
.map(entry -> entry.getKey() + ": " + entry.getValue())
|
||||||
|
.collect(Collectors.joining("<br>"))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,21 @@ function StockCollectionDetail() {
|
|||||||
undefined,
|
undefined,
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
className: 'white-space-pre',
|
||||||
name: 'score',
|
name: 'score',
|
||||||
label: '得分',
|
label: '得分',
|
||||||
width: 50,
|
width: 50,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '${score}',
|
||||||
|
popOver: {
|
||||||
|
trigger: 'click',
|
||||||
|
showIcon: false,
|
||||||
|
body: {
|
||||||
|
type: 'tpl',
|
||||||
|
tpl: '${extra|raw}',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user