|
|
|
|
@ -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());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|