@@ -4,9 +4,11 @@ import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil ;
import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator ;
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 java.time.LocalDate ;
import java.util.Comparator ;
import java.util.HashMap ;
import java.util.Set ;
import java.util.stream.Collectors ;
import lombok.extern.slf4j.Slf4j ;
@@ -34,185 +36,202 @@ public class PyramidStockSelector implements StockSelector<PyramidStockSelector.
public Set < Candidate > select ( Request request ) {
// 选择至少有最近5年财报的股票
// 有点奇怪, 001400.SZ有近5年的财报但资料显示是2025年才上市的
var stocks = 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 ( )
return stockRepository . findAll ( QStock . stock . listedDate . before ( LocalDate . of ( request . year ( ) , 1 , 1 ) ) )
. stream ( )
. sorted ( ( e1 , e2 ) - > e2 . getValue ( ) - e1 . getValue ( ) )
. limit ( request . limit ( ) )
. map ( entry - > new Candidate ( entry . getKey ( ) , entry . getValue ( ) ) )
. map ( stock - > {
var extra = new HashMap < String , String > ( ) ;
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 ( ) ) ;
}