Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .gradle/8.14.3/executionHistory/executionHistory.bin
Binary file not shown.
Binary file modified .gradle/8.14.3/executionHistory/executionHistory.lock
Binary file not shown.
Binary file modified .gradle/8.14.3/fileHashes/fileHashes.bin
Binary file not shown.
Binary file modified .gradle/8.14.3/fileHashes/fileHashes.lock
Binary file not shown.
Binary file modified .gradle/8.14.3/fileHashes/resourceHashesCache.bin
Binary file not shown.
Binary file modified .gradle/buildOutputCleanup/buildOutputCleanup.lock
Binary file not shown.
Binary file modified .gradle/file-system.probe
Binary file not shown.
2 changes: 1 addition & 1 deletion build/reports/problems/problems-report.html
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,7 @@
<script type="text/javascript">
function configurationCacheProblems() { return (
// begin-report-data
{"diagnostics":[{"locations":[{}],"problem":[{"text":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This is scheduled to be removed in Gradle 10.0."}],"contextualLabel":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated.","documentationLink":"https://docs.gradle.org/8.14.3/userguide/upgrading_version_8.html#groovy_space_assignment_syntax","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"properties-should-be-assigned-using-the-propname-value-syntax-setting-a-property-via-the-gradle-generated-propname-value-or-propname-value-syntax-in-groovy-dsl","displayName":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"solutions":[[{"text":"Use assignment ('url = <value>') instead."}]]}],"problemsReport":{"totalProblemCount":1,"buildName":"server","requestedTasks":":test","documentationLink":"https://docs.gradle.org/8.14.3/userguide/reporting_problems.html","documentationLinkCaption":"Problem report","summaries":[]}}
{"diagnostics":[{"locations":[{}],"problem":[{"text":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This is scheduled to be removed in Gradle 10.0."}],"contextualLabel":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated.","documentationLink":"https://docs.gradle.org/8.14.3/userguide/upgrading_version_8.html#groovy_space_assignment_syntax","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"properties-should-be-assigned-using-the-propname-value-syntax-setting-a-property-via-the-gradle-generated-propname-value-or-propname-value-syntax-in-groovy-dsl","displayName":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"solutions":[[{"text":"Use assignment ('url = <value>') instead."}]]}],"problemsReport":{"totalProblemCount":1,"buildName":"server","requestedTasks":":classes","documentationLink":"https://docs.gradle.org/8.14.3/userguide/reporting_problems.html","documentationLinkCaption":"Problem report","summaries":[]}}
// end-report-data
);}
</script>
Expand Down
Binary file modified build/tmp/compileJava/previous-compilation-data.bin
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,4 @@ public ApiResponse<String> updatePeriodicStockData(
stockService.updatePeriodicStockData(startDate, endDate);
return ApiResponse.onSuccess("주가 데이터 업데이트 성공");
}

@GetMapping("/update/historical-data")
@Operation(
summary = "과거 주식 데이터 전체 업데이트",
description = "과거 주식 데이터를 처음부터 현재까지 모두 업데이트 합니다. (1일 이상 소요)"
)
public ApiResponse<String> updateHistoricalStockData() {
stockService.updateHistoricalStockData();
return ApiResponse.onSuccess("과거 주가 데이터 전체 업데이트 성공");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
@Builder
public class StockPriceResponse {
private LocalDate baseDate;
private Integer openPrice;
private Integer highPrice;
private Integer lowPrice;
private Integer closePrice;
private Integer changeAmount;
private BigDecimal openPrice;
private BigDecimal highPrice;
private BigDecimal lowPrice;
private BigDecimal closePrice;
private BigDecimal changeAmount;
private BigDecimal changeRate;

public static StockPriceResponse of(StockCurrentPrice stockCurrentPrice) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
public class IndexDataScheduler {
private final IndexDataService indexDataService;

@Scheduled(cron = "0 0/5 9-18 * * MON-FRI", zone = "Asia/Seoul") // todo: 공휴일/휴장일 스케쥴러 처리 필요.
public void updateKospi() {
indexDataService.updateCurrentIndexData(MarketType.KOSPI);
log.info("[Scheduler] 코스피 데이터 업데이트 완료");
}

@Scheduled(cron = "0 0/5 9-18 * * MON-FRI", zone = "Asia/Seoul") // todo: 공휴일/휴장일 스케쥴러 처리 필요.
public void updateKosdaq() {
indexDataService.updateCurrentIndexData(MarketType.KOSPI);
log.info("[Scheduler] 코스닥 데이터 업데이트 완료");
}
// @Scheduled(cron = "0 0/5 9-18 * * MON-FRI", zone = "Asia/Seoul") // todo: 공휴일/휴장일 스케쥴러 처리 필요.
// public void updateKospi() {
// indexDataService.updateCurrentIndexData(MarketType.KOSPI);
// log.info("[Scheduler] 코스피 데이터 업데이트 완료");
// }
//
// @Scheduled(cron = "0 0/5 9-18 * * MON-FRI", zone = "Asia/Seoul") // todo: 공휴일/휴장일 스케쥴러 처리 필요.
// public void updateKosdaq() {
// indexDataService.updateCurrentIndexData(MarketType.KOSPI);
// log.info("[Scheduler] 코스닥 데이터 업데이트 완료");
// }
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
public class StockScheduler {
private final StockService stockService;

@Scheduled(cron = "0 5 18 * * MON-FRI", zone = "Asia/Seoul") // todo: 공휴일/휴장일 스케쥴러 처리 필요.
public void saveDailyStockData() {
stockService.saveDailyStockData();
log.info("[Scheduler] 일별 주가 데이터 업데이트 완료");
}

@Scheduled(cron = "0 0/5 9-18 * * MON-FRI", zone = "Asia/Seoul") // todo: 공휴일/휴장일 스케쥴러 처리 필요.
public void updateStockData() {
stockService.updateCurrentStockData();
log.info("[Scheduler] 주가 데이터 업데이트 완료");
}
// @Scheduled(cron = "0 5 18 * * MON-FRI", zone = "Asia/Seoul") // todo: 공휴일/휴장일 스케쥴러 처리 필요.
// public void saveDailyStockData() {
// stockService.saveDailyStockData();
// log.info("[Scheduler] 일별 주가 데이터 업데이트 완료");
// }
//
// @Scheduled(cron = "0 0/5 9-18 * * MON-FRI", zone = "Asia/Seoul") // todo: 공휴일/휴장일 스케쥴러 처리 필요.
// public void updateStockData() {
// stockService.updateCurrentStockData();
// log.info("[Scheduler] 주가 데이터 업데이트 완료");
// }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.stockport.server.application.service.stock;

import com.stockport.server.domain.stock.entity.Stock;
import com.stockport.server.domain.stock.entity.StockPrice;
import com.stockport.server.domain.stock.repository.StockPriceRepository;
import com.stockport.server.global.feign.adaptor.KisStockPriceAdaptor;
import com.stockport.server.global.feign.dto.KisStockPeriodPrice;
import com.stockport.server.global.utils.KisParsingUtils;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Service
@Slf4j
@RequiredArgsConstructor
public class PeriodicStockSaver {

private final StockPriceRepository stockPriceRepository;
private final KisStockPriceAdaptor kisStockPriceAdaptor;
@PersistenceContext private final EntityManager entityManager;

@Transactional
public void saveOnePeriod(Stock stock, LocalDate start, LocalDate end) {

// 1) 해당 종목의 기존 날짜 목록을 한 번에 가져오기
List<LocalDate> existingDates = stockPriceRepository
.findAllBaseDatesByStockAndDateRange(stock, start, end);

Set<LocalDate> existingDateSet = new HashSet<>(existingDates); // O(1) 조회용

// 2) API 호출하여 새 데이터 변환
List<StockPrice> newList = kisStockPriceAdaptor.getStockPeriodPrice(stock.getStockCd(), start, end)
.getOutput2().stream()
.filter(dto -> !existingDateSet.contains(KisParsingUtils.parseDateSafe(dto.getBaseDate()))) // 필터링
.map(KisStockPeriodPrice::toEntity)
.peek(entity -> entity.updateStock(stock))
.toList();

// 3) 새 데이터만 saveAll 수행
if (!newList.isEmpty()) {
stockPriceRepository.saveAll(newList);
}

entityManager.flush();
entityManager.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,5 @@ public interface StockService {

void updatePeriodicStockData(LocalDate startDate, LocalDate endDate);

void updateHistoricalStockData();

void saveDailyStockData();
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.stockport.server.domain.stock.entity.Stock;
import com.stockport.server.domain.stock.entity.StockCurrentPrice;
import com.stockport.server.domain.stock.entity.StockPrice;
import com.stockport.server.domain.stock.repository.StockCurrentPriceRepository;
import com.stockport.server.domain.stock.repository.StockPriceRepository;
import com.stockport.server.domain.stock.repository.StockRepository;
import com.stockport.server.global.apipayload.code.status.ErrorStatus;
Expand Down Expand Up @@ -36,6 +37,8 @@ public class StockServiceImpl implements StockService {
private final StockRepository stockRepository;
private final StockPriceRepository stockPriceRepository;
private final KisStockPriceAdaptor kisStockPriceAdaptor;
private final StockCurrentPriceRepository stockCurrentPriceRepository;
private final PeriodicStockSaver periodicSaver;

@PersistenceContext
private EntityManager entityManager;
Expand Down Expand Up @@ -66,7 +69,7 @@ public StockInfoResponse getStockInfo(String stockCode, LocalDate startDate, Loc

@Override
public List<StockQueryResponse> searchStocks(String query) {
List<Stock> stocks = stockRepository.findTop10ByStockNameContainingIgnoreCaseOrStockCdContainingIgnoreCaseOrIsinCdContainingIgnoreCaseOrderByStockNameAsc(query, query, query);
List<Stock> stocks = stockRepository.findTop10ByStockNameContainingIgnoreCaseOrStockCdContainingIgnoreCaseOrIsinCdContainingIgnoreCaseOrderByMarketCapDesc(query, query, query);

return stocks.stream()
.map(StockQueryResponse::of)
Expand All @@ -82,61 +85,32 @@ public void updateCurrentStockData() {
List<String> stockCdList = stockList.stream()
.map(Stock::getStockCd)
.toList();
List<StockCurrentPrice> stockCurrentPriceList = kisStockPriceAdaptor.getMultiStockCurrentPrice(stockCdList).getOutput().stream()

List<KisMultieStockCurrentPrice> output = kisStockPriceAdaptor.getMultiStockCurrentPrice(stockCdList).getOutput();

List<StockCurrentPrice> stockCurrentPriceList = output.stream()
.map(currentPrice -> currentPrice.toEntity(LocalDate.now()))
.toList();

for (int i = 0; i < Math.min(30, stockList.size()); i++)
stockList.get(i).updateCurrentPriceInfo(stockCurrentPriceList.get(i));
log.info("[stock] 현재 주가 데이터 업데이트 진행률 {}%", Math.min((stockIdx + 1) * 30, stocks.size()) * 100 / stocks.size());
}
log.info("[stock] 현재 주가 데이터 업데이트 완료");
}

@Override
@Transactional
public void updatePeriodicStockData(LocalDate startDate, LocalDate endDate) {
List<Stock> stocks = stockRepository.findAll();
for (Stock stock : stocks) {
List<StockPrice> stockPriceList = kisStockPriceAdaptor.getStockPeriodPrice(stock.getStockCd(), startDate, endDate)
.getOutput2().stream()
.map(KisStockPeriodPrice::toEntity)
.toList();

for (StockPrice stockPrice : stockPriceList) {
if (stockPriceRepository.existsByStockAndBaseDate(stock, stockPrice.getBaseDate()))
continue;
stockPrice.updateStock(stock);
stockPriceRepository.save(stockPrice);
}
log.info("[stock] 기간 주가 데이터 업데이트 완료: {} 진행률 {}%", stock.getStockCd(), (stocks.indexOf(stock) + 1) * 100 / stocks.size());
entityManager.flush();
entityManager.clear();
}
}

@Override
@Transactional
public void updateHistoricalStockData() {
List<Stock> stocks = stockRepository.findAll();
LocalDate endDate = LocalDate.now();
LocalDate startDate = endDate.minusYears(10);

for (Stock stock : stocks) {
for (LocalDate updateDate = startDate; updateDate.isBefore(endDate); updateDate = updateDate.plusDays(140)) {
List<StockPrice> stockPriceList = kisStockPriceAdaptor.getStockPeriodPrice(stock.getStockCd(), updateDate, updateDate.plusDays(139))
.getOutput2().stream()
.map(KisStockPeriodPrice::toEntity)
.toList();
for (StockPrice stockPrice : stockPriceList) {
if (stockPriceRepository.existsByStockAndBaseDate(stock, stockPrice.getBaseDate()))
continue;
stockPrice.updateStock(stock);
stockPriceRepository.save(stockPrice);
}
periodicSaver.saveOnePeriod(stock, updateDate, updateDate.plusDays(139));
}
log.info("[stock] 과거 주가 데이터 업데이트 완료: {} 진행률 {}%", stock.getStockCd(), (stocks.indexOf(stock) + 1) * 100 / stocks.size());
entityManager.flush();
entityManager.clear();

log.info("[stock] 기간 주가 데이터 업데이트 완료: {} 진행률 {}%",
stock.getStockCd(),
(stocks.indexOf(stock) + 1) * 100 / stocks.size());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,18 @@ public class Stock extends BaseEntity {
private List<StockPrice> stockPrices; // 과거 주가 정보

public void updateCurrentPriceInfo(StockCurrentPrice newCurrentPriceInfo) {
if (this.currentPriceInfo != null)
this.currentPriceInfo.updateStock(null);

this.currentPriceInfo = newCurrentPriceInfo;
newCurrentPriceInfo.updateStock(this);
if (this.currentPriceInfo == null) {
this.currentPriceInfo = newCurrentPriceInfo;
newCurrentPriceInfo.updateStock(this);
return;
}

this.currentPriceInfo.updateCurrentPrice(newCurrentPriceInfo);
updateMarketCap();
}

public void updateMarketCap() {
this.marketCap = currentPriceInfo.getCurrentPrice() * this.listedShares;
this.marketCap = currentPriceInfo.getCurrentPrice().toBigInteger().longValue() * this.listedShares;
}

@Builder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,20 @@ public class StockCurrentPrice extends BaseEntity {
@Column(nullable = false)
private LocalDate baseDate; // 기준일

@Column(nullable = false)
private Integer openPrice; // 시가
@Column(precision = 12, scale = 2)
private BigDecimal openPrice; // 시가

@Column(nullable = false)
private Integer currentPrice; // 현재가
@Column(precision = 12, scale = 2)
private BigDecimal currentPrice; // 현재가

@Column(nullable = false)
private Integer highPrice; // 고가
@Column(precision = 12, scale = 2)
private BigDecimal highPrice; // 고가

@Column(nullable = false)
private Integer lowPrice; // 저가
@Column(precision = 12, scale = 2)
private BigDecimal lowPrice; // 저가

@Column(nullable = false)
private Integer changeAmount; // 등락폭
@Column(precision = 12, scale = 2)
private BigDecimal changeAmount; // 등락폭

@Column(precision = 5, scale = 2)
private BigDecimal changeRate; // 등락률
Expand All @@ -50,11 +50,21 @@ public void updateStock(Stock stock) {
this.stock = stock;
}

public void updateCurrentPrice(StockCurrentPrice newCurrentPrice) {
this.baseDate = newCurrentPrice.getBaseDate();
this.openPrice = newCurrentPrice.getOpenPrice();
this.currentPrice = newCurrentPrice.getCurrentPrice();
this.highPrice = newCurrentPrice.getHighPrice();
this.lowPrice = newCurrentPrice.getLowPrice();
this.changeAmount = newCurrentPrice.getChangeAmount();
this.changeRate = newCurrentPrice.getChangeRate();
}

@Builder
public StockCurrentPrice(Stock stock, LocalDate baseDate,
Integer openPrice, Integer currentPrice,
Integer highPrice, Integer lowPrice,
Integer changeAmount, BigDecimal changeRate) {
BigDecimal openPrice, BigDecimal currentPrice,
BigDecimal highPrice, BigDecimal lowPrice,
BigDecimal changeAmount, BigDecimal changeRate) {
this.stock = stock;
this.baseDate = baseDate;
this.openPrice = openPrice;
Expand All @@ -66,9 +76,9 @@ public StockCurrentPrice(Stock stock, LocalDate baseDate,
}

public static StockCurrentPrice create(LocalDate baseDate,
Integer openPrice, Integer currentPrice,
Integer highPrice, Integer lowPrice,
Integer changeAmount, BigDecimal changeRate) {
BigDecimal openPrice, BigDecimal currentPrice,
BigDecimal highPrice, BigDecimal lowPrice,
BigDecimal changeAmount, BigDecimal changeRate) {
return StockCurrentPrice.builder()
.baseDate(baseDate)
.openPrice(openPrice)
Expand Down
Loading
Loading