Skip to content

Commit

Permalink
Merge pull request #69 from CSID-DGU/feature/#42/dashboard
Browse files Browse the repository at this point in the history
[refactor] : 현재가 기준으로 계산하도록 리팩토링
  • Loading branch information
bbbang105 authored Jun 14, 2024
2 parents cb796ca + 1e13211 commit 9fbe9b1
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 72 deletions.
14 changes: 13 additions & 1 deletion backend/src/main/java/org/dgu/backend/domain/UserCoin.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,25 @@ public class UserCoin extends BaseEntity {
@Column(name = "coin_name", nullable = false, length = 50)
private String coinName;

@Column(name = "coin_count", precision = 20, scale = 10, nullable = false)
private BigDecimal coinCount;

@Column(name = "price", nullable = false)
private BigDecimal price;

@Column(name = "balance", nullable = false)
private BigDecimal balance;

@Column(name = "rate", nullable = false)
private BigDecimal rate;

@Builder
public UserCoin(User user, String coinName, BigDecimal balance) {
public UserCoin(User user, String coinName, BigDecimal coinCount, BigDecimal price, BigDecimal balance, BigDecimal rate) {
this.user = user;
this.coinName = coinName;
this.coinCount = coinCount;
this.price = price;
this.balance = balance;
this.rate = rate;
}
}
8 changes: 7 additions & 1 deletion backend/src/main/java/org/dgu/backend/dto/DashBoardDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,22 @@ public static class UserAccountResponse {
public static class UserCoinResponse {
private String coinName;
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal balance;
private BigDecimal coinCount;
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal price;
@JsonSerialize(using = BigDecimalSerializer.class)
private BigDecimal balance;
private Boolean isIncrease;
private BigDecimal rate;

public UserCoin to(User user) {
return UserCoin.builder()
.user(user)
.coinName(coinName)
.coinCount(coinCount)
.price(price)
.balance(balance)
.rate(rate)
.build();
}
}
Expand Down
3 changes: 2 additions & 1 deletion backend/src/main/java/org/dgu/backend/dto/UpbitDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ public static class CandleInfoResponse {
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class Account {
private String currency;
private BigDecimal balance;
@JsonProperty("balance")
private BigDecimal coinCount;
private BigDecimal locked;
private BigDecimal avgBuyPrice;
private String unitCurrency;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.dgu.backend.repository.CandleInfoRepository;
import org.dgu.backend.repository.CandleRepository;
import org.dgu.backend.repository.MarketRepository;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
Expand All @@ -22,7 +23,10 @@
@Transactional
@RequiredArgsConstructor
public class CandleInfoServiceImpl implements CandleInfoService {

@Value("${upbit.url.candle-minute}")
private String UPBIT_URL_CANDLE_MINUTE;
@Value("${upbit.url.candle-etc}")
private String UPBIT_URL_CANDLE_ETC;
private final CandleInfoRepository candleInfoRepository;
private final MarketRepository marketRepository;
private final CandleRepository candleRepository;
Expand All @@ -39,10 +43,10 @@ public void getCandleInfo(String koreanName, LocalDateTime to, int count, String
if (candleName.startsWith("minutes")) {
// 분봉인 경우
int unit = Integer.parseInt(candleName.substring(7));
url = String.format("https://api.upbit.com/v1/candles/%s/%d?market=%s&count=%d", candleName.substring(0,7), unit, marketName, count);
url = String.format(UPBIT_URL_CANDLE_MINUTE, candleName.substring(0,7), unit, marketName, count);
} else {
// 그 외 (일봉, 주봉, 월봉)
url = String.format("https://api.upbit.com/v1/candles/%s?market=%s&count=%d", candleName, marketName, count);
url = String.format(UPBIT_URL_CANDLE_ETC, candleName, marketName, count);
}

if (to != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public void ensureCandleInfoUpToDate(String koreanName, String candleName) {
int candleInterval = candleUtil.calculateCandleInterval(candleName);
LocalDateTime startDate;
if (Objects.isNull(latestCandleInfo)) {
startDate = dateUtil.convertToLocalDateTime("2019-01-01T00:00:00");
startDate = dateUtil.convertToLocalDateTime("2018-01-01T00:00:00");
} else {
startDate = latestCandleInfo.getDateTime();
if (startDate.plusMinutes(candleInterval).isAfter(LocalDateTime.now())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.dgu.backend.repository.UpbitKeyRepository;
import org.dgu.backend.repository.UserCoinRepository;
import org.dgu.backend.util.JwtUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;
Expand All @@ -30,6 +31,10 @@
@RequiredArgsConstructor
@Slf4j
public class DashBoardServiceImpl implements DashBoardService {
@Value("${upbit.url.account}")
private String UPBIT_URL_ACCOUNT;
@Value("${upbit.url.ticker}")
private String UPBIT_URL_TICKER;
private final RestTemplate restTemplate;
private final JwtUtil jwtUtil;
private final UpbitKeyRepository upbitKeyRepository;
Expand All @@ -39,18 +44,9 @@ public class DashBoardServiceImpl implements DashBoardService {
@Override
public DashBoardDto.UserAccountResponse getUserAccount(String authorizationHeader) {
User user = jwtUtil.getUserFromHeader(authorizationHeader);
UpbitKey upbitKey = upbitKeyRepository.findByUser(user);
if (Objects.isNull(upbitKey)) {
throw new UpbitException(UpbitErrorResult.NOT_FOUND_UPBIT_KEY);
}
UpbitDto.Account[] accounts = getUpbitAccounts(user);

String token = jwtUtil.generateUpbitToken(upbitKey);
String url = "https://api.upbit.com/v1/accounts";
UpbitDto.Account[] responseBody = getUserAccountsAtUpbit(url, token);
if (Objects.isNull(responseBody)) {
throw new UpbitException(UpbitErrorResult.FAIL_ACCESS_USER_ACCOUNT);
}
BigDecimal accountSum = getAccountSum(responseBody);
BigDecimal accountSum = getAccountSum(accounts);

return DashBoardDto.UserAccountResponse.builder()
.account(accountSum.setScale(3, RoundingMode.HALF_UP))
Expand All @@ -61,86 +57,109 @@ public DashBoardDto.UserAccountResponse getUserAccount(String authorizationHeade
@Override
public List<DashBoardDto.UserCoinResponse> getUserCoins(String authorizationHeader) {
User user = jwtUtil.getUserFromHeader(authorizationHeader);
UpbitKey upbitKey = upbitKeyRepository.findByUser(user);
if (Objects.isNull(upbitKey)) {
throw new UpbitException(UpbitErrorResult.NOT_FOUND_UPBIT_KEY);
}
UpbitDto.Account[] accounts = getUpbitAccounts(user);

String token = jwtUtil.generateUpbitToken(upbitKey);
String url = "https://api.upbit.com/v1/accounts";
UpbitDto.Account[] responseBody = getUserAccountsAtUpbit(url, token);
if (Objects.isNull(responseBody)) {
throw new UpbitException(UpbitErrorResult.FAIL_ACCESS_USER_ACCOUNT);
}
return processUserCoins(accounts, user);
}

// 유저 보유 코인을 처리하는 메서드
private List<DashBoardDto.UserCoinResponse> processUserCoins(UpbitDto.Account[] accounts, User user) {
List<DashBoardDto.UserCoinResponse> userCoinResponses = new ArrayList<>();
for (UpbitDto.Account account : responseBody) {
for (UpbitDto.Account account : accounts) {
String coinName = account.getCurrency();
if (coinName.equals("KRW")) {
continue;
}

UserCoin userCoin = userCoinRepository.findByCoinName(account.getCurrency());
boolean isIncrease = false;
if (!Objects.isNull(userCoin)) {
isIncrease = isBalanceIncreased(account, userCoin);
userCoinRepository.delete(userCoin);
// 현금은 제외
if (!coinName.equals("KRW")) {
userCoinResponses.add(processSingleCoin(account, user, coinName));
}
}
return userCoinResponses;
}

DashBoardDto.UserCoinResponse userCoinResponse = DashBoardDto.UserCoinResponse.builder()
.coinName(coinName)
.balance(account.getBalance())
.price(account.getAvgBuyPrice())
.isIncrease(isIncrease)
.build();
userCoinResponses.add(userCoinResponse);

userCoinRepository.save(userCoinResponse.to(user));
// 단일 코인 정보를 처리하는 메서드
private DashBoardDto.UserCoinResponse processSingleCoin(UpbitDto.Account account, User user, String coinName) {
coinName = "KRW-" + coinName;
UserCoin userCoin = userCoinRepository.findByCoinName(coinName);
UpbitDto.Ticker[] ticker = getTickerPriceAtUpbit(UPBIT_URL_TICKER + coinName);
BigDecimal curPrice = BigDecimal.valueOf(ticker[0].getPrice());
BigDecimal curCoinCount = account.getCoinCount();
boolean isIncrease = false;
BigDecimal rate = BigDecimal.ZERO;
if (!Objects.isNull(userCoin)) {
rate = getCoinPriceIncreaseRate(userCoin, curPrice, curCoinCount);
isIncrease = rate.compareTo(BigDecimal.ZERO) > 0;
userCoinRepository.delete(userCoin);
}

return userCoinResponses;
DashBoardDto.UserCoinResponse userCoinResponse = DashBoardDto.UserCoinResponse.builder()
.coinName(coinName)
.coinCount(curCoinCount)
.price(curPrice)
.balance(curPrice.multiply(curCoinCount).setScale(4, RoundingMode.HALF_UP))
.isIncrease(isIncrease)
.rate(rate)
.build();

userCoinRepository.save(userCoinResponse.to(user));
return userCoinResponse;
}

// 대표 코인 5개 정보를 반환하는 메서드
@Override
public List<DashBoardDto.RepresentativeCoinResponse> getRepresentativeCoins() {
String url = "https://api.upbit.com/v1/ticker?markets=";
List<DashBoardDto.RepresentativeCoinResponse> representativeCoinResponses = new ArrayList<>();
for (Coin coin : Coin.values()) {
UpbitDto.Ticker[] responseBody = getTickerPriceAtUpbit(url + coin.getMarketName());
if (Objects.isNull(responseBody[0])) {
throw new UpbitException(UpbitErrorResult.FAIL_ACCESS_COIN_INFO);
}
representativeCoinResponses.add(DashBoardDto.RepresentativeCoinResponse.of(responseBody[0], coin.getKoreanName()));
UpbitDto.Ticker[] ticker = getTickerPriceAtUpbit(UPBIT_URL_TICKER + coin.getMarketName());
representativeCoinResponses.add(DashBoardDto.RepresentativeCoinResponse.of(ticker[0], coin.getKoreanName()));
}

return representativeCoinResponses;
}

// 현재 업비트 잔고를 계산하는 메서드
private BigDecimal getAccountSum(UpbitDto.Account[] responseBody) {
private BigDecimal getAccountSum(UpbitDto.Account[] accounts) {
BigDecimal accountSum = BigDecimal.ZERO;
for (UpbitDto.Account account : responseBody) {
for (UpbitDto.Account account : accounts) {
if (account.getCurrency().equals("KRW")) {
accountSum = accountSum.add(account.getBalance());
accountSum = accountSum.add(account.getCoinCount());
} else {
BigDecimal balance = account.getBalance();
BigDecimal avgBuyPrice = account.getAvgBuyPrice();
accountSum = accountSum.add(balance.multiply(avgBuyPrice));
// 현재가를 가져옴
String coinName = "KRW-" + account.getCurrency();
UpbitDto.Ticker[] ticker = getTickerPriceAtUpbit(UPBIT_URL_TICKER + coinName);
BigDecimal curPrice = BigDecimal.valueOf(ticker[0].getPrice());
BigDecimal userCoinCount = account.getCoinCount();
accountSum = accountSum.add(curPrice.multiply(userCoinCount));
}
}
return accountSum;
}

// 코인 가격 상승 여부를 판단하는 메서드
private boolean isBalanceIncreased(UpbitDto.Account account, UserCoin userCoin) {
// 기존 대비 코인 가격 상승률을 계산하는 메서드
private BigDecimal getCoinPriceIncreaseRate(UserCoin userCoin, BigDecimal curPrice, BigDecimal curCoinCount) {
BigDecimal pastValue = userCoin.getPrice().multiply(userCoin.getCoinCount());
BigDecimal currentValue = curPrice.multiply(curCoinCount);

return account.getBalance().compareTo(userCoin.getBalance()) > 0;
return currentValue.subtract(pastValue)
.divide(pastValue, 6, RoundingMode.HALF_UP)
.multiply(new BigDecimal("100"));
}

// 유저 업비트 계좌 정보를 조회하는 메서드
private UpbitDto.Account[] getUpbitAccounts(User user) {
UpbitKey upbitKey = upbitKeyRepository.findByUser(user);
if (Objects.isNull(upbitKey)) {
throw new UpbitException(UpbitErrorResult.NOT_FOUND_UPBIT_KEY);
}

String token = jwtUtil.generateUpbitToken(upbitKey);
UpbitDto.Account[] responseBody = getUserAccountsAtUpbit(UPBIT_URL_ACCOUNT, token);
if (Objects.isNull(responseBody)) {
throw new UpbitException(UpbitErrorResult.FAIL_ACCESS_USER_ACCOUNT);
}
return responseBody;
}

// 전체 계좌 조회 업비트 API와 통신하는 메서드
private UpbitDto.Account[] getUserAccountsAtUpbit(String url, String token) {

String authenticationToken = "Bearer " + token;
HttpHeaders headers = new HttpHeaders();
headers.set("accept", MediaType.APPLICATION_JSON_VALUE);
Expand Down Expand Up @@ -175,6 +194,9 @@ private UpbitDto.Ticker[] getTickerPriceAtUpbit(String url) {
new HttpEntity<>(headers),
UpbitDto.Ticker[].class
);
if (Objects.isNull(responseEntity.getBody()[0])) {
throw new UpbitException(UpbitErrorResult.FAIL_ACCESS_COIN_INFO);
}
return responseEntity.getBody();
} catch (HttpClientErrorException e) {
if (e.getStatusCode() == HttpStatus.UNAUTHORIZED && e.getResponseBodyAsString().contains("no_authorization_ip")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import lombok.extern.slf4j.Slf4j;
import org.dgu.backend.dto.UpbitDto;
import org.dgu.backend.repository.MarketRepository;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
Expand All @@ -14,20 +15,19 @@
@RequiredArgsConstructor
@Slf4j
public class MarketServiceImpl implements MarketService {

@Value("${upbit.url.market}")
private String UPBIT_URL_MARKET;
private final RestTemplate restTemplate;
private final MarketRepository marketRepository;

// 모든 가상화폐 데이터를 가져와 저장하는 메서드
@Override
public void getAllMarkets() {
String url = "https://api.upbit.com/v1/market/all?isDetails=false";

HttpHeaders headers = new HttpHeaders();
headers.set("accept", MediaType.APPLICATION_JSON_VALUE);

ResponseEntity<UpbitDto.MarketResponse[]> responseEntity = restTemplate.exchange(
url,
UPBIT_URL_MARKET,
HttpMethod.GET,
new HttpEntity<>(headers),
UpbitDto.MarketResponse[].class
Expand Down
2 changes: 1 addition & 1 deletion backend/src/main/java/org/dgu/backend/util/CandleUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public LocalDateTime getStartDateByCandleName(String candleName) {
case "minutes10" -> now.minusMonths(10);
case "minutes15" -> now.minusMonths(15);
case "minutes30" -> now.minusMonths(30);
default -> LocalDateTime.of(2019, 1, 1, 0, 0);
default -> LocalDateTime.of(2018, 1, 1, 0, 0);
};
}

Expand Down
10 changes: 7 additions & 3 deletions backend/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ jwt:
aes:
secret: ${AES_SECRET}

upbit-open-api:
access-key: ${UPBIT_ACCESS_KEY}
secret-key: ${UPBIT_SECRET_KEY}
upbit:
url:
market: ${UPBIT_URL_MARKET}
ticker: ${UPBIT_URL_TICKER}
account: ${UPBIT_URL_ACCOUNT}
candle-minute: ${UPBIT_URL_CANDLE_MINUTE}
candle-etc: ${UPBIT_URL_CANDLE_ETC}

0 comments on commit 9fbe9b1

Please sign in to comment.