From 59e4aa9e1a4321c8cef6c1e6f28c628b0785d63d Mon Sep 17 00:00:00 2001 From: quangnguyen202 Date: Thu, 19 Sep 2024 14:33:22 +0700 Subject: [PATCH 1/6] Add alternal source --- .../stock/market/utils/NseIndiaRequest.java | 82 ++++++++++++++++++- .../stock/market/web/MoneyApiController.java | 18 ++++ 2 files changed, 96 insertions(+), 4 deletions(-) diff --git a/src/main/java/cn/stock/market/utils/NseIndiaRequest.java b/src/main/java/cn/stock/market/utils/NseIndiaRequest.java index 326eb18..3fa4bbe 100644 --- a/src/main/java/cn/stock/market/utils/NseIndiaRequest.java +++ b/src/main/java/cn/stock/market/utils/NseIndiaRequest.java @@ -1,13 +1,15 @@ package cn.stock.market.utils; +import cn.stock.market.dto.StockHistoryRequest; +import cn.stock.market.dto.StockHistoryResponse; +import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import okhttp3.*; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.util.*; public class NseIndiaRequest { private static final String NSE_INDIA_URL = "https://www.nseindia.com"; @@ -93,4 +95,76 @@ public class NseIndiaRequest { throw new RuntimeException("Failed to fetch data", e); } } + + public static StockHistoryResponse stockKLineFromHttp(StockHistoryRequest stockHistoryRequest) { + initCookie(); + + SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy"); + String fromDate = sdf.format(new Date(stockHistoryRequest.getFrom() * 1000)); + String toDate = sdf.format(new Date(stockHistoryRequest.getTo() * 1000)); + + String url = String.format("%s/api/historical/cm/equity?symbol=%s&from=%s&to=%s", NSE_INDIA_URL, stockHistoryRequest.getSymbol(), fromDate, toDate); + + Request request = createRequest(url).newBuilder() + .addHeader("referer", NSE_INDIA_URL) + .addHeader("origin", NSE_INDIA_URL) + .build(); + + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("Request failed with code: " + response.code()); + } + + JSONObject jsonData = JSONObject.parseObject(response.body().string()); + JSONArray data =jsonData.getJSONArray("data"); + + StockHistoryResponse result = new StockHistoryResponse(); + List tList = new ArrayList<>(); + List oList = new ArrayList<>(); + List hList = new ArrayList<>(); + List lList = new ArrayList<>(); + List cList = new ArrayList<>(); + List vList = new ArrayList<>(); + + for (int i = 0; i < data.size(); i++) { + Long t, v; + Double o, h, l, c; + + try { + JSONObject jsonObject = data.getJSONObject(i); + + String timestampStr = jsonObject.getString("TIMESTAMP"); + Instant instant = Instant.parse(timestampStr); + t = instant.toEpochMilli() / 1000; + + o = jsonObject.getDouble("CH_OPENING_PRICE"); + c = jsonObject.getDouble("CH_CLOSING_PRICE"); + h = jsonObject.getDouble("CH_TRADE_HIGH_PRICE"); + l = jsonObject.getDouble("CH_TRADE_LOW_PRICE"); + v = jsonObject.getLong("CH_TOT_TRADED_VAL"); + + } catch (Exception e) { + continue; + } + + tList.add(t); + oList.add(o); + hList.add(h); + lList.add(l); + cList.add(c); + vList.add(v); + } + + result.setT(tList); + result.setO(oList); + result.setH(hList); + result.setL(lList); + result.setC(cList); + result.setV(vList); + + return result; + } catch (IOException e) { + throw new RuntimeException("Failed to fetch data", e); + } + } } diff --git a/src/main/java/cn/stock/market/web/MoneyApiController.java b/src/main/java/cn/stock/market/web/MoneyApiController.java index 399db83..9649e99 100644 --- a/src/main/java/cn/stock/market/web/MoneyApiController.java +++ b/src/main/java/cn/stock/market/web/MoneyApiController.java @@ -745,6 +745,24 @@ public class MoneyApiController { // API request successful, return the response return ResponseEntity.ok(response); } else { + if (!StringUtils.equals("H", resolution)) { + try { + MoneyStock moneyStock = moneyStockRepository.findOne((QMoneyStockPO.moneyStockPO.moneyScId.eq(symbol)) + .and(QMoneyStockPO.moneyStockPO.isLock.eq(0)) + .and(QMoneyStockPO.moneyStockPO.isShow.eq(0))) + .orElse(null); + + if (moneyStock != null && moneyStock.getNseIndiaId() != null && !moneyStock.getNseIndiaId().isEmpty()) { + request.setSymbol(moneyStock.getNseIndiaId()); + response = NseIndiaRequest.stockKLineFromHttp(request); + return ResponseEntity.ok(response); + } + } catch (Exception e) { + log.error("Failed to get data from nseindia.", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); + } + } + // All retries failed, return an error response log.error("Failed to get a successful response after {} retries.", maxRetries); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); From 3870400411d92793840a729e575ed0c6d436f578 Mon Sep 17 00:00:00 2001 From: gavin Date: Thu, 19 Sep 2024 15:59:12 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E5=A4=87=E7=94=A8=E6=95=B0=E6=8D=AE=E6=BA=90=E7=9A=84=E5=88=A4?= =?UTF-8?q?=E6=96=AD=E6=9D=A1=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/cn/stock/market/web/MoneyApiController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/cn/stock/market/web/MoneyApiController.java b/src/main/java/cn/stock/market/web/MoneyApiController.java index 9649e99..d70667b 100644 --- a/src/main/java/cn/stock/market/web/MoneyApiController.java +++ b/src/main/java/cn/stock/market/web/MoneyApiController.java @@ -740,7 +740,7 @@ public class MoneyApiController { } } - if (response != null) { + if (response != null && !response.getS().equals("error")) { setResponse(response, resolution); // API request successful, return the response return ResponseEntity.ok(response); From 549345719a70780e893a2de04b9800dfa92541c6 Mon Sep 17 00:00:00 2001 From: quangnguyen202 Date: Thu, 26 Sep 2024 16:51:41 +0700 Subject: [PATCH 3/6] Add alternate source --- .../infrastructure/db/po/MoneyStockPO.java | 4 + .../stock/market/utils/NseIndiaRequest.java | 133 ++++++++++-------- .../stock/market/web/MoneyApiController.java | 26 ++-- 3 files changed, 88 insertions(+), 75 deletions(-) diff --git a/src/main/java/cn/stock/market/infrastructure/db/po/MoneyStockPO.java b/src/main/java/cn/stock/market/infrastructure/db/po/MoneyStockPO.java index 8e73f8e..959bdbe 100644 --- a/src/main/java/cn/stock/market/infrastructure/db/po/MoneyStockPO.java +++ b/src/main/java/cn/stock/market/infrastructure/db/po/MoneyStockPO.java @@ -60,6 +60,10 @@ public class MoneyStockPO { * NSE India的id */ String nseIndiaId; + /** + * NSE India Chart的id */ + String nseIndiaChartId; + /** * 自有self_url */ String selfUrl; diff --git a/src/main/java/cn/stock/market/utils/NseIndiaRequest.java b/src/main/java/cn/stock/market/utils/NseIndiaRequest.java index 3fa4bbe..9868600 100644 --- a/src/main/java/cn/stock/market/utils/NseIndiaRequest.java +++ b/src/main/java/cn/stock/market/utils/NseIndiaRequest.java @@ -2,18 +2,21 @@ package cn.stock.market.utils; import cn.stock.market.dto.StockHistoryRequest; import cn.stock.market.dto.StockHistoryResponse; -import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; +import com.fasterxml.jackson.databind.ObjectMapper; import okhttp3.*; +import org.apache.commons.lang.StringUtils; import java.io.IOException; -import java.text.SimpleDateFormat; -import java.time.Instant; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class NseIndiaRequest { private static final String NSE_INDIA_URL = "https://www.nseindia.com"; + private static final String NSE_INDIA_CHART_URL = "https://charting.nseindia.com"; private static final OkHttpClient client; + private static final ObjectMapper objectMapper = new ObjectMapper(); static { client = new OkHttpClient.Builder() @@ -47,8 +50,8 @@ public class NseIndiaRequest { return request; } - private static void initCookie() { - Request request = createRequest(NSE_INDIA_URL); + private static void initCookie(String url) { + Request request = createRequest(url); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException("Failed to fetch initial cookies"); @@ -58,8 +61,35 @@ public class NseIndiaRequest { } } + private static Integer getCode(String symbol) { + Request request = createRequest(NSE_INDIA_CHART_URL + "//Charts/GetEQMasters").newBuilder() + .addHeader("referer", NSE_INDIA_CHART_URL) + .addHeader("origin", NSE_INDIA_CHART_URL) + .build(); + + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("Failed to get EQ code"); + } + + String result = response.body().string(); + + String regex = "(\\d+)\\|" + symbol + "\\|.*"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(result); + + if (matcher.find()) { + return Integer.valueOf(matcher.group(1)); + } + throw new IOException("No data found"); + + } catch (IOException e) { + throw new RuntimeException("Failed to get EQ code", e); + } + } + public static JSONObject stockByJYSFromHttp(String stockType, String symbol, String nseIndiaId) { - initCookie(); + initCookie(NSE_INDIA_URL); String url = NSE_INDIA_URL + "/api/quote-equity?symbol=" + nseIndiaId; Request request = createRequest(url).newBuilder() @@ -96,18 +126,44 @@ public class NseIndiaRequest { } } - public static StockHistoryResponse stockKLineFromHttp(StockHistoryRequest stockHistoryRequest) { - initCookie(); + public static StockHistoryResponse stockKLineFromHttp(StockHistoryRequest stockHistoryRequest, String resolution) { + initCookie(NSE_INDIA_CHART_URL); - SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy"); - String fromDate = sdf.format(new Date(stockHistoryRequest.getFrom() * 1000)); - String toDate = sdf.format(new Date(stockHistoryRequest.getTo() * 1000)); + Integer code = getCode(stockHistoryRequest.getSymbol()); - String url = String.format("%s/api/historical/cm/equity?symbol=%s&from=%s&to=%s", NSE_INDIA_URL, stockHistoryRequest.getSymbol(), fromDate, toDate); + int interval = 1; + if (StringUtils.equals("H", resolution)) { + resolution = "I"; + interval = 60; + } - Request request = createRequest(url).newBuilder() - .addHeader("referer", NSE_INDIA_URL) - .addHeader("origin", NSE_INDIA_URL) + Map body = new HashMap<>(); + body.put("chartPeriod", resolution); + body.put("chartStart", 0); + body.put("exch", "N"); + body.put("fromDate", 0); + body.put("instrType", "C"); + body.put("scripCode", code); + body.put("timeInterval", interval); + body.put("toDate", stockHistoryRequest.getTo() + 18000); + body.put("ulToken", code); + + String payload; + try { + payload = objectMapper.writeValueAsString(body); + } catch (Exception e) { + throw new RuntimeException("Failed to serialize body", e); + } + + RequestBody requestBody = RequestBody.create( + MediaType.get("application/json; charset=utf-8"), + payload + ); + + Request request = createRequest(NSE_INDIA_CHART_URL + "//Charts/symbolhistoricaldata/").newBuilder() + .addHeader("referer", NSE_INDIA_CHART_URL) + .addHeader("origin", NSE_INDIA_CHART_URL) + .post(requestBody) .build(); try (Response response = client.newCall(request).execute()) { @@ -115,52 +171,7 @@ public class NseIndiaRequest { throw new IOException("Request failed with code: " + response.code()); } - JSONObject jsonData = JSONObject.parseObject(response.body().string()); - JSONArray data =jsonData.getJSONArray("data"); - - StockHistoryResponse result = new StockHistoryResponse(); - List tList = new ArrayList<>(); - List oList = new ArrayList<>(); - List hList = new ArrayList<>(); - List lList = new ArrayList<>(); - List cList = new ArrayList<>(); - List vList = new ArrayList<>(); - - for (int i = 0; i < data.size(); i++) { - Long t, v; - Double o, h, l, c; - - try { - JSONObject jsonObject = data.getJSONObject(i); - - String timestampStr = jsonObject.getString("TIMESTAMP"); - Instant instant = Instant.parse(timestampStr); - t = instant.toEpochMilli() / 1000; - - o = jsonObject.getDouble("CH_OPENING_PRICE"); - c = jsonObject.getDouble("CH_CLOSING_PRICE"); - h = jsonObject.getDouble("CH_TRADE_HIGH_PRICE"); - l = jsonObject.getDouble("CH_TRADE_LOW_PRICE"); - v = jsonObject.getLong("CH_TOT_TRADED_VAL"); - - } catch (Exception e) { - continue; - } - - tList.add(t); - oList.add(o); - hList.add(h); - lList.add(l); - cList.add(c); - vList.add(v); - } - - result.setT(tList); - result.setO(oList); - result.setH(hList); - result.setL(lList); - result.setC(cList); - result.setV(vList); + StockHistoryResponse result = objectMapper.readValue(response.body().string(), StockHistoryResponse.class); return result; } catch (IOException e) { diff --git a/src/main/java/cn/stock/market/web/MoneyApiController.java b/src/main/java/cn/stock/market/web/MoneyApiController.java index d70667b..27547a7 100644 --- a/src/main/java/cn/stock/market/web/MoneyApiController.java +++ b/src/main/java/cn/stock/market/web/MoneyApiController.java @@ -745,22 +745,20 @@ public class MoneyApiController { // API request successful, return the response return ResponseEntity.ok(response); } else { - if (!StringUtils.equals("H", resolution)) { - try { - MoneyStock moneyStock = moneyStockRepository.findOne((QMoneyStockPO.moneyStockPO.moneyScId.eq(symbol)) - .and(QMoneyStockPO.moneyStockPO.isLock.eq(0)) - .and(QMoneyStockPO.moneyStockPO.isShow.eq(0))) - .orElse(null); + try { + MoneyStock moneyStock = moneyStockRepository.findOne((QMoneyStockPO.moneyStockPO.moneyScId.eq(symbol)) + .and(QMoneyStockPO.moneyStockPO.isLock.eq(0)) + .and(QMoneyStockPO.moneyStockPO.isShow.eq(0))) + .orElse(null); - if (moneyStock != null && moneyStock.getNseIndiaId() != null && !moneyStock.getNseIndiaId().isEmpty()) { - request.setSymbol(moneyStock.getNseIndiaId()); - response = NseIndiaRequest.stockKLineFromHttp(request); - return ResponseEntity.ok(response); - } - } catch (Exception e) { - log.error("Failed to get data from nseindia.", e.getMessage()); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); + if (moneyStock != null && moneyStock.getNseIndiaChartId() != null && !moneyStock.getNseIndiaChartId().isEmpty()) { + request.setSymbol(moneyStock.getNseIndiaChartId()); + response = NseIndiaRequest.stockKLineFromHttp(request, resolution); + return ResponseEntity.ok(response); } + } catch (Exception e) { + log.error("Failed to get data from nseindia.", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } // All retries failed, return an error response From ba29ab98c7b5b84dd39d9504518ae59cd0b4449e Mon Sep 17 00:00:00 2001 From: quangnguyen202 Date: Tue, 1 Oct 2024 14:06:24 +0700 Subject: [PATCH 4/6] Add api for optional stock --- .../db/po/QOptionalStockPO.java | 41 ++++++++++++++++++ .../basic/convert/OptionalStockConvert.java | 16 +++++++ .../domain/basic/entity/OptionalStock.java | 16 +++++++ .../repository/OptionalStockRepository.java | 32 ++++++++++++++ .../market/dto/OptionalStockResponse.java | 18 ++++++++ .../infrastructure/db/po/OptionalStockPO.java | 35 +++++++++++++++ .../db/repo/OptionalStockRepo.java | 7 +++ .../stock/market/web/MoneyApiController.java | 43 +++++++++++++++++++ 8 files changed, 208 insertions(+) create mode 100644 src/main/generated/cn/stock/market/infrastructure/db/po/QOptionalStockPO.java create mode 100644 src/main/java/cn/stock/market/domain/basic/convert/OptionalStockConvert.java create mode 100644 src/main/java/cn/stock/market/domain/basic/entity/OptionalStock.java create mode 100644 src/main/java/cn/stock/market/domain/basic/repository/OptionalStockRepository.java create mode 100644 src/main/java/cn/stock/market/dto/OptionalStockResponse.java create mode 100644 src/main/java/cn/stock/market/infrastructure/db/po/OptionalStockPO.java create mode 100644 src/main/java/cn/stock/market/infrastructure/db/repo/OptionalStockRepo.java diff --git a/src/main/generated/cn/stock/market/infrastructure/db/po/QOptionalStockPO.java b/src/main/generated/cn/stock/market/infrastructure/db/po/QOptionalStockPO.java new file mode 100644 index 0000000..7c210e9 --- /dev/null +++ b/src/main/generated/cn/stock/market/infrastructure/db/po/QOptionalStockPO.java @@ -0,0 +1,41 @@ +package cn.stock.market.infrastructure.db.po; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.Generated; +import com.querydsl.core.types.Path; + + +/** + * QOptionalStockPO is a Querydsl query type for OptionalStockPO + */ +@Generated("com.querydsl.codegen.EntitySerializer") +public class QOptionalStockPO extends EntityPathBase { + + private static final long serialVersionUID = 1161631810L; + + public static final QOptionalStockPO optionalStockPO = new QOptionalStockPO("optionalStockPO"); + + public final StringPath company = createString("company"); + + public final NumberPath id = createNumber("id", Integer.class); + + public final StringPath symbol = createString("symbol"); + + public QOptionalStockPO(String variable) { + super(OptionalStockPO.class, forVariable(variable)); + } + + public QOptionalStockPO(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QOptionalStockPO(PathMetadata metadata) { + super(OptionalStockPO.class, metadata); + } + +} + diff --git a/src/main/java/cn/stock/market/domain/basic/convert/OptionalStockConvert.java b/src/main/java/cn/stock/market/domain/basic/convert/OptionalStockConvert.java new file mode 100644 index 0000000..22445c8 --- /dev/null +++ b/src/main/java/cn/stock/market/domain/basic/convert/OptionalStockConvert.java @@ -0,0 +1,16 @@ +package cn.stock.market.domain.basic.convert; + +import cn.qutaojing.common.domain.convert.SimpleEntityPOConvert; +import cn.qutaojing.common.utils.SpringUtils; +import cn.stock.market.domain.basic.entity.OptionalStock; +import cn.stock.market.infrastructure.db.po.OptionalStockPO; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +@Component +@Lazy +public class OptionalStockConvert extends SimpleEntityPOConvert { + public static OptionalStockConvert of() { + return SpringUtils.getBean(OptionalStockConvert.class); + } +} diff --git a/src/main/java/cn/stock/market/domain/basic/entity/OptionalStock.java b/src/main/java/cn/stock/market/domain/basic/entity/OptionalStock.java new file mode 100644 index 0000000..6bb2483 --- /dev/null +++ b/src/main/java/cn/stock/market/domain/basic/entity/OptionalStock.java @@ -0,0 +1,16 @@ +package cn.stock.market.domain.basic.entity; + +import cn.stock.market.infrastructure.db.po.OptionalStockPO; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@NoArgsConstructor +@SuperBuilder +@EqualsAndHashCode( + callSuper = false +) +public class OptionalStock extends OptionalStockPO { +} diff --git a/src/main/java/cn/stock/market/domain/basic/repository/OptionalStockRepository.java b/src/main/java/cn/stock/market/domain/basic/repository/OptionalStockRepository.java new file mode 100644 index 0000000..5e315b4 --- /dev/null +++ b/src/main/java/cn/stock/market/domain/basic/repository/OptionalStockRepository.java @@ -0,0 +1,32 @@ +package cn.stock.market.domain.basic.repository; + +import cn.qutaojing.common.domain.convert.IEntityPOConvert; +import cn.qutaojing.common.domain.respostory.SimplePoConvertEntityRepository; +import cn.stock.market.domain.basic.convert.OptionalStockConvert; +import cn.stock.market.domain.basic.entity.OptionalStock; +import cn.stock.market.infrastructure.db.po.OptionalStockPO; +import cn.stock.market.infrastructure.db.repo.OptionalStockRepo; +import com.rp.spring.jpa.GenericJpaRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor( + onConstructor = @__(@Autowired) +) +public class OptionalStockRepository extends SimplePoConvertEntityRepository { + final OptionalStockRepo repo; + + final OptionalStockConvert convert; + + @Override + public GenericJpaRepository repo() { + return repo; + } + + @Override + public IEntityPOConvert convert() { + return convert; + } +} diff --git a/src/main/java/cn/stock/market/dto/OptionalStockResponse.java b/src/main/java/cn/stock/market/dto/OptionalStockResponse.java new file mode 100644 index 0000000..0a5cd89 --- /dev/null +++ b/src/main/java/cn/stock/market/dto/OptionalStockResponse.java @@ -0,0 +1,18 @@ +package cn.stock.market.dto; + +import lombok.Data; + +@Data +public class OptionalStockResponse { + private String message; + private Integer code; + private DataResponse data = new DataResponse(); + + @Data + public static class DataResponse { + private String symbol; + private String company; + private Double pricecurrent; + private Float pricepercentchange; + } +} diff --git a/src/main/java/cn/stock/market/infrastructure/db/po/OptionalStockPO.java b/src/main/java/cn/stock/market/infrastructure/db/po/OptionalStockPO.java new file mode 100644 index 0000000..6197e75 --- /dev/null +++ b/src/main/java/cn/stock/market/infrastructure/db/po/OptionalStockPO.java @@ -0,0 +1,35 @@ +package cn.stock.market.infrastructure.db.po; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +@SuperBuilder +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity +@DynamicInsert +@DynamicUpdate +@Table( + name = "optional_stock" +) +public class OptionalStockPO { + @Id + @GeneratedValue( + strategy = javax.persistence.GenerationType.IDENTITY + ) + Integer id; + + String symbol; + + String company; +} diff --git a/src/main/java/cn/stock/market/infrastructure/db/repo/OptionalStockRepo.java b/src/main/java/cn/stock/market/infrastructure/db/repo/OptionalStockRepo.java new file mode 100644 index 0000000..d61d258 --- /dev/null +++ b/src/main/java/cn/stock/market/infrastructure/db/repo/OptionalStockRepo.java @@ -0,0 +1,7 @@ +package cn.stock.market.infrastructure.db.repo; + +import cn.stock.market.infrastructure.db.po.OptionalStockPO; +import com.rp.spring.jpa.GenericJpaRepository; + +public interface OptionalStockRepo extends GenericJpaRepository { +} diff --git a/src/main/java/cn/stock/market/web/MoneyApiController.java b/src/main/java/cn/stock/market/web/MoneyApiController.java index 27547a7..c4dcee6 100644 --- a/src/main/java/cn/stock/market/web/MoneyApiController.java +++ b/src/main/java/cn/stock/market/web/MoneyApiController.java @@ -3,14 +3,19 @@ package cn.stock.market.web; import cn.hutool.core.date.DateUtil; import cn.stock.market.MoneyStockSuggestDTO; import cn.stock.market.domain.basic.entity.MoneyStock; +import cn.stock.market.domain.basic.entity.OptionalStock; import cn.stock.market.domain.basic.repository.MoneyStockRepository; +import cn.stock.market.domain.basic.repository.OptionalStockRepository; +import cn.stock.market.dto.OptionalStockResponse; import cn.stock.market.dto.StockHistoryRequest; import cn.stock.market.dto.StockHistoryResponse; import cn.stock.market.infrastructure.db.po.QMoneyStockPO; +import cn.stock.market.utils.HttpRequest; import cn.stock.market.utils.NseIndiaRequest; import cn.stock.market.utils.ServerResponse; import cn.stock.market.web.annotations.EncryptFilter; import com.alibaba.fastjson.JSONObject; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.collect.Lists; @@ -36,8 +41,12 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.web.util.UriUtils; import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -58,7 +67,14 @@ public class MoneyApiController { @Autowired private MoneyStockRepository moneyStockRepository; + @Autowired + private OptionalStockRepository optionalStockRepository; + + @Autowired + private ObjectMapper objectMapper; + private static final String EXTERNAL_API_URL = "https://priceapi.moneycontrol.com/techCharts/indianMarket/stock/history"; + private static final String OPTIONAL_STOCK_MONEYCONTROL_URL = "https://priceapi.moneycontrol.com/pricefeed/notapplicable/inidicesindia/"; @ApiOperation(value = "股票详情信息", httpMethod = "GET") @ApiImplicitParams({ @@ -768,6 +784,33 @@ public class MoneyApiController { // 返回响应 } + @GetMapping({"/market/api/market/stock/optional", "/api/market/stock/optional"}) + @ResponseBody + @EncryptFilter(decryptRequest = false) + public ResponseEntity> getOptionalStock() { + List optionalStocks = optionalStockRepository.findAll(); + List data = optionalStocks.stream().map(stock -> { + try { + String responseStr = HttpRequest.doGrabGet(OPTIONAL_STOCK_MONEYCONTROL_URL + URLEncoder.encode(stock.getSymbol(), "UTF-8")); + OptionalStockResponse response = objectMapper.readValue(responseStr, OptionalStockResponse.class); + if (response != null && response.getCode().equals(200)) { + return response.getData(); + } + } + catch (Exception e) { + log.error("Failed to get optional stock from moneycontrol for " + stock.getSymbol(), e); + } + + OptionalStockResponse response = new OptionalStockResponse(); + response.getData().setSymbol(stock.getSymbol()); + response.getData().setSymbol(stock.getCompany()); + + return response.getData(); + }).collect(Collectors.toList()); + + return ResponseEntity.ok(data); + } + private void setResponse(StockHistoryResponse response, String resolution) { if (!"ok".equals(response.getS())) { return; From 425754134215b9cdf2d19b8ee1cad0d530b46fab Mon Sep 17 00:00:00 2001 From: vpckiet Date: Wed, 9 Oct 2024 17:18:26 +0700 Subject: [PATCH 5/6] fix bug kline --- pom.xml | 6 ++ .../stock/market/dto/query/StockChartDto.java | 48 +++++++++++ .../stock/market/web/MoneyApiController.java | 80 +++++++++++++++---- 3 files changed, 117 insertions(+), 17 deletions(-) create mode 100644 src/main/java/cn/stock/market/dto/query/StockChartDto.java diff --git a/pom.xml b/pom.xml index 870d991..bd70d8f 100644 --- a/pom.xml +++ b/pom.xml @@ -185,6 +185,12 @@ 30.1-jre + + org.apache.httpcomponents + httpclient + 4.5.13 + + diff --git a/src/main/java/cn/stock/market/dto/query/StockChartDto.java b/src/main/java/cn/stock/market/dto/query/StockChartDto.java new file mode 100644 index 0000000..08f2263 --- /dev/null +++ b/src/main/java/cn/stock/market/dto/query/StockChartDto.java @@ -0,0 +1,48 @@ +package cn.stock.market.dto.query; + +import lombok.Data; + +import java.time.LocalDate; + +@Data +public class StockChartDto { + private Long timestamp; + private double open; + private double high; + private double low; + private double close; + private double volume; + + public StockChartDto(Long date, double open, double high, double low, double close, double volume) { + this.timestamp = date; + this.open = open; + this.high = high; + this.low = low; + this.close = close; + this.volume = volume; + } + + public double getVolume() { + return volume; + } + + public Long getTimestamp() { + return timestamp; + } + + public double getOpen() { + return open; + } + + public double getHigh() { + return high; + } + + public double getLow() { + return low; + } + + public double getClose() { + return close; + } +} diff --git a/src/main/java/cn/stock/market/web/MoneyApiController.java b/src/main/java/cn/stock/market/web/MoneyApiController.java index c4dcee6..6204e7f 100644 --- a/src/main/java/cn/stock/market/web/MoneyApiController.java +++ b/src/main/java/cn/stock/market/web/MoneyApiController.java @@ -9,12 +9,14 @@ import cn.stock.market.domain.basic.repository.OptionalStockRepository; import cn.stock.market.dto.OptionalStockResponse; import cn.stock.market.dto.StockHistoryRequest; import cn.stock.market.dto.StockHistoryResponse; +import cn.stock.market.dto.query.StockChartDto; import cn.stock.market.infrastructure.db.po.QMoneyStockPO; import cn.stock.market.utils.HttpRequest; import cn.stock.market.utils.NseIndiaRequest; import cn.stock.market.utils.ServerResponse; import cn.stock.market.web.annotations.EncryptFilter; import com.alibaba.fastjson.JSONObject; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; @@ -28,6 +30,12 @@ import io.swagger.annotations.ApiResponses; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -706,26 +714,26 @@ public class MoneyApiController { Long to = null; Long from = null; int countback = 5; - if (StringUtils.equals("H", resolution)) { - to = (long) (System.currentTimeMillis() / 1000); - from = to - (60 * 60); - countback = 60; - request.setResolution("1"); - } else if (StringUtils.equals("D", resolution)) { - to = (long) (System.currentTimeMillis() / 1000); - from = to - (24 * 60 * 60); - countback = 390; - request.setResolution("1"); + if(StringUtils.equals("H",resolution)){ + to = (long) (System.currentTimeMillis() / 1000); + from = to - (10 * 60 * 60 ); + countback = 328; + request.setResolution("60"); + }else if(StringUtils.equals("D",resolution)){ + to = (long) (System.currentTimeMillis() / 1000); + from = to - (2 * 30 * 24 * 60 * 60 ); + countback = 329; + request.setResolution("1D"); } else if (StringUtils.equals("W", resolution)) { to = (long) (System.currentTimeMillis() / 1000); from = to - (7 * 24 * 60 * 60); countback = 471; - request.setResolution("5"); + request.setResolution("1W"); } else if (StringUtils.equals("M", resolution)) { - to = (long) (System.currentTimeMillis() / 1000); - from = to - (35 * 24 * 60 * 60); - countback = 328; - request.setResolution("30"); + to = (long) (System.currentTimeMillis() / 1000); + from = to - (2 * 30 * 24 * 60 * 60 ); + countback = 329; + request.setResolution("1D"); } request.setFrom(from); @@ -740,7 +748,38 @@ public class MoneyApiController { while (response == null && retryCount < maxRetries) { try { - response = restTemplate.getForObject(apiUrl, StockHistoryResponse.class); + if (StringUtils.equals("M", resolution)) { + CloseableHttpClient client = HttpClients.createDefault(); + HttpGet req = new HttpGet(apiUrl); + HttpResponse resp = client.execute(req); + String jsonResponse = EntityUtils.toString(resp.getEntity(), "UTF-8"); + ObjectMapper mapper = new ObjectMapper(); + JsonNode rootNode = mapper.readTree(jsonResponse); + + JsonNode timeNode = rootNode.get("t"); + JsonNode lowNode = rootNode.get("l"); + JsonNode highNode = rootNode.get("h"); + JsonNode openNode = rootNode.get("o"); + JsonNode closeNode = rootNode.get("c"); + JsonNode volumeNote = rootNode.get("v"); + + List stocks = new ArrayList<>(); + for (int i = 0; i < timeNode.size(); i++) { + long timestamp = timeNode.get(i).asLong(); + double closePrice = closeNode.get(i).asDouble(); + double openPrice = openNode.get(i).asDouble(); + double volume = volumeNote.get(i).asDouble(); + double low = lowNode.get(i).asDouble(); + double high = highNode.get(i).asDouble(); + + stocks.add(new StockChartDto(timestamp, openPrice, closePrice, volume, low, high)); + } + + response = restTemplate.getForObject(apiUrl, StockHistoryResponse.class); + + } else { + response = restTemplate.getForObject(apiUrl, StockHistoryResponse.class); + } } catch (RestClientException e) { // Log the exception or perform any other error handling log.error("Error while making API request. Retrying... (Retry count: {})", retryCount + 1); @@ -753,11 +792,18 @@ public class MoneyApiController { } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } + } catch (ClientProtocolException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); } } if (response != null && !response.getS().equals("error")) { - setResponse(response, resolution); + if (StringUtils.equals("M", resolution)) { + + } +// setResponse(response, resolution); // API request successful, return the response return ResponseEntity.ok(response); } else { From c19277e39050a7789719db767df26bef240856cd Mon Sep 17 00:00:00 2001 From: vpckiet Date: Thu, 10 Oct 2024 10:50:01 +0700 Subject: [PATCH 6/6] update --- .../stock/market/web/MoneyApiController.java | 62 ++++++++++++++++--- 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/src/main/java/cn/stock/market/web/MoneyApiController.java b/src/main/java/cn/stock/market/web/MoneyApiController.java index 6204e7f..adfc339 100644 --- a/src/main/java/cn/stock/market/web/MoneyApiController.java +++ b/src/main/java/cn/stock/market/web/MoneyApiController.java @@ -55,6 +55,10 @@ import org.springframework.web.util.UriUtils; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.YearMonth; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -722,17 +726,17 @@ public class MoneyApiController { }else if(StringUtils.equals("D",resolution)){ to = (long) (System.currentTimeMillis() / 1000); from = to - (2 * 30 * 24 * 60 * 60 ); - countback = 329; + countback = 730; request.setResolution("1D"); } else if (StringUtils.equals("W", resolution)) { to = (long) (System.currentTimeMillis() / 1000); from = to - (7 * 24 * 60 * 60); - countback = 471; + countback = 730; request.setResolution("1W"); } else if (StringUtils.equals("M", resolution)) { to = (long) (System.currentTimeMillis() / 1000); - from = to - (2 * 30 * 24 * 60 * 60 ); - countback = 329; + from = to - (15 * 30 * 24 * 60 * 60); + countback = 730; request.setResolution("1D"); } @@ -772,10 +776,53 @@ public class MoneyApiController { double low = lowNode.get(i).asDouble(); double high = highNode.get(i).asDouble(); - stocks.add(new StockChartDto(timestamp, openPrice, closePrice, volume, low, high)); + stocks.add(new StockChartDto(timestamp, openPrice, high, low, closePrice, volume)); } - response = restTemplate.getForObject(apiUrl, StockHistoryResponse.class); + Map> groupedByMonth = stocks.stream() + .collect(Collectors.groupingBy( + sp -> Instant.ofEpochSecond(sp.getTimestamp()) + .atZone(ZoneId.systemDefault()) + .toLocalDate() + .format(DateTimeFormatter.ofPattern("yyyy-MM")), + LinkedHashMap::new, + Collectors.toList() + )); + + List timestamps = new ArrayList<>(); + List opens = new ArrayList<>(); + List closes = new ArrayList<>(); + List highs = new ArrayList<>(); + List lows = new ArrayList<>(); + List volumes = new ArrayList<>(); + + response = new StockHistoryResponse(); + groupedByMonth.forEach((month, prices) -> { + double open = prices.get(0).getOpen(); + double close = prices.get(prices.size() - 1).getClose(); + double high = prices.stream().mapToDouble(StockChartDto::getHigh).max().orElse(0); + double low = prices.stream().mapToDouble(StockChartDto::getLow).min().orElse(0); + double volume = prices.stream().mapToDouble(StockChartDto::getVolume).sum(); + + long timestamp = YearMonth.parse(month, DateTimeFormatter.ofPattern("yyyy-MM")).atDay(1).atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli() / 1000; + timestamps.add(timestamp); + opens.add(open); + closes.add(close); + highs.add(high); + lows.add(low); + volumes.add((long) volume); + + System.out.println("Month: " + month); + System.out.println("Open: " + open + ", Close: " + close + ", High: " + high + ", Low: " + low); + }); + + response.setS("ok"); + response.setT(timestamps); + response.setL(lows); + response.setH(highs); + response.setO(opens); + response.setC(closes); + response.setV(volumes); } else { response = restTemplate.getForObject(apiUrl, StockHistoryResponse.class); @@ -800,9 +847,6 @@ public class MoneyApiController { } if (response != null && !response.getS().equals("error")) { - if (StringUtils.equals("M", resolution)) { - - } // setResponse(response, resolution); // API request successful, return the response return ResponseEntity.ok(response);