fix: 增加机构持仓字段;分析基本吻合正确

dev_refactor_0120_qoder
Lxy 4 months ago
parent 79e5fe9417
commit 2ce533d145

@ -59,6 +59,14 @@ public class TStockDailyTrade extends BaseEntity
@Excel(name = "自由流通市值")
private BigDecimal freeCirculationCap;
/** 机构持仓(百分比) */
@Excel(name = "机构持仓")
private BigDecimal agenciesHold;
/** 20日区间平均成交量单位 */
@Excel(name = "20日区间平均成交量")
private Long avgVolume20;
/** 是否涨停1=是0=否) */
@Excel(name = "是否涨停", readConverterExp = "1=是,0=否")
private Integer isLimitUp;
@ -208,6 +216,26 @@ public class TStockDailyTrade extends BaseEntity
this.freeCirculationCap = freeCirculationCap;
}
public BigDecimal getAgenciesHold()
{
return agenciesHold;
}
public void setAgenciesHold(BigDecimal agenciesHold)
{
this.agenciesHold = agenciesHold;
}
public Long getAvgVolume20()
{
return avgVolume20;
}
public void setAvgVolume20(Long avgVolume20)
{
this.avgVolume20 = avgVolume20;
}
public Integer getIsLimitUp()
{
return isLimitUp;
@ -342,6 +370,8 @@ public class TStockDailyTrade extends BaseEntity
", volume=" + volume +
", turnover=" + turnover +
", freeCirculationCap=" + freeCirculationCap +
", agenciesHold=" + agenciesHold +
", avgVolume20=" + avgVolume20 +
", isLimitUp=" + isLimitUp +
", isLimitDown=" + isLimitDown +
", momentum10d=" + momentum10d +

@ -117,4 +117,12 @@ public interface TStockDailyTradeMapper
* @return 10
*/
public int checkTradeDataExistsByDate(Date tradeDate);
/**
*
*
* @param currentDate
* @return
*/
public Date selectPreviousTradeDate(Date currentDate);
}

@ -1,5 +1,6 @@
package com.ruoyi.newstocksystem.service.impl;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
@ -7,6 +8,12 @@ import java.util.HashSet;
import java.util.Set;
import java.util.HashMap;
import java.util.Map;
import java.util.Arrays;
import java.util.Calendar;
import java.util.stream.Collectors;
import com.ruoyi.newstocksystem.domain.*;
import com.ruoyi.newstocksystem.mapper.TStocksInTrendMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -14,9 +21,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.newstocksystem.domain.TIndustryBasic;
import com.ruoyi.newstocksystem.domain.TStockBasic;
import com.ruoyi.newstocksystem.domain.TStockDailyTrade;
import com.ruoyi.newstocksystem.mapper.TStockDailyTradeMapper;
import com.ruoyi.newstocksystem.mapper.TTrendsMapper;
import com.ruoyi.newstocksystem.service.IIndustryIndexService;
@ -50,6 +54,9 @@ public class StockDailyTradeServiceImpl implements IStockDailyTradeService
@Autowired
private ITIndustryBasicService industryBasicService;
@Autowired
private TStocksInTrendMapper tStocksInTrendMapper;
@Override
public TStockDailyTrade selectStockDailyTradeByCodeAndDate(String stockCode, Date tradeDate)
{
@ -438,14 +445,143 @@ public class StockDailyTradeServiceImpl implements IStockDailyTradeService
return "该日期已存在分析数据,无需重复分析";
}
// 3. 这里实现具体的分析逻辑参考stocksystem的analysis方法
// 目前先记录日志并返回成功
// 3. 实现具体的分析逻辑
// 分析周期为10日、20日、60日
List<String> momentumTypes = Arrays.asList("10", "20", "60");
for (String momentumType : momentumTypes) {
logger.info("开始分析动量类型: {}", momentumType);
// 4. 获取当天交易日内按照分析周期的涨跌幅倒序获取涨幅前16%的股票
List<TStockDailyTrade> topStocks = getTop16PercentStocks(stockDailyTrade.getTradeDate(), momentumType);
logger.info("动量类型 {} 的前16%股票数量: {}", momentumType, topStocks.size());
if (topStocks.isEmpty()) {
logger.info("动量类型 {} 没有足够的股票数据,跳过分析", momentumType);
continue;
}
// 4. 过滤出有机构持仓的股票
topStocks = topStocks.stream()
.filter(stock -> stock.getAgenciesHold() != null && stock.getAgenciesHold().compareTo(new BigDecimal(2)) == 1)
.collect(Collectors.toList());
logger.info("动量类型 {} 的前16%股票数量: {}", momentumType, topStocks.size());
if (topStocks.isEmpty()) {
logger.info("动量类型 {} 没有足够的股票数据,跳过分析", momentumType);
continue;
}
// 5. 根据2中的所有股票按照行业统计统计这16%股票所在行业分布及数量
Map<String, Integer> industryDistribution = new HashMap<>();
for (TStockDailyTrade stock : topStocks) {
String industry = stock.getIndustryIndexName();
if (industry != null && !industry.isEmpty()) {
industryDistribution.put(industry, industryDistribution.getOrDefault(industry, 0) + 1);
}
}
logger.info("动量类型 {} 的行业分布: {}", momentumType, industryDistribution);
// 6. 获取3中所有涉及行业的个股数
Map<String, Integer> industryStockCounts = getIndustryStockCounts(stockDailyTrade.getTradeDate(), new ArrayList<>(industryDistribution.keySet()));
logger.info("动量类型 {} 的行业个股数: {}", momentumType, industryStockCounts);
// 7. 计算趋势值,方法是行业个股数乘以行业个股数除以行业趋势个股数,结果四舍五入
List<TTrends> trendsList = new ArrayList<>();
for (Map.Entry<String, Integer> entry : industryDistribution.entrySet()) {
String industry = entry.getKey();
int trendStockCount = entry.getValue();
int totalStockCount = industryStockCounts.getOrDefault(industry, 0);
if (totalStockCount > 0 && trendStockCount > 0) {
// double trendValue = Math.round((double) totalStockCount * totalStockCount / trendStockCount);
BigDecimal c = new BigDecimal(trendStockCount);
BigDecimal allStocks = new BigDecimal(industryStockCounts.get(industry));
double trendValue = c.multiply(c).divide(allStocks, 2, BigDecimal.ROUND_HALF_UP).doubleValue();
TTrends trend = new TTrends();
trend.setTradeDate(stockDailyTrade.getTradeDate());
trend.setIndustryName(industry);
trend.setStocksCount((double) trendStockCount);
trend.setTrendValue(trendValue);
trend.setMomentumType(momentumType);
trend.setCreateTime(new Date());
trend.setUpdateTime(new Date());
trendsList.add(trend);
}
}
// 8. 将5中的行业趋势值按照大小进行排序并设置排名值
trendsList.sort((t1, t2) -> Double.compare(t2.getTrendValue(), t1.getTrendValue()));
for (int i = 0; i < trendsList.size(); i++) {
TTrends trend = trendsList.get(i);
trend.setRank(i + 1);
}
// 9. 根据当前的趋势板块,分别获取前一个交易日的趋势值,计算排名变化和趋势值变化
// 先获取前一个交易日
Date previousTradeDate = getPreviousTradeDate(stockDailyTrade.getTradeDate());
if (previousTradeDate != null) {
// 获取前一个交易日的趋势数据
TTrends queryTrend = new TTrends();
queryTrend.setTradeDate(previousTradeDate);
queryTrend.setMomentumType(momentumType);
List<TTrends> previousTrends = trendsMapper.selectTTrendsList(queryTrend);
// 构建行业到趋势数据的映射
Map<String, TTrends> previousTrendsMap = new HashMap<>();
for (TTrends t : previousTrends) {
previousTrendsMap.put(t.getIndustryName(), t);
}
// 计算排名变化和趋势值变化
for (TTrends currentTrend : trendsList) {
TTrends previousTrend = previousTrendsMap.get(currentTrend.getIndustryName());
if (previousTrend != null) {
// 排名变化 = 前一个交易日排名 - 当前排名
int rankChange = previousTrend.getRank() - currentTrend.getRank();
currentTrend.setRankChange(rankChange);
// 可以在这里添加分析逻辑,比如:
// - 更新涨跌幅数据
// - 计算动量指标
// - 生成技术分析结果
// - 其他业务逻辑
// 趋势值变化 = 当前趋势值 - 前一个交易日趋势值
double trendValueChange = currentTrend.getTrendValue() - previousTrend.getTrendValue();
currentTrend.setTrendValueChange(trendValueChange);
} else {
// 新出现的行业排名变化105-当前排名105为目前行业所有行业的排名上限
currentTrend.setRankChange(105-currentTrend.getRank());
currentTrend.setTrendValueChange(currentTrend.getTrendValue());
}
}
}
// 10. 将3中的数据存入t_stock_in_trend表中
List<TStocksInTrend> stocksInTrendList = new ArrayList<>();
for (int i = 0; i < topStocks.size(); i++) {
TStockDailyTrade stock = topStocks.get(i);
TStocksInTrend stocksInTrend = new TStocksInTrend();
stocksInTrend.setStockCode(stock.getStockCode());
stocksInTrend.setTradeDate(stockDailyTrade.getTradeDate());
stocksInTrend.setRank(i + 1);
stocksInTrend.setMomentumType(momentumType);
stocksInTrend.setCreateTime(new Date());
stocksInTrend.setUpdateTime(new Date());
stocksInTrendList.add(stocksInTrend);
}
// 批量插入趋势中的股票数据
if (!stocksInTrendList.isEmpty()) {
tStocksInTrendMapper.batchInsertTStocksInTrend(stocksInTrendList);
logger.info("动量类型 {} 批量插入趋势中的股票数据完成,数量: {}", momentumType, stocksInTrendList.size());
}
// 11. 将趋势值等数据存入t_trends中
if (!trendsList.isEmpty()) {
for (TTrends trend : trendsList) {
trendsMapper.insertTTrends(trend);
}
logger.info("动量类型 {} 批量插入趋势数据完成,数量: {}", momentumType, trendsList.size());
}
}
logger.info("股票数据分析完成,交易日期: {}", stockDailyTrade.getTradeDate());
return "分析完成";
@ -454,4 +590,133 @@ public class StockDailyTradeServiceImpl implements IStockDailyTradeService
throw new RuntimeException("分析失败: " + e.getMessage());
}
}
/**
* 16%
*
* @param tradeDate
* @param momentumType
* @return 16%
*/
private List<TStockDailyTrade> getTop16PercentStocks(Date tradeDate, String momentumType)
{
try {
// 1. 查询tradeDate交易日的所有股票数据
TStockDailyTrade query = new TStockDailyTrade();
query.setTradeDate(tradeDate);
List<TStockDailyTrade> allStocks = stockDailyTradeMapper.selectStockDailyTradeListWithBasic(query);
int totalCount = allStocks.size();
logger.info("交易日 {} 的股票数量: {}", tradeDate, totalCount);
if (allStocks.isEmpty()) {
logger.warn("交易日 {} 没有股票数据,返回空列表", tradeDate);
return new ArrayList<>();
}
// 2. 根据momentumType周期进行排序
allStocks.sort((s1, s2) -> {
BigDecimal momentum1 = getMomentumValue(s1, momentumType);
BigDecimal momentum2 = getMomentumValue(s2, momentumType);
// 倒序排序,涨幅大的在前
return momentum2.compareTo(momentum1);
});
// 3. 计算股票总数的16%,取整
// int topCount = (int) (totalCount * 0.16);
int topCount = 600;//暂时为了验证计算使用600固定数
// 确保至少返回1只股票
topCount = Math.max(1, topCount);
logger.info("股票总数: {}, 前16%数量: {}", totalCount, topCount);
// 4. 获取前16%的股票列表
if (topCount > 0) {
return allStocks.subList(0, Math.min(topCount, totalCount));
} else {
return new ArrayList<>();
}
} catch (Exception e) {
logger.error("获取前16%股票失败,交易日: {}, 动量类型: {}", tradeDate, momentumType, e);
throw new RuntimeException("获取前16%股票失败: " + e.getMessage());
}
}
/**
*
*
* @param stock
* @param momentumType
* @return
*/
private BigDecimal getMomentumValue(TStockDailyTrade stock, String momentumType)
{
// 根据不同的动量类型返回对应的动量值
switch (momentumType) {
case "10":
return stock.getMomentum10d() != null ? stock.getMomentum10d() : BigDecimal.ZERO;
case "20":
return stock.getMomentum20d() != null ? stock.getMomentum20d() : BigDecimal.ZERO;
case "60":
return stock.getMomentum60d() != null ? stock.getMomentum60d() : BigDecimal.ZERO;
default:
// 默认返回当日涨跌幅
return stock.getPriceChangeRate() != null ? stock.getPriceChangeRate() : BigDecimal.ZERO;
}
}
/**
*
*
* @param tradeDate
* @param industries
* @return
*/
private Map<String, Integer> getIndustryStockCounts(Date tradeDate, List<String> industries)
{
try {
Map<String, Integer> industryStockCounts = new HashMap<>();
if (industries.isEmpty()) {
return industryStockCounts;
}
// 查询tradeDate交易日的所有股票数据
TStockDailyTrade query = new TStockDailyTrade();
query.setTradeDate(tradeDate);
List<TStockDailyTrade> allStocks = stockDailyTradeMapper.selectStockDailyTradeListWithBasic(query);
// 按行业统计个股数量
for (TStockDailyTrade stock : allStocks) {
String industry = stock.getIndustryIndexName();
if (industry != null && !industry.isEmpty() && industries.contains(industry)) {
industryStockCounts.put(industry, industryStockCounts.getOrDefault(industry, 0) + 1);
}
}
logger.info("交易日 {} 的行业个股数统计完成,行业数量: {}", tradeDate, industryStockCounts.size());
return industryStockCounts;
} catch (Exception e) {
logger.error("获取行业个股数失败,交易日: {}", tradeDate, e);
throw new RuntimeException("获取行业个股数失败: " + e.getMessage());
}
}
/**
*
*
* @param currentDate
* @return
*/
private Date getPreviousTradeDate(Date currentDate)
{
try {
// 查询交易日期表,获取当前日期之前的最近一个交易日
Date previousTradeDate = stockDailyTradeMapper.selectPreviousTradeDate(currentDate);
logger.info("当前日期 {} 的前一个交易日: {}", currentDate, previousTradeDate);
return previousTradeDate;
} catch (Exception e) {
logger.error("获取前一个交易日失败,当前日期: {}", currentDate, e);
throw new RuntimeException("获取前一个交易日失败: " + e.getMessage());
}
}
}

@ -14,6 +14,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="volume" column="volume" />
<result property="turnover" column="turnover" />
<result property="freeCirculationCap" column="free_circulation_cap" />
<result property="agenciesHold" column="agencies_hold" />
<result property="avgVolume20" column="avg_volume20" />
<result property="isLimitUp" column="is_limit_up" />
<result property="isLimitDown" column="is_limit_down" />
<result property="momentum10d" column="momentum_10d" />
@ -31,6 +33,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<sql id="selectStockDailyTradeVo">
select stock_code, trade_date, open_price, close_price, high_price, low_price,
price_change_rate, volume, turnover, free_circulation_cap,
agencies_hold, avg_volume20,
is_limit_up, is_limit_down, momentum_10d, momentum_20d, momentum_60d, create_time
from t_stock_daily_trade
</sql>
@ -38,6 +41,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<sql id="selectStockDailyTradeWithBasicVo">
select t.stock_code, t.trade_date, t.open_price, t.close_price, t.high_price, t.low_price,
t.price_change_rate, t.volume, t.turnover, t.free_circulation_cap,
t.agencies_hold, t.avg_volume20,
t.is_limit_up, t.is_limit_down, t.momentum_10d, t.momentum_20d, t.momentum_60d, t.create_time,
b.stock_name, b.industry_index_code, b.industry_index_name
from t_stock_daily_trade t
@ -156,6 +160,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="volume != null">volume,</if>
<if test="turnover != null">turnover,</if>
<if test="freeCirculationCap != null">free_circulation_cap,</if>
<if test="agenciesHold != null">agencies_hold,</if>
<if test="avgVolume20 != null">avg_volume20,</if>
<if test="isLimitUp != null">is_limit_up,</if>
<if test="isLimitDown != null">is_limit_down,</if>
<if test="momentum10d != null">momentum_10d,</if>
@ -174,6 +180,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="volume != null">#{volume},</if>
<if test="turnover != null">#{turnover},</if>
<if test="freeCirculationCap != null">#{freeCirculationCap},</if>
<if test="agenciesHold != null">#{agenciesHold},</if>
<if test="avgVolume20 != null">#{avgVolume20},</if>
<if test="isLimitUp != null">#{isLimitUp},</if>
<if test="isLimitDown != null">#{isLimitDown},</if>
<if test="momentum10d != null">#{momentum10d},</if>
@ -194,6 +202,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="volume != null">volume = #{volume},</if>
<if test="turnover != null">turnover = #{turnover},</if>
<if test="freeCirculationCap != null">free_circulation_cap = #{freeCirculationCap},</if>
<if test="agenciesHold != null">agencies_hold = #{agenciesHold},</if>
<if test="avgVolume20 != null">avg_volume20 = #{avgVolume20},</if>
<if test="isLimitUp != null">is_limit_up = #{isLimitUp},</if>
<if test="isLimitDown != null">is_limit_down = #{isLimitDown},</if>
<if test="momentum10d != null">momentum_10d = #{momentum10d},</if>
@ -209,24 +219,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<insert id="batchInsertStockDailyTrade" parameterType="java.util.List">
insert into t_stock_daily_trade(stock_code, trade_date, open_price, close_price, high_price, low_price,
price_change_rate, volume, turnover, free_circulation_cap,
price_change_rate, volume, turnover, free_circulation_cap, agencies_hold, avg_volume20,
is_limit_up, is_limit_down, momentum_10d, momentum_20d, momentum_60d, create_time)
values
<foreach collection="list" item="item" separator=",">
(#{item.stockCode}, #{item.tradeDate}, #{item.openPrice}, #{item.closePrice}, #{item.highPrice}, #{item.lowPrice},
#{item.priceChangeRate}, #{item.volume}, #{item.turnover}, #{item.freeCirculationCap},
#{item.priceChangeRate}, #{item.volume}, #{item.turnover}, #{item.freeCirculationCap}, #{item.agenciesHold}, #{item.avgVolume20},
#{item.isLimitUp}, #{item.isLimitDown}, #{item.momentum10d}, #{item.momentum20d}, #{item.momentum60d}, NOW())
</foreach>
</insert>
<insert id="batchUpsertStockDailyTrade" parameterType="java.util.List">
insert into t_stock_daily_trade(stock_code, trade_date, open_price, close_price, high_price, low_price,
price_change_rate, volume, turnover, free_circulation_cap,
price_change_rate, volume, turnover, free_circulation_cap, agencies_hold, avg_volume20,
is_limit_up, is_limit_down, momentum_10d, momentum_20d, momentum_60d, create_time)
values
<foreach collection="list" item="item" separator=",">
(#{item.stockCode}, #{item.tradeDate}, #{item.openPrice}, #{item.closePrice}, #{item.highPrice}, #{item.lowPrice},
#{item.priceChangeRate}, #{item.volume}, #{item.turnover}, #{item.freeCirculationCap},
#{item.priceChangeRate}, #{item.volume}, #{item.turnover}, #{item.freeCirculationCap}, #{item.agenciesHold}, #{item.avgVolume20},
#{item.isLimitUp}, #{item.isLimitDown}, #{item.momentum10d}, #{item.momentum20d}, #{item.momentum60d}, NOW())
</foreach>
ON DUPLICATE KEY UPDATE
@ -238,6 +248,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
volume = VALUES(volume),
turnover = VALUES(turnover),
free_circulation_cap = VALUES(free_circulation_cap),
agencies_hold = VALUES(agencies_hold),
avg_volume20 = VALUES(avg_volume20),
is_limit_up = VALUES(is_limit_up),
is_limit_down = VALUES(is_limit_down),
momentum_10d = VALUES(momentum_10d),
@ -248,4 +260,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="checkTradeDataExistsByDate" parameterType="Date" resultType="int">
select count(1) from t_stock_daily_trade where trade_date = #{tradeDate}
</select>
<select id="selectPreviousTradeDate" parameterType="Date" resultType="Date">
select max(trade_date) as trade_date
from t_stock_daily_trade
where trade_date &lt; #{currentDate}
</select>
</mapper>

@ -15,7 +15,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectTStocksInTrendVo">
select id, stock_code, trade_date, rank, momentum_type, create_time, update_time from t_stocks_in_trend
select id, stock_code, trade_date, `rank`, momentum_type, create_time, update_time from t_stocks_in_trend
</sql>
<select id="selectTStocksInTrendById" parameterType="Long" resultMap="TStocksInTrendResult">
@ -28,7 +28,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<where>
<if test="stockCode != null and stockCode != ''">and stock_code = #{stockCode}</if>
<if test="tradeDate != null ">and trade_date = #{tradeDate}</if>
<if test="rank != null ">and rank = #{rank}</if>
<if test="rank != null ">and `rank` = #{rank}</if>
<if test="momentumType != null and momentumType != ''">and momentum_type = #{momentumType}</if>
</where>
</select>
@ -38,7 +38,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="stockCode != null and stockCode != ''">stock_code,</if>
<if test="tradeDate != null">trade_date,</if>
<if test="rank != null">rank,</if>
<if test="rank != null">`rank`,</if>
<if test="momentumType != null and momentumType != ''">momentum_type,</if>
<if test="createTime != null">create_time,</if>
<if test="updateTime != null">update_time,</if>
@ -58,7 +58,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<trim prefix="SET" suffixOverrides=",">
<if test="stockCode != null and stockCode != ''">stock_code = #{stockCode},</if>
<if test="tradeDate != null">trade_date = #{tradeDate},</if>
<if test="rank != null">rank = #{rank},</if>
<if test="rank != null">`rank` = #{rank},</if>
<if test="momentumType != null and momentumType != ''">momentum_type = #{momentumType},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
</trim>
@ -76,11 +76,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</foreach>
</delete>
<insert id="batchInsertTStocksInTrend" parameterType="java.util.List">
insert into t_stocks_in_trend (stock_code, trade_date, `rank`, momentum_type, create_time, update_time)
values
<foreach collection="list" item="item" separator=",">
(#{item.stockCode}, #{item.tradeDate}, #{item.rank}, #{item.momentumType}, #{item.createTime}, #{item.updateTime})
</foreach>
</insert>
<insert id="batchUpsert" parameterType="java.util.List">
INSERT INTO t_stocks_in_trend (
stock_code,
trade_date,
rank,
`rank`,
momentum_type,
create_time,
update_time
@ -96,7 +104,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
)
</foreach>
ON DUPLICATE KEY UPDATE
rank = VALUES(rank),
`rank` = VALUES(`rank`),
update_time = VALUES(update_time)
</insert>
</mapper>
Loading…
Cancel
Save