diff --git a/backend/src/main/java/org/dgu/backend/exception/UpbitErrorResult.java b/backend/src/main/java/org/dgu/backend/exception/UpbitErrorResult.java index 7e90864..a612750 100644 --- a/backend/src/main/java/org/dgu/backend/exception/UpbitErrorResult.java +++ b/backend/src/main/java/org/dgu/backend/exception/UpbitErrorResult.java @@ -9,12 +9,9 @@ @Getter @RequiredArgsConstructor public enum UpbitErrorResult implements BaseErrorCode { - FAIL_ACCESS_USER_ACCOUNT(HttpStatus.NOT_FOUND, "404", "업비트에서 유저 잔고를 가져오는 데 실패했습니다."), - FAIL_ACCESS_COIN_INFO(HttpStatus.NOT_FOUND, "404", "업비트에서 코인 정보를 가져오는 데 실패했습니다."), - FAIL_GET_CANDLE_INFO(HttpStatus.NOT_FOUND, "404", "업비트에서 캔들 정보를 가져오는 데 실패했습니다."), - NOT_FOUND_UPBIT_KEY(HttpStatus.NOT_FOUND, "404", "업비트 키가 존재하지 않습니다."), + FAIL_GET_RESPONSE(HttpStatus.UNAUTHORIZED, "401", "업비트에서 데이터를 가져오는 데 실패했습니다."), UNAUTHORIZED_IP(HttpStatus.UNAUTHORIZED, "401", "허용되지 않은 IP 주소입니다."), - UNAUTHORIZED_UPBIT_KEY(HttpStatus.UNAUTHORIZED, "401", "올바른 업비트 키가 아닙니다."); + UNAUTHORIZED_KEY(HttpStatus.UNAUTHORIZED, "401", "올바른 업비트 키가 아닙니다."); private final HttpStatus httpStatus; private final String code; diff --git a/backend/src/main/java/org/dgu/backend/exception/UserErrorResult.java b/backend/src/main/java/org/dgu/backend/exception/UserErrorResult.java index 4abdf7e..0646eaa 100644 --- a/backend/src/main/java/org/dgu/backend/exception/UserErrorResult.java +++ b/backend/src/main/java/org/dgu/backend/exception/UserErrorResult.java @@ -10,7 +10,8 @@ @RequiredArgsConstructor public enum UserErrorResult implements BaseErrorCode { NOT_FOUND_USER(HttpStatus.NOT_FOUND, "404", "존재하지 않는 유저입니다."), - ALREADY_AGREED(HttpStatus.CONFLICT, "409", "이미 서비스 약관 동의를 한 유저입니다."); + ALREADY_AGREED(HttpStatus.CONFLICT, "409", "이미 서비스 약관 동의를 한 유저입니다."), + NOT_FOUND_KEY(HttpStatus.NOT_FOUND, "404", "업비트 키가 존재하지 않습니다."); private final HttpStatus httpStatus; private final String code; diff --git a/backend/src/main/java/org/dgu/backend/service/CandleInfoServiceImpl.java b/backend/src/main/java/org/dgu/backend/service/CandleInfoServiceImpl.java index 1cc38c0..45e8ee9 100644 --- a/backend/src/main/java/org/dgu/backend/service/CandleInfoServiceImpl.java +++ b/backend/src/main/java/org/dgu/backend/service/CandleInfoServiceImpl.java @@ -6,18 +6,15 @@ import org.dgu.backend.domain.CandleInfo; import org.dgu.backend.domain.Market; import org.dgu.backend.dto.UpbitDto; -import org.dgu.backend.exception.UpbitErrorResult; -import org.dgu.backend.exception.UpbitException; 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; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.Objects; @Service @Transactional @@ -30,7 +27,7 @@ public class CandleInfoServiceImpl implements CandleInfoService { private final CandleInfoRepository candleInfoRepository; private final MarketRepository marketRepository; private final CandleRepository candleRepository; - private final RestTemplate restTemplate; + private final UpbitApiClient upbitApiClient; // 업비트 API를 통해 캔들 정보를 가져오는 메서드 @Override @@ -49,30 +46,16 @@ public void getCandleInfo(String koreanName, LocalDateTime to, int count, String url = String.format(UPBIT_URL_CANDLE_ETC, candleName, marketName, count); } - if (to != null) { + if (!Objects.isNull(to)) { // 마지막 캔들 시각도 지정한 경우 String formattedTo = to.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")); url += ("&to=" + formattedTo); } - HttpHeaders headers = new HttpHeaders(); - headers.set("accept", MediaType.APPLICATION_JSON_VALUE); - - ResponseEntity responseEntity = restTemplate.exchange( - url, - HttpMethod.GET, - new HttpEntity<>(headers), - UpbitDto.CandleInfoResponse[].class - ); - - UpbitDto.CandleInfoResponse[] responseBody = responseEntity.getBody(); - if (responseBody != null) { - for (UpbitDto.CandleInfoResponse candleInfoResponse : responseBody) { - CandleInfo candleInfo = CandleInfo.toEntity(candleInfoResponse, market, candle); - candleInfoRepository.save(candleInfo); - } - } else { - throw new UpbitException(UpbitErrorResult.FAIL_GET_CANDLE_INFO); + UpbitDto.CandleInfoResponse[] responseBody = upbitApiClient.getCandleInfoAtUpbit(url); + for (UpbitDto.CandleInfoResponse candleInfoResponse : responseBody) { + CandleInfo candleInfo = CandleInfo.toEntity(candleInfoResponse, market, candle); + candleInfoRepository.save(candleInfo); } } } \ No newline at end of file diff --git a/backend/src/main/java/org/dgu/backend/service/DashBoardServiceImpl.java b/backend/src/main/java/org/dgu/backend/service/DashBoardServiceImpl.java index 8fde0eb..ebb00c4 100644 --- a/backend/src/main/java/org/dgu/backend/service/DashBoardServiceImpl.java +++ b/backend/src/main/java/org/dgu/backend/service/DashBoardServiceImpl.java @@ -10,19 +10,13 @@ import org.dgu.backend.domain.UserCoin; import org.dgu.backend.dto.DashBoardDto; import org.dgu.backend.dto.UpbitDto; -import org.dgu.backend.exception.MarketErrorResult; -import org.dgu.backend.exception.MarketException; -import org.dgu.backend.exception.UpbitErrorResult; -import org.dgu.backend.exception.UpbitException; +import org.dgu.backend.exception.*; import org.dgu.backend.repository.MarketRepository; 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; -import org.springframework.web.client.RestTemplate; import java.math.BigDecimal; import java.math.RoundingMode; @@ -39,8 +33,8 @@ public class DashBoardServiceImpl implements DashBoardService { private String UPBIT_URL_ACCOUNT; @Value("${upbit.url.ticker}") private String UPBIT_URL_TICKER; - private final RestTemplate restTemplate; private final JwtUtil jwtUtil; + private final UpbitApiClient upbitApiClient; private final UpbitKeyRepository upbitKeyRepository; private final UserCoinRepository userCoinRepository; private final MarketRepository marketRepository; @@ -88,8 +82,8 @@ private DashBoardDto.UserCoinResponse processSingleCoin(UpbitDto.Account account if (Objects.isNull(market)) { throw new MarketException(MarketErrorResult.NOT_FOUND_MARKET); } - UpbitDto.Ticker[] ticker = getTickerPriceAtUpbit(UPBIT_URL_TICKER + marketName); - BigDecimal curPrice = BigDecimal.valueOf(ticker[0].getPrice()); + UpbitDto.Ticker[] ticker = upbitApiClient.getTickerPriceAtUpbit(UPBIT_URL_TICKER + marketName); + BigDecimal curPrice = ticker[0].getPrice(); BigDecimal curCoinCount = account.getCoinCount(); boolean isIncrease = false; BigDecimal rate = BigDecimal.ZERO; @@ -119,7 +113,7 @@ private DashBoardDto.UserCoinResponse processSingleCoin(UpbitDto.Account account public List getRepresentativeCoins() { List representativeCoinResponses = new ArrayList<>(); for (Coin coin : Coin.values()) { - UpbitDto.Ticker[] ticker = getTickerPriceAtUpbit(UPBIT_URL_TICKER + coin.getMarketName()); + UpbitDto.Ticker[] ticker = upbitApiClient.getTickerPriceAtUpbit(UPBIT_URL_TICKER + coin.getMarketName()); representativeCoinResponses.add(DashBoardDto.RepresentativeCoinResponse.of(ticker[0], coin.getKoreanName(), coin.getEnglishName())); } @@ -135,8 +129,8 @@ private BigDecimal getAccountSum(UpbitDto.Account[] accounts) { } else { // 현재가를 가져옴 String marketName = "KRW-" + account.getCurrency(); - UpbitDto.Ticker[] ticker = getTickerPriceAtUpbit(UPBIT_URL_TICKER + marketName); - BigDecimal curPrice = BigDecimal.valueOf(ticker[0].getPrice()); + UpbitDto.Ticker[] ticker = upbitApiClient.getTickerPriceAtUpbit(UPBIT_URL_TICKER + marketName); + BigDecimal curPrice = ticker[0].getPrice(); BigDecimal userCoinCount = account.getCoinCount(); accountSum = accountSum.add(curPrice.multiply(userCoinCount)); } @@ -158,63 +152,10 @@ private BigDecimal getCoinPriceIncreaseRate(UserCoin userCoin, BigDecimal curPri private UpbitDto.Account[] getUpbitAccounts(User user) { UpbitKey upbitKey = upbitKeyRepository.findByUser(user); if (Objects.isNull(upbitKey)) { - throw new UpbitException(UpbitErrorResult.NOT_FOUND_UPBIT_KEY); + throw new UserException(UserErrorResult.NOT_FOUND_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); - headers.add("Authorization", authenticationToken); - - try { - ResponseEntity responseEntity = restTemplate.exchange( - url, - HttpMethod.GET, - new HttpEntity<>(headers), - UpbitDto.Account[].class - ); - return responseEntity.getBody(); - } catch (HttpClientErrorException e) { - if (e.getStatusCode() == HttpStatus.UNAUTHORIZED && e.getResponseBodyAsString().contains("no_authorization_ip")) { - throw new UpbitException(UpbitErrorResult.UNAUTHORIZED_IP); - } else { - throw new UpbitException(UpbitErrorResult.UNAUTHORIZED_UPBIT_KEY); - } - } - } - - // 시세 현재가 조회 업비트 API와 통신하는 메서드 - private UpbitDto.Ticker[] getTickerPriceAtUpbit(String url) { - HttpHeaders headers = new HttpHeaders(); - headers.set("accept", MediaType.APPLICATION_JSON_VALUE); - - try { - ResponseEntity responseEntity = restTemplate.exchange( - url, - HttpMethod.GET, - 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")) { - throw new UpbitException(UpbitErrorResult.UNAUTHORIZED_IP); - } else { - throw new UpbitException(UpbitErrorResult.UNAUTHORIZED_UPBIT_KEY); - } - } + return upbitApiClient.getUserAccountsAtUpbit(UPBIT_URL_ACCOUNT, token); } } \ No newline at end of file diff --git a/backend/src/main/java/org/dgu/backend/service/MarketServiceImpl.java b/backend/src/main/java/org/dgu/backend/service/MarketServiceImpl.java index cb40ee3..89969b3 100644 --- a/backend/src/main/java/org/dgu/backend/service/MarketServiceImpl.java +++ b/backend/src/main/java/org/dgu/backend/service/MarketServiceImpl.java @@ -17,32 +17,18 @@ public class MarketServiceImpl implements MarketService { @Value("${upbit.url.market}") private String UPBIT_URL_MARKET; - private final RestTemplate restTemplate; + private final UpbitApiClient upbitApiClient; private final MarketRepository marketRepository; // 모든 가상화폐 데이터를 가져와 저장하는 메서드 @Override public void getAllMarkets() { - HttpHeaders headers = new HttpHeaders(); - headers.set("accept", MediaType.APPLICATION_JSON_VALUE); - - ResponseEntity responseEntity = restTemplate.exchange( - UPBIT_URL_MARKET, - HttpMethod.GET, - new HttpEntity<>(headers), - UpbitDto.MarketResponse[].class - ); - - UpbitDto.MarketResponse[] responseBody = responseEntity.getBody(); - if (responseBody != null) { - for (UpbitDto.MarketResponse marketResponse : responseBody) { - // "KRW-"로 시작하는 가상화폐만 저장 - if (marketResponse.getName().startsWith("KRW-")) { - marketRepository.save(marketResponse.to()); - } + UpbitDto.MarketResponse[] responseBody = upbitApiClient.getAllMarketsAtUpbit(UPBIT_URL_MARKET); + for (UpbitDto.MarketResponse marketResponse : responseBody) { + // "KRW-"로 시작하는 가상화폐만 저장 + if (marketResponse.getName().startsWith("KRW-")) { + marketRepository.save(marketResponse.to()); } - } else { - log.error("Failed to receive market info"); } } } \ No newline at end of file diff --git a/backend/src/main/java/org/dgu/backend/service/UpbitApiClient.java b/backend/src/main/java/org/dgu/backend/service/UpbitApiClient.java new file mode 100644 index 0000000..f57c53c --- /dev/null +++ b/backend/src/main/java/org/dgu/backend/service/UpbitApiClient.java @@ -0,0 +1,97 @@ +package org.dgu.backend.service; + +import lombok.RequiredArgsConstructor; +import org.dgu.backend.dto.UpbitDto; +import org.dgu.backend.exception.UpbitErrorResult; +import org.dgu.backend.exception.UpbitException; +import org.springframework.http.*; +import org.springframework.stereotype.Component; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import java.util.Objects; +import java.util.Optional; + +@Component +@RequiredArgsConstructor +public class UpbitApiClient { + private final RestTemplate restTemplate; + + // HTTP GET 요청을 보내고 결과를 처리하는 메서드 + private T sendHttpGetRequest(String url, Class responseType, Optional token) { + HttpHeaders headers = new HttpHeaders(); + headers.set("accept", MediaType.APPLICATION_JSON_VALUE); + token.ifPresent(t -> headers.add("Authorization", "Bearer " + t)); + + try { + ResponseEntity responseEntity = restTemplate.exchange( + url, + HttpMethod.GET, + new HttpEntity<>(headers), + responseType + ); + if (Objects.isNull(responseEntity.getBody())) { + throw new UpbitException(UpbitErrorResult.FAIL_GET_RESPONSE); + } + return responseEntity.getBody(); + } catch (HttpClientErrorException e) { + if (e.getStatusCode() == HttpStatus.UNAUTHORIZED && e.getResponseBodyAsString().contains("no_authorization_ip")) { + throw new UpbitException(UpbitErrorResult.UNAUTHORIZED_IP); + } else { + throw new UpbitException(UpbitErrorResult.UNAUTHORIZED_KEY); + } + } + } + + // HTTP POST 주문 요청을 보내고 결과를 처리하는 메서드 + private T sendHttpPostRequest(String url, Class responseType, String token, Object requestBody) { + HttpHeaders headers = new HttpHeaders(); + headers.set("accept", MediaType.APPLICATION_JSON_VALUE); + headers.add("Authorization", "Bearer " + token); + headers.setContentType(MediaType.APPLICATION_JSON); + + try { + ResponseEntity responseEntity = restTemplate.exchange( + url, + HttpMethod.POST, + new HttpEntity<>(requestBody, headers), + responseType + ); + if (Objects.isNull(responseEntity.getBody())) { + throw new UpbitException(UpbitErrorResult.FAIL_GET_RESPONSE); + } + return responseEntity.getBody(); + } catch (HttpClientErrorException e) { + if (e.getStatusCode() == HttpStatus.UNAUTHORIZED && e.getResponseBodyAsString().contains("no_authorization_ip")) { + throw new UpbitException(UpbitErrorResult.UNAUTHORIZED_IP); + } else { + throw new UpbitException(UpbitErrorResult.UNAUTHORIZED_KEY); + } + } + } + + // 캔들 차트 조회 업비트 API와 통신하는 메서드 + public UpbitDto.CandleInfoResponse[] getCandleInfoAtUpbit(String url) { + return sendHttpGetRequest(url, UpbitDto.CandleInfoResponse[].class, Optional.empty()); + } + + // 가상화폐 조회 업비트 API와 통신하는 메서드 + public UpbitDto.MarketResponse[] getAllMarketsAtUpbit(String url) { + return sendHttpGetRequest(url, UpbitDto.MarketResponse[].class, Optional.empty()); + } + + // 전체 계좌 조회 업비트 API와 통신하는 메서드 + public UpbitDto.Account[] getUserAccountsAtUpbit(String url, String token) { + return sendHttpGetRequest(url, UpbitDto.Account[].class, Optional.ofNullable(token)); + } + + // 시세 현재가 조회 업비트 API와 통신하는 메서드 + public UpbitDto.Ticker[] getTickerPriceAtUpbit(String url) { + return sendHttpGetRequest(url, UpbitDto.Ticker[].class, Optional.empty()); + } + + // 주문 생성 업비트 API와 통신하는 메서드 + public UpbitDto.OrderResponse[] createNewOrder(String url, String token, UpbitDto.OrderRequest request) { + return sendHttpPostRequest(url, UpbitDto.OrderResponse[].class, token, request); + } +} \ No newline at end of file diff --git a/backend/src/main/java/org/dgu/backend/service/UpbitAutoTrader.java b/backend/src/main/java/org/dgu/backend/service/UpbitAutoTrader.java new file mode 100644 index 0000000..41ccc97 --- /dev/null +++ b/backend/src/main/java/org/dgu/backend/service/UpbitAutoTrader.java @@ -0,0 +1,99 @@ +package org.dgu.backend.service; + +import lombok.RequiredArgsConstructor; +import org.dgu.backend.domain.*; +import org.dgu.backend.dto.UpbitDto; +import org.dgu.backend.exception.PortfolioErrorResult; +import org.dgu.backend.exception.PortfolioException; +import org.dgu.backend.exception.UserErrorResult; +import org.dgu.backend.exception.UserException; +import org.dgu.backend.repository.PortfolioOptionRepository; +import org.dgu.backend.repository.TradingOptionRepository; +import org.dgu.backend.repository.UpbitKeyRepository; +import org.dgu.backend.util.JwtUtil; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Objects; + +@Component +@RequiredArgsConstructor +public class UpbitAutoTrader { + private static final String BIT_COIN_MARKET_NAME = "KRW-BTC"; + @Value("${upbit.url.ticker}") + private String UPBIT_URL_TICKER; + @Value("${upbit.url.order}") + private String UPBIT_URL_ORDER; + private final UpbitApiClient upbitApiClient; + private final JwtUtil jwtUtil; + private final BackTestingCalculator backTestingCalculator; + private final TradingOptionRepository tradingOptionRepository; + private final PortfolioOptionRepository portfolioOptionRepository; + private final UpbitKeyRepository upbitKeyRepository; + + @Scheduled(fixedRate = 60000) // 1분마다 실행 + public void performAutoTrading() { + System.out.println("자동매매 로직 실행 중..."); + + List tradingOptions = tradingOptionRepository.findAll(); + for (TradingOption tradingOption : tradingOptions) { + User user = tradingOption.getUser(); + Portfolio portfolio = tradingOption.getPortfolio(); + PortfolioOption portfolioOption = portfolioOptionRepository.findByPortfolio(portfolio) + .orElseThrow(() -> new PortfolioException(PortfolioErrorResult.NOT_FOUND_PORTFOLIO_OPTIONS)); + // 현재가 조회 + UpbitDto.Ticker[] ticker = upbitApiClient.getTickerPriceAtUpbit(UPBIT_URL_TICKER + BIT_COIN_MARKET_NAME); + BigDecimal curPrice = ticker[0].getPrice(); + executeTrade(user, portfolioOption, tradingOption, curPrice); + } + } + + // 매매 조건을 검토하고 거래를 실행하는 메서드 + public void executeTrade(User user, PortfolioOption portfolioOption, TradingOption tradingOption, BigDecimal curPrice) { + System.out.println("매매 조건을 검토하고 거래를 실행합니다..."); + + BigDecimal curRate = backTestingCalculator.calculateRate(tradingOption.getCurrentCapital(), tradingOption.getInitialCapital(), curPrice, tradingOption.getCoinCount()); + String action = backTestingCalculator.determineAction(curPrice, tradingOption.getAvgPrice(), tradingOption.getTradingCount(), tradingOption.getBuyingCount(), portfolioOption.getBuyingPoint(), portfolioOption.getSellingPoint(), portfolioOption.getStopLossPoint(), curRate); + UpbitKey upbitKey = upbitKeyRepository.findByUser(user); + if (Objects.isNull(upbitKey)) { + throw new UserException(UserErrorResult.NOT_FOUND_KEY); + } + String token = jwtUtil.generateUpbitToken(upbitKey); + + // 매수 처리 + if (action.equals("BUY")) { + BigDecimal numCoins = curPrice.divide(tradingOption.getTradingUnitPrice()); + UpbitDto.OrderRequest orderRequest = UpbitDto.OrderRequest.of(BIT_COIN_MARKET_NAME, "bid", numCoins, curPrice, "limit"); + executeBuy(action, token, orderRequest); + + } + // 익절 처리 + else if (action.equals("SELL")) { + UpbitDto.OrderRequest orderRequest = UpbitDto.OrderRequest.of(BIT_COIN_MARKET_NAME, "ask", tradingOption.getCoinCount(), curPrice, "limit"); + executeSell(action, token, orderRequest); + } + // 손절 처리 + else if (action.equals("STOP_LOSS")) { + UpbitDto.OrderRequest orderRequest = UpbitDto.OrderRequest.of(BIT_COIN_MARKET_NAME, "ask", tradingOption.getCoinCount(), curPrice, "limit"); + executeStopLoss(action, token, orderRequest); + } + } + + // 매수 처리 메서드 + private void executeBuy(String action, String token, UpbitDto.OrderRequest orderRequest) { + upbitApiClient.createNewOrder(UPBIT_URL_ORDER, token, orderRequest); + } + + // 익절 처리 메서드 + private void executeSell(String action, String token, UpbitDto.OrderRequest orderRequest) { + upbitApiClient.createNewOrder(UPBIT_URL_ORDER, token, orderRequest); + } + + // 손절 처리 메서드 + private void executeStopLoss(String action, String token, UpbitDto.OrderRequest orderRequest) { + upbitApiClient.createNewOrder(UPBIT_URL_ORDER, token, orderRequest); + } +} \ No newline at end of file diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 5a1fe35..6e598cb 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -91,4 +91,5 @@ upbit: ticker: ${UPBIT_URL_TICKER} account: ${UPBIT_URL_ACCOUNT} candle-minute: ${UPBIT_URL_CANDLE_MINUTE} - candle-etc: ${UPBIT_URL_CANDLE_ETC} \ No newline at end of file + candle-etc: ${UPBIT_URL_CANDLE_ETC} + order: ${UPBIT_URL_ORDER} \ No newline at end of file