From a086f33541e15efcb5cacc7616d1a12161054511 Mon Sep 17 00:00:00 2001 From: vu-tran Date: Fri, 4 Jul 2025 17:30:59 +0700 Subject: [PATCH] update top gainer and loser --- .../infrastructure/job/InvestingTask.java | 2 +- .../stock/market/web/MoneyApiController.java | 91 +++++---------- .../market/web/service/MoneyApiService.java | 104 ++++++++++++++++++ 3 files changed, 135 insertions(+), 62 deletions(-) diff --git a/src/main/java/cn/stock/market/infrastructure/job/InvestingTask.java b/src/main/java/cn/stock/market/infrastructure/job/InvestingTask.java index c433910..d807405 100644 --- a/src/main/java/cn/stock/market/infrastructure/job/InvestingTask.java +++ b/src/main/java/cn/stock/market/infrastructure/job/InvestingTask.java @@ -310,7 +310,7 @@ public class InvestingTask { } @Scheduled(cron = "0 0 0/3 * * ?") - @PostConstruct +// @PostConstruct public void getBoerseNews(){ String url_request = "https://www.boerse-online.de"; diff --git a/src/main/java/cn/stock/market/web/MoneyApiController.java b/src/main/java/cn/stock/market/web/MoneyApiController.java index 7fb6fd6..0801986 100644 --- a/src/main/java/cn/stock/market/web/MoneyApiController.java +++ b/src/main/java/cn/stock/market/web/MoneyApiController.java @@ -538,7 +538,7 @@ public class MoneyApiController { @ApiOperation(value = "股票推荐TopGainer", httpMethod = "GET") @ApiImplicitParams({ - @ApiImplicitParam(name = "stockType", value = "BSE或者NSE"), + @ApiImplicitParam(name = "stockType", value = "BSE或者NSE或者germany"), }) @ApiResponses(value = { @ApiResponse(code = 200, message = "" + @@ -548,42 +548,41 @@ public class MoneyApiController { @ResponseBody @EncryptFilter(decryptRequest = false) - public List getTopGainer(@RequestParam String stockType) { + public List getTopGainer(@RequestParam(required = false, defaultValue = "germany") String stockType) { List moneyStockSuggestDTOS = null; // 尝试从缓存中获取结果 - moneyStockSuggestDTOS = gainerStockSuggestCache.getIfPresent(stockType); + // Use TradingView API for German stocks + List stockQuoteDataList = moneyApiService.getTopGainersFromTradingView(); + moneyStockSuggestDTOS = convertStockQuoteDataToMoneyStockSuggestDTO(stockQuoteDataList); + - if (moneyStockSuggestDTOS == null) { - // 缓存未命中,执行业务查询 - if (StringUtils.equals(stockType, "nse")) { - moneyStockSuggestDTOS = nseGainer(); - } else if (StringUtils.equals(stockType, "bse")) { - moneyStockSuggestDTOS = bseGainer(); - } - Map map = new HashMap<>(); - moneyStockSuggestDTOS = moneyStockSuggestDTOS.stream() - .filter(f -> StringUtils.isNotBlank(f.getStockName())) - .filter(i -> map.putIfAbsent(i.getStockName(), Boolean.TRUE) == null).collect(Collectors.toList()); - if (CollectionUtils.isNotEmpty(moneyStockSuggestDTOS)) { - List selfUlrList = moneyStockSuggestDTOS.stream().map(MoneyStockSuggestDTO::getStockName).collect(Collectors.toList()); - if (CollectionUtils.isNotEmpty(selfUlrList)) { - List all = moneyStockRepository.findAll(QMoneyStockPO.moneyStockPO.stockName.in(selfUlrList)); - if (CollectionUtils.isNotEmpty(all)) { - moneyStockSuggestDTOS.stream().filter(f -> all.stream().anyMatch(s -> s.getStockName().equals(f.getStockName()))) - .forEach(f -> f.setScId(all.stream().filter(s -> s.getStockName().equals(f.getStockName())).findFirst().orElse(null).getMoneyScId())); - } - } - gainerStockSuggestCache.put(stockType, moneyStockSuggestDTOS); - } - // 将结果放入缓存 - } return moneyStockSuggestDTOS; } + private List convertStockQuoteDataToMoneyStockSuggestDTO(List stockQuoteDataList) { + List result = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(stockQuoteDataList)) { + for (StockQuoteData stockQuoteData : stockQuoteDataList) { + MoneyStockSuggestDTO dto = new MoneyStockSuggestDTO(); + dto.setStockName(stockQuoteData.getName()); + dto.setStockType("XETRA"); + dto.setLastPrice(String.valueOf(stockQuoteData.getClose())); + dto.setChange(String.valueOf(stockQuoteData.getChange())); + dto.setChangePercent(String.valueOf(stockQuoteData.getPercent_change())); + dto.setHighPrice(String.valueOf(stockQuoteData.getHigh())); + dto.setLowPrice(String.valueOf(stockQuoteData.getLow())); + dto.setPrevClosePrice(String.valueOf(stockQuoteData.getPrevious_close())); + dto.setScId(String.valueOf(stockQuoteData.getSymbol())); + dto.setDispId(String.valueOf(stockQuoteData.getSymbol())); + result.add(dto); + } + } + return result; + } @ApiOperation(value = "股票推荐TopLoser", httpMethod = "GET") @ApiImplicitParams({ - @ApiImplicitParam(name = "stockType", value = "BSE或者NSE"), + @ApiImplicitParam(name = "stockType", value = "BSE或者NSE或者germany"), }) @ApiResponses(value = { @ApiResponse(code = 200, message = "" + @@ -595,39 +594,9 @@ public class MoneyApiController { public List getTopLoser(@RequestParam String stockType) { List moneyStockSuggestDTOS = null; - moneyStockSuggestDTOS = loserStockSuggestCache.getIfPresent(stockType); - if (null == moneyStockSuggestDTOS) { - if (StringUtils.equals(stockType, "nse")) { - moneyStockSuggestDTOS = nseTopLoser(); - } else if (StringUtils.equals(stockType, "bse")) { - moneyStockSuggestDTOS = bseTopLoser(); - } - Map map = new HashMap<>(); - moneyStockSuggestDTOS = moneyStockSuggestDTOS.stream() - .filter(f -> StringUtils.isNotBlank(f.getStockName())) - .filter(i -> map.putIfAbsent(i.getStockName(), Boolean.TRUE) == null).collect(Collectors.toList()); - if (CollectionUtils.isNotEmpty(moneyStockSuggestDTOS)) { - moneyStockSuggestDTOS.stream().forEach(f -> f.setDispId(extractLastSegment(f.getStockUrl()))); - List selfUlrList = moneyStockSuggestDTOS.stream().map(MoneyStockSuggestDTO::getStockName).collect(Collectors.toList()); - if (CollectionUtils.isNotEmpty(selfUlrList)) { - List all = moneyStockRepository.findAll(QMoneyStockPO.moneyStockPO.stockName.in(selfUlrList)); - if (CollectionUtils.isNotEmpty(all)) { - moneyStockSuggestDTOS.stream().filter(f -> all.stream().anyMatch(s -> s.getStockName().equals(f.getStockName()))) - .forEach(f -> f.setScId(all.stream().filter(s -> s.getStockName().equals(f.getStockName())).findFirst().orElse(null).getMoneyScId())); - } - List noScIdList = moneyStockSuggestDTOS.stream().filter(f -> StringUtils.isBlank(f.getScId())).collect(Collectors.toList()); - if (CollectionUtils.isNotEmpty(noScIdList)) { - List dispIdList = noScIdList.stream().map(MoneyStockSuggestDTO::getDispId).collect(Collectors.toList()); - List all1 = moneyStockRepository.findAll(QMoneyStockPO.moneyStockPO.selfDispId.in(dispIdList)); - if (CollectionUtils.isNotEmpty(all1)) { - moneyStockSuggestDTOS.stream().filter(f -> all1.stream().anyMatch(s -> s.getSelfDispId().equals(f.getDispId()))) - .forEach(f -> f.setScId(all.stream().filter(s -> s.getSelfDispId().equals(f.getDispId())).findFirst().orElse(null).getMoneyScId())); - } - } - } - loserStockSuggestCache.put(stockType, moneyStockSuggestDTOS); - } - } + List stockQuoteDataList = moneyApiService.getTopLosersFromTradingView(); + moneyStockSuggestDTOS = convertStockQuoteDataToMoneyStockSuggestDTO(stockQuoteDataList); + return moneyStockSuggestDTOS; } diff --git a/src/main/java/cn/stock/market/web/service/MoneyApiService.java b/src/main/java/cn/stock/market/web/service/MoneyApiService.java index 16fae98..f74efd2 100644 --- a/src/main/java/cn/stock/market/web/service/MoneyApiService.java +++ b/src/main/java/cn/stock/market/web/service/MoneyApiService.java @@ -4,6 +4,7 @@ import cn.qutaojing.common.utils.SpringUtils; import cn.stock.market.domain.basic.entity.Stock; import cn.stock.market.domain.basic.repository.StockRepository; import cn.stock.market.dto.*; +import cn.stock.market.infrastructure.db.po.QStockPO; import cn.stock.market.web.config.Config; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; @@ -154,4 +155,107 @@ public class MoneyApiService { return response; } + + public List getTopGainersFromTradingView() { + List result = getTopStocksFromTradingView("desc"); + // Sort by percent_change in descending order for gainers + return result.stream() + .sorted((a, b) -> Double.compare(b.getPercent_change(), a.getPercent_change())) + .collect(Collectors.toList()); + } + + public List getTopLosersFromTradingView() { + List result = getTopStocksFromTradingView("asc"); + // Sort by percent_change in ascending order for losers + return result.stream() + .sorted((a, b) -> Double.compare(a.getPercent_change(), b.getPercent_change())) + .collect(Collectors.toList()); + } + + public List getTopStocksFromTradingView(String sortOrder) { + String url = "https://scanner.tradingview.com/germany/scan"; + + // Prepare request body + String requestBody = "{\n" + + " \"columns\": [\n" + + " \"name\",\n" + + " \"description\",\n" + + " \"logoid\",\n" + + " \"update_mode\",\n" + + " \"type\",\n" + + " \"currency\",\n" + + " \"change\",\n" + + " \"volume\",\n" + + " \"exchange\"\n" + + " ],\n" + + " \"filter\": [\n" + + " {\n" + + " \"left\": \"is_primary\",\n" + + " \"operation\": \"equal\",\n" + + " \"right\": true\n" + + " }\n" + + " ],\n" + + " \"options\": {\n" + + " \"lang\": \"en\"\n" + + " },\n" + + " \"range\": [\n" + + " 0,\n" + + " 30\n" + + " ],\n" + + " \"sort\": {\n" + + " \"sortBy\": \"change\",\n" + + " \"sortOrder\": \"" + sortOrder + "\"\n" + + " },\n" + + " \"markets\": [\n" + + " \"germany\"\n" + + " ]\n" + + "}"; + + HttpHeaders headers = new HttpHeaders(); + headers.add("accept", "application/json"); + headers.add("accept-language", "en-US,en;q=0.9,vi;q=0.8,ug;q=0.7,fr;q=0.6"); + headers.add("origin", "https://www.tradingview.com"); + headers.add("referer", "https://www.tradingview.com/"); + headers.add("user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36"); + headers.add("Content-Type", "application/json"); + + HttpEntity entity = new HttpEntity<>(requestBody, headers); + RestTemplate restTemplate = new RestTemplate(); + + try { + ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class); + ObjectMapper mapper = new ObjectMapper(); + TradingViewResponse tradingViewResponse = mapper.readValue(response.getBody(), TradingViewResponse.class); + + if (tradingViewResponse != null && tradingViewResponse.getData() != null) { + // Extract symbols from TradingView response + List symbols = tradingViewResponse.getData().stream() + .filter(item -> item.getS() != null && item.getS().startsWith("XETR:")) + .map(item -> item.getS().substring(5)) // Remove "XETR:" prefix + .limit(30) + .collect(Collectors.toList()); + + if (!symbols.isEmpty()) { + // Find stocks in database + List stocks = stockRepository.findAll(QStockPO.stockPO.stockCode.in(symbols)); + + if (!stocks.isEmpty()) { + // Get real-time quotes for these stocks + List stockQuoteDatas = getStocksQuote(stocks); + for (StockQuoteData stockQuoteData : stockQuoteDatas) { + Stock name = stocks.stream().filter(e->e.getStockCode().equals(stockQuoteData.getSymbol())).findFirst().orElse(null); + if (name != null) { + stockQuoteData.setName(name.getStockName()); + } + } + return stockQuoteDatas; + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + + return new ArrayList<>(); + } }