From 2ce533d145c7d8d8e9c754596d6a5b1fa0e36bd8 Mon Sep 17 00:00:00 2001 From: Lxy Date: Fri, 30 Jan 2026 23:43:39 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=A2=9E=E5=8A=A0=E6=9C=BA=E6=9E=84?= =?UTF-8?q?=E6=8C=81=E4=BB=93=E5=AD=97=E6=AE=B5=EF=BC=9B=E5=88=86=E6=9E=90?= =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=90=BB=E5=90=88=E6=AD=A3=E7=A1=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/TStockDailyTrade.java | 30 ++ .../mapper/TStockDailyTradeMapper.java | 8 + .../impl/StockDailyTradeServiceImpl.java | 285 +++++++++++++++++- .../newstocksystem/StockDailyTradeMapper.xml | 26 +- .../newstocksystem/TStocksInTrendMapper.xml | 20 +- 5 files changed, 349 insertions(+), 20 deletions(-) diff --git a/newstock-system/src/main/java/com/ruoyi/newstocksystem/domain/TStockDailyTrade.java b/newstock-system/src/main/java/com/ruoyi/newstocksystem/domain/TStockDailyTrade.java index 833cbdb..63d1baa 100644 --- a/newstock-system/src/main/java/com/ruoyi/newstocksystem/domain/TStockDailyTrade.java +++ b/newstock-system/src/main/java/com/ruoyi/newstocksystem/domain/TStockDailyTrade.java @@ -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 + diff --git a/newstock-system/src/main/java/com/ruoyi/newstocksystem/mapper/TStockDailyTradeMapper.java b/newstock-system/src/main/java/com/ruoyi/newstocksystem/mapper/TStockDailyTradeMapper.java index c579e2b..dd8bbae 100644 --- a/newstock-system/src/main/java/com/ruoyi/newstocksystem/mapper/TStockDailyTradeMapper.java +++ b/newstock-system/src/main/java/com/ruoyi/newstocksystem/mapper/TStockDailyTradeMapper.java @@ -117,4 +117,12 @@ public interface TStockDailyTradeMapper * @return 存在则返回1,否则返回0 */ public int checkTradeDataExistsByDate(Date tradeDate); + + /** + * 查询指定日期之前的最近一个交易日 + * + * @param currentDate 当前日期 + * @return 上一个交易日 + */ + public Date selectPreviousTradeDate(Date currentDate); } diff --git a/newstock-system/src/main/java/com/ruoyi/newstocksystem/service/impl/StockDailyTradeServiceImpl.java b/newstock-system/src/main/java/com/ruoyi/newstocksystem/service/impl/StockDailyTradeServiceImpl.java index 861bd7c..a203e2a 100644 --- a/newstock-system/src/main/java/com/ruoyi/newstocksystem/service/impl/StockDailyTradeServiceImpl.java +++ b/newstock-system/src/main/java/com/ruoyi/newstocksystem/service/impl/StockDailyTradeServiceImpl.java @@ -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; @@ -49,6 +53,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 momentumTypes = Arrays.asList("10", "20", "60"); - // 可以在这里添加分析逻辑,比如: - // - 更新涨跌幅数据 - // - 计算动量指标 - // - 生成技术分析结果 - // - 其他业务逻辑 + for (String momentumType : momentumTypes) { + logger.info("开始分析动量类型: {}", momentumType); + + // 4. 获取当天交易日内,按照分析周期的涨跌幅倒序,获取涨幅前16%的股票 + List 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 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 industryStockCounts = getIndustryStockCounts(stockDailyTrade.getTradeDate(), new ArrayList<>(industryDistribution.keySet())); + logger.info("动量类型 {} 的行业个股数: {}", momentumType, industryStockCounts); + + // 7. 计算趋势值,方法是行业个股数乘以行业个股数除以行业趋势个股数,结果四舍五入 + List trendsList = new ArrayList<>(); + for (Map.Entry 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 previousTrends = trendsMapper.selectTTrendsList(queryTrend); + + // 构建行业到趋势数据的映射 + Map 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 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 getTop16PercentStocks(Date tradeDate, String momentumType) + { + try { + // 1. 查询tradeDate交易日的所有股票数据 + TStockDailyTrade query = new TStockDailyTrade(); + query.setTradeDate(tradeDate); + List 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 getIndustryStockCounts(Date tradeDate, List industries) + { + try { + Map industryStockCounts = new HashMap<>(); + + if (industries.isEmpty()) { + return industryStockCounts; + } + + // 查询tradeDate交易日的所有股票数据 + TStockDailyTrade query = new TStockDailyTrade(); + query.setTradeDate(tradeDate); + List 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()); + } + } } \ No newline at end of file diff --git a/newstock-system/src/main/resources/mapper/newstocksystem/StockDailyTradeMapper.xml b/newstock-system/src/main/resources/mapper/newstocksystem/StockDailyTradeMapper.xml index 99b8efa..824de5f 100644 --- a/newstock-system/src/main/resources/mapper/newstocksystem/StockDailyTradeMapper.xml +++ b/newstock-system/src/main/resources/mapper/newstocksystem/StockDailyTradeMapper.xml @@ -14,6 +14,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + @@ -31,6 +33,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 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 @@ -38,6 +41,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 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" volume, turnover, free_circulation_cap, + agencies_hold, + avg_volume20, is_limit_up, is_limit_down, momentum_10d, @@ -174,6 +180,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{volume}, #{turnover}, #{freeCirculationCap}, + #{agenciesHold}, + #{avgVolume20}, #{isLimitUp}, #{isLimitDown}, #{momentum10d}, @@ -194,6 +202,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" volume = #{volume}, turnover = #{turnover}, free_circulation_cap = #{freeCirculationCap}, + agencies_hold = #{agenciesHold}, + avg_volume20 = #{avgVolume20}, is_limit_up = #{isLimitUp}, is_limit_down = #{isLimitDown}, momentum_10d = #{momentum10d}, @@ -209,24 +219,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 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 (#{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()) 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 (#{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()) 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" + + diff --git a/newstock-system/src/main/resources/mapper/newstocksystem/TStocksInTrendMapper.xml b/newstock-system/src/main/resources/mapper/newstocksystem/TStocksInTrendMapper.xml index b9e52a6..24e0e68 100644 --- a/newstock-system/src/main/resources/mapper/newstocksystem/TStocksInTrendMapper.xml +++ b/newstock-system/src/main/resources/mapper/newstocksystem/TStocksInTrendMapper.xml @@ -15,7 +15,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - 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 @@ -38,7 +38,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" stock_code, trade_date, - rank, + `rank`, momentum_type, create_time, update_time, @@ -58,7 +58,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" stock_code = #{stockCode}, trade_date = #{tradeDate}, - rank = #{rank}, + `rank` = #{rank}, momentum_type = #{momentumType}, update_time = #{updateTime}, @@ -76,11 +76,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + insert into t_stocks_in_trend (stock_code, trade_date, `rank`, momentum_type, create_time, update_time) + values + + (#{item.stockCode}, #{item.tradeDate}, #{item.rank}, #{item.momentumType}, #{item.createTime}, #{item.updateTime}) + + + 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" ) ON DUPLICATE KEY UPDATE - rank = VALUES(rank), + `rank` = VALUES(`rank`), update_time = VALUES(update_time) \ No newline at end of file