Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
d969b94
refactor:테스트 하기 좋게 코드 리펙토링(SRP에 맞게 리펙토링)
bongj9 Jun 1, 2025
c81e2d5
refactor:시간 데이터를 파라미터로 받도록 수정(테스트할때마다 시간이 달라지는점때문에 수정)
bongj9 Jun 1, 2025
6888f2f
refactor:결합도가 높은 메서드들을 테스트 하기 좋게 메서드 분리,SRP 원칙에 따라 리펙토링
bongj9 Jun 1, 2025
9c1ae72
refactor:결합도가 높은 메서드들을 테스트 하기 좋게 메서드 분리,SRP 원칙에 따라 리펙토링
bongj9 Jun 1, 2025
c8c7ad7
refactor:결합도가 높은 메서드들을 테스트 하기 좋게 메서드 분리,SRP 원칙에 따라 리펙토링
bongj9 Jun 1, 2025
126229c
refactor:결합도가 높은 메서드들을 테스트 하기 좋게 메서드 분리,SRP 원칙에 따라 리펙토링
bongj9 Jun 1, 2025
dc2eecb
refactor:테스트시 CCMap에서 null을 허용하는 예외를 발견
bongj9 Jun 1, 2025
23f3053
test:ChartSubscription unitTest
bongj9 Jun 1, 2025
57ca2d7
test: MinuteOhlcDataService unitTest
bongj9 Jun 1, 2025
61b9d1e
test: RealTimeDataPrevRate unitTest
bongj9 Jun 1, 2025
ca22a83
test: RealTimeOhlcService unitTest
bongj9 Jun 1, 2025
1d3b2d9
test: RealTimeTradeService unitTest
bongj9 Jun 1, 2025
60407ee
test: WebsocketSendService unitTest
bongj9 Jun 1, 2025
b199067
refactor:필드 생성방식을 리플렉션에서 기본 생성자로 변경
bongj9 Jun 2, 2025
88a8149
Test:updateTradeCache, generateRealTimeData, calculateChangeRate 메서드 …
bongj9 Jun 2, 2025
d2ad2cf
Refactor:테스트에서 발견된 ohlcv로직 변경(List를 reverse를 하여 시작가 종가를 찾았으나 불필요한 방법인…
bongj9 Jun 2, 2025
a9e077c
Refactor:역순을 확인한 test 삭제(본 서비스에서 해당 기능을 삭제했기 때문)
bongj9 Jun 2, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,28 @@ public class ChartSubscriptionService {
실시간 체결 내역 구독
*/
public void subscribeRealTimeTradeRate(String ticker) {
validateTicker(ticker);
log.debug("실시간 체결 정보 티커 구독 추가: {}", ticker);
realTimeTradeRateSubscribedTickers.add(ticker);
}

//구독 해지
public void unsubscribeRealTimeTradeRate(String ticker) {
validateTicker(ticker);
log.debug("실시간 체결 정보 티커 구독 해지: {}", ticker);
realTimeTradeRateSubscribedTickers.remove(ticker);
}

//모든 구독 종목 반환
public Set<String> getAllRealTimeTradeRateSubscribedTickers() {
return realTimeTradeRateSubscribedTickers;
}

//종목에 대한 구독 여부
public boolean isSubscribedToRealTimeTradeRate(String ticker) {
if (ticker == null || ticker.trim().isEmpty()) {
return false; // 유효하지 않은 티커는 구독되지 않은 것으로 처리
}
return realTimeTradeRateSubscribedTickers.contains(ticker);
}

Expand All @@ -48,11 +56,13 @@ public boolean isSubscribedToRealTimeTradeRate(String ticker) {
* 실시간 OHLC 티커 구독 추가
*/
public void subscribeRealTimeOhlc(String ticker) {
validateTicker(ticker);
log.debug("실시간 OHLC 티커 구독 추가: {}", ticker);
realTimeOhlcSubscribedTickers.add(ticker);
}

public void unsubscribeRealTimeOhlc(String ticker) {
validateTicker(ticker);
log.debug("실시간 OHLC 티커 구독 해지: {}", ticker);
realTimeOhlcSubscribedTickers.remove(ticker);
}
Expand All @@ -66,25 +76,45 @@ public Set<String> getAllRealTimeOhlcSubscribedTickers() {
}

public boolean isSubscribedToRealTimeOhlc(String ticker) {
if (ticker == null || ticker.trim().isEmpty()) {
return false; // 유효하지 않은 티커는 구독되지 않은 것으로 처리
}
return realTimeOhlcSubscribedTickers.contains(ticker);
}


/*
전날 종가 변동률 구독 추가,삭제,조회
*/
public void subscribePrevRate(String ticker) {
validateTicker(ticker);
log.debug("전날 종가 변동률 티커 구독 추가: {}", ticker);
PrevRateSubscribedTickers.add(ticker);
}

public void unsubscribePrevRate(String ticker) {
validateTicker(ticker);
log.debug("전날 종가 변동률 티커 구독 해지: {}", ticker);
PrevRateSubscribedTickers.remove(ticker);
}

public Set<String> getAllPrevRateSubscribedTickers(String ticker) {
return PrevRateSubscribedTickers;
}

public boolean isSubscribedToPrevRate(String ticker) {
if (ticker == null || ticker.trim().isEmpty()) {
return false; // 유효하지 않은 티커는 구독되지 않은 것으로 처리
}
return PrevRateSubscribedTickers.contains(ticker);
}

//CCmap은 null을 허용시키기때문에 null 종목이 들어가도 npe발생안되는 이슈 테스트에서 발견
//검증 로직 추가
private void validateTicker(String ticker) {
if (ticker == null || ticker.trim().isEmpty()) {
throw new IllegalArgumentException("유효하지 않은 티커입니다: " + ticker);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.cleanengine.coin.trade.entity.Trade;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
Expand All @@ -18,29 +19,45 @@ public class RealTimeDataPrevRateService {
private final RealTimeTradeRepository tradeRepository;

public PrevRateDto generatePrevRateData(TradeEventDto currentTrade) {
// 전일 종가 계산
LocalDateTime today = LocalDateTime.now();
LocalDateTime yesterdayStart = today.minusDays(1).withHour(0).withMinute(0).withSecond(0);
LocalDateTime yesterdayEnd = today.minusDays(1).withHour(23).withMinute(59).withSecond(59);
log.debug("조회 시간 범위: {} ~ {}", yesterdayStart, yesterdayEnd);
return generatePrevRateData(currentTrade, LocalDateTime.now());
}

PrevRateDto generatePrevRateData(TradeEventDto currentTrade, LocalDateTime currentTime) {
String ticker = currentTrade.getTicker();
YesterDay yesterDay = getYesterDay(currentTime);
log.debug("조회 시간 범위: {} ~ {}", yesterDay.yesterdayStart(), yesterDay.yesterdayEnd());

Trade yesterdayLastTrade = tradeRepository.findFirstByTickerAndTradeTimeBetweenOrderByTradeTimeDesc(
ticker, yesterdayStart, yesterdayEnd);
ticker, yesterDay.yesterdayStart(), yesterDay.yesterdayEnd());

if(yesterdayLastTrade == null){
if (yesterdayLastTrade == null) {
log.debug("전일 거래 데이터가 없습니다: {}", ticker);
return new PrevRateDto(ticker, 0.0, currentTrade.getPrice(), 0.0, LocalDateTime.now());
return new PrevRateDto(ticker, 0.0, currentTrade.getPrice(), 0.0, currentTime);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

시간 주입받는 식으로 변경하셨네요 ㅋㅋ 실행력 굿,,

}
double prevClose = yesterdayLastTrade.getPrice();
double currentPrice = currentTrade.getPrice();
double changeRate = ((currentPrice - prevClose) / prevClose) * 100;
double changeRate = getChangeRate(currentPrice, prevClose);

return new PrevRateDto(
ticker,
prevClose,
currentPrice,
changeRate,
LocalDateTime.now()
currentTime
);
}

static double getChangeRate(double currentPrice, double prevClose) {
return ((currentPrice - prevClose) / prevClose) * 100;
}

@NotNull
static YesterDay getYesterDay(LocalDateTime today) {
LocalDateTime yesterdayStart = today.minusDays(1).withHour(0).withMinute(0).withSecond(0);
LocalDateTime yesterdayEnd = today.minusDays(1).withHour(23).withMinute(59).withSecond(59);
return new YesterDay(yesterdayStart, yesterdayEnd);
}

record YesterDay(LocalDateTime yesterdayStart, LocalDateTime yesterdayEnd) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.cleanengine.coin.trade.repository.TradeRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
Expand All @@ -31,56 +32,88 @@ public class RealTimeOhlcService {
*/
public RealTimeOhlcDto getRealTimeOhlc(String ticker) {
try {
// 현재 시간
LocalDateTime now = LocalDateTime.now();

// 마지막 처리 시간 (없으면 현재 시간에서 1초 전)
LocalDateTime lastProcessedTime = lastProcessedTimeMap.getOrDefault(
ticker, now.minusSeconds(1));
// 시간 범위 계산
TimeRange timeRange = calculateTimeRange(ticker, now);

// 1초 전부터 현재까지의 데이터 조회
List<Trade> recentTrades = tradeRepository.findByTickerAndTradeTimeBetweenOrderByTradeTimeAsc(
ticker,
lastProcessedTime,
now
);
// 거래 데이터 조회 및 전처리
List<Trade> recentTrades = getProcessedTradeData(ticker, timeRange);

// 시간 순서대로 정렬이 필요하면 뒤집음
Collections.reverse(recentTrades);

// 거래 데이터가 없으면 마지막으로 캐싱된 데이터 반환
// 거래 데이터가 없으면 캐시된 데이터 반환
if (recentTrades.isEmpty()) {
return lastOhlcDataMap.getOrDefault(ticker, null);
return getCachedData(ticker);
}

// 새로운 마지막 처리 시간 업데이트
lastProcessedTimeMap.put(ticker, now);

// OHLC 계산
Double open = recentTrades.get(0).getPrice();
Double high = recentTrades.stream().mapToDouble(Trade::getPrice).max().orElse(0.0);
Double low = recentTrades.stream().mapToDouble(Trade::getPrice).min().orElse(0.0);
Double close = recentTrades.get(recentTrades.size() - 1).getPrice();
Double volume = recentTrades.stream().mapToDouble(Trade::getSize).sum();

// RealTimeOhlcDto 생성
RealTimeOhlcDto ohlcData = new RealTimeOhlcDto(
ticker,
now,
open,
high,
low,
close,
volume
);

// 캐시에 저장
lastOhlcDataMap.put(ticker, ohlcData);
calculateOhlcv ohlcv = getCalculateOhlcv(recentTrades);

RealTimeOhlcDto ohlcData = createOhlcDto(ticker, now, ohlcv);

// 캐시 업데이트
updateCache(ticker, now, ohlcData);

return ohlcData;
} catch (Exception e) {
log.error("실시간 OHLC 데이터 생성 중 오류: {}", e.getMessage(), e);
return lastOhlcDataMap.getOrDefault(ticker, null);
return getCachedData(ticker);
}
}

// 시간 범위 계산
TimeRange calculateTimeRange(String ticker, LocalDateTime now) {
LocalDateTime lastProcessedTime = lastProcessedTimeMap.getOrDefault(
ticker, now.minusSeconds(1));
return new TimeRange(lastProcessedTime, now);
}

// 거래 데이터 조회 및 전처리
List<Trade> getProcessedTradeData(String ticker, TimeRange timeRange) {
List<Trade> recentTrades = tradeRepository.findByTickerAndTradeTimeBetweenOrderByTradeTimeAsc(
ticker,
timeRange.start(),
timeRange.end()
);

Collections.reverse(recentTrades);
return recentTrades;
}

// 캐시 업데이트
void updateCache(String ticker, LocalDateTime now, RealTimeOhlcDto ohlcData) {
lastProcessedTimeMap.put(ticker, now);
lastOhlcDataMap.put(ticker, ohlcData);
}

// 캐시된 데이터 조회
RealTimeOhlcDto getCachedData(String ticker) {
return lastOhlcDataMap.getOrDefault(ticker, null);
}

// DTO 생성
RealTimeOhlcDto createOhlcDto(String ticker, LocalDateTime timestamp, calculateOhlcv ohlcv) {
return new RealTimeOhlcDto(
ticker,
timestamp,
ohlcv.open(),
ohlcv.high(),
ohlcv.low(),
ohlcv.close(),
ohlcv.volume()
);
}

// OHLCV 계산 메서드
@NotNull
static calculateOhlcv getCalculateOhlcv(List<Trade> recentTrades) {
Double open = recentTrades.get(0).getPrice();
Double high = recentTrades.stream().mapToDouble(Trade::getPrice).max().orElse(0.0);
Double low = recentTrades.stream().mapToDouble(Trade::getPrice).min().orElse(0.0);
Double close = recentTrades.get(recentTrades.size() - 1).getPrice();
Double volume = recentTrades.stream().mapToDouble(Trade::getSize).sum();
return new calculateOhlcv(open, high, low, close, volume);
}

record TimeRange(LocalDateTime start, LocalDateTime end) {}

record calculateOhlcv(Double open, Double high, Double low, Double close, Double volume) {}
}
Loading
Loading