개발 이야기/코인 자동 매매 프로그램

업비트 API를 이용하여 코인 자동 매매 프로그램 개발하기 - 전체 계좌 조회 [Spring]

제이온 (J.ON) 2021. 10. 4.

안녕하세요? 제이온입니다.

오늘은 업비트 개발자 센터를 접속하여 API 문서를 참고하고, 그 내용에 맞게 전체 계좌를 조회하는 API를 구축해 보겠습니다. 여기서 전체 계좌를 조회한다는 뜻은 "내가 보유한 자산 리스트"를 보여준다는 의미입니다. 즉, 본인이 어떤 코인들을 갖고 있나 조회하는 기능이라고 생각하시면 됩니다.

업비트 개발자 센터 접속하기

이 링크를 클릭하여 업비트 개발자 센터에 접속해 봅시다.



앞으로는 거의 이 홈페이지를 많이 방문하게 될 것입니다. 위의 세부 메뉴 중에서 "API Reference"를 클릭합니다.



각종 API 목록이 나와 있고, 예제 코드까지 참고하실 수 있습니다. 이번 시간에는 첫 번째 API인 전체 계좌 조회 기능을 구현해 보겠습니다.

전체 계좌 조회하기

전체 계좌를 조회하기 위해서는 GET 방식으로 JWT 토큰을 담아서 'https://api.upbit.com/v1/accounts'로 요청을 보내면 됩니다. 그리고 아래는 업비트에서 제공하는 Java 예제 코드입니다.

package main; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.util.EntityUtils; import java.io.IOException; import java.util.UUID; public class GetAccounts { public static void main(String[] args) { String accessKey = System.getenv("UPBIT_OPEN_API_ACCESS_KEY"); String secretKey = System.getenv("UPBIT_OPEN_API_SECRET_KEY"); String serverUrl = System.getenv("UPBIT_OPEN_API_SERVER_URL"); Algorithm algorithm = Algorithm.HMAC256(secretKey); String jwtToken = JWT.create() .withClaim("access_key", accessKey) .withClaim("nonce", UUID.randomUUID().toString()) .sign(algorithm); String authenticationToken = "Bearer " + jwtToken; try { HttpClient client = HttpClientBuilder.create().build(); HttpGet request = new HttpGet(serverUrl + "/v1/accounts"); request.setHeader("Content-Type", "application/json"); request.addHeader("Authorization", authenticationToken); HttpResponse response = client.execute(request); HttpEntity entity = response.getEntity(); System.out.println(EntityUtils.toString(entity, "UTF-8")); } catch (IOException e) { e.printStackTrace(); } } }



이것저것 코드가 나와 있는데, 위 코드를 그대로 쓸 것은 아니고 적절히 역할을 객체에게 분배하고 좀 더 최신 기술을 사용할 것입니다. 그 전에 먼저 해야할 일이 있습니다.

서브 모듈을 활용하여 Key 값 관리하기

Spring에서는 주로 설정 값을 application.yml에 저장하는데, 이를 그대로 깃허브에 노출하게 되면 다른 사람들이 보안에 취약해질 수 밖에 없습니다. 물론, .gitingore에 application.yml을 등록하여 깃허브에 올라가지 않도록 막을 수 있지만, 팀원끼리 값을 공유해야하거나 CI / CD할 때 시크릿 키를 따로 등록해야 하는등 조금 번거로운 단점이 있습니다. 그래서 저는 Git의 서브 모듈을 통해 따로 환경 설정만 다루는 저장소를 만들려고 합니다.

우선, 깃허브에 접속하여 적당히 private 저장소를 하나 만들어 줍니다. 그리고 해당 저장소에 액세스 키와 시크릿 키를 기록한 application.yml 파일만 따로 push 합니다. 다음으로, Spring을 사용하는 프로젝트의 /src/main/.../resources 경로에 들어가서 아래와 같은 명령어를 입력합니다.

git submodule add [private 저장소의 경로] config

 



그러면 위와 같이 config라는 디렉토리가 생기며, 해당 디렉토리와 private 저장소와 연동되어 있다는 사실을 알 수 있습니다. 그리고 우리의 프로젝트에서는 이 application.yml을 마음대로 사용이 가능합니다.

JWT 토큰 생성하기

이제 인증 헤더에 담을 JWT 토큰을 생성해 봅시다. 이 토큰을 만들기 위해서는 액세스 키와 시크릿 키가 필요한데, 이 작업때문에 위의 서브 모듈 작업을 수행한 것입니다. 아래 코드를 /src/main/.../auth/infrastructure에 작성합니다. 패키지 구조는 자유롭게 만드시면 됩니다.

@Component public class JwtTokenProvider { @Value("${security.access-key}") private String accessKey; @Value("${security.secret-key}") private String secretKey; public String createToken() { final Algorithm algorithm = Algorithm.HMAC256(secretKey); return JWT.create() .withClaim("access_key", accessKey) .withClaim("nonce", UUID.randomUUID().toString()) .sign(algorithm); } }



System.getenv() 메소드 대신에 @Value을 이용하여 application.yml에 있는 값을 매핑하였습니다. 그리고 토큰을 만드는 로직을 그대로 가져왔습니다. 다만, 해당 로직을 사용하기 위해서는 jwt 의존성이 필요한데, build.gradle에 아래와 같은 의존성이 있어야 합니다.

 

implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'com.auth0:java-jwt:3.16.0'

 

 

업비트 API 서버에 요청 보내기

업비트 API 서버에 요청을 보내서 전체 계좌 조회 응답을 받기 위한 사전 작업은 끝났습니다. 이제, 헤더에 JWT 토큰을 담아서 주어진 경로로 요청을 보내면 됩니다. 다만, 업비트 측에서 제공한 예제 코드의 HttpClient는 사용하지 않고, RestTemplate을 사용할 것입니다. 먼저, 핵심이 되는 코드부터 봅시다.

@RequiredArgsConstructor @Component public class AccountClient { private final JwtTokenProvider jwtTokenProvider; private final RestTemplate restTemplate; public List<AccountResponse> getAccounts() { HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(jwtTokenProvider.createToken()); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity<?> entity = new HttpEntity<>(headers); return restTemplate.exchange(UPBIT_GET_ACCOUNTS_URL, HttpMethod.GET, entity, new ParameterizedTypeReference<List<AccountResponse>>() { }).getBody(); } }



AccountClient에서 실제 요청을 합니다. RestTemplate의 exchage() 메소드를 이용하여 헤더에 JWT 토큰을 담고 GET 방식으로 전체 계좌 조회하기 관련 링크로 요청을 보내면, 적절한 응답을 받습니다. 그리고 그 응답은 'List<>'와 같이 제네릭을 바로 사용할 수 없으므로 ParameterizedTypeReference의 도움을 받았습니다. 이 객체는 Spring 3.2 이상부터 사용이 가능합니다.

AccountClient에서 RestTemplate를 사용하려면 RestTemplate을 빈으로 등록해야 합니다. 아래와 같이 Configuration 클래스를 만들면 됩니다.

@Configuration public class UpBitApiConfig { @Bean public RestTemplate getRestTemplate() { RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); return restTemplateBuilder.build(); } }



업비트 API의 응답값을 매핑하기 이해 Dto를 정의하였습니다.

@Getter @Builder @AllArgsConstructor @NoArgsConstructor public class AccountResponse { private String currency; private String balance; private String locked; @JsonProperty("avg_buy_price") private String avgBuyPrice; @JsonProperty("avg_buy_price_modified") private Boolean avgBuyPriceModified; @JsonProperty("unit_currency") private String unitCurrency; }



참고로, 응답값에는 snake case로 만들어진 변수가 있어서 @JsonProperty를 이용하여 camel case로 바꿔주었습니다. 결과적으로 아래와 같은 패키지에 객체들이 있으면 됩니다.



마지막으로 Controller을 정의합시다. 위의 사진에서 /account 아래의 ui 패키지를 만들고, AccountController 객체를 만들어 줍니다.

@RequiredArgsConstructor @RestController public class AccountController { private final AccountClient accountClient; @GetMapping("/accounts") public ResponseEntity<List<AccountResponse>> showAllAccounts() { return ResponseEntity.ok(accountClient.getAccounts()); } }



만약에 업비트에서 제공받는 응답을 수정할 필요가 있다면, Service layer 단을 두어서 커스텀한 응답을 만들어서 반환할 것 같습니다. 하지만, 현재 그럴 필요는 없어서 심플하게 코드를 작성했습니다.

실행 결과



아래는 제 실제 자산 리스트입니다.



예전에 업비트 가입해서 받은 비트코인에 대한 정보가 api의 응답으로 잘 날아온 것을 확인하실 수 있습니다. 다만, 매수 금액, 평가 금액, 평가 손익과 관련된 부분은 응답으로 받지 못하는 것이 아쉽습니다. 만약 해당 값들이 필요해지면 그 때가서 방법을 찾아보겠습니다.

테스트해 보기

지금까지 작업한 내용을 테스트하려면 직접 업비트 api 서버에 요청을 보내는 방법 밖에 없습니다. 하지만, 업비트 api 서버에 문제가 생긴다거나, 초당 요청 횟수를 초과하여 요청하는 작업을 한다면 IP를 밴 당할 수도 있습니다. 그래서 업비트 api 서버를 mock 서버로 만들어서 테스트를 해야 합니다.

@AutoConfigureWebClient(registerRestTemplate = true) @RestClientTest(AccountClient.class) class AccountClientTest { private final List<AccountResponse> accountResponses = Arrays.asList( AccountResponse .builder() .currency("KRW") .balance("1000000.0") .locked("0.0") .avgBuyPrice("0") .avgBuyPriceModified(false) .unitCurrency("KRW") .build(), AccountResponse .builder() .currency("BTC") .balance("2.0") .locked("0.0") .avgBuyPrice("101000") .avgBuyPriceModified(false) .unitCurrency("KRW") .build() ); @MockBean private JwtTokenProvider jwtTokenProvider; @Autowired private AccountClient accountClient; @Autowired private MockRestServiceServer mockServer; @Autowired private ObjectMapper objectMapper; @DisplayName("업비트의 모든 계좌 정보를 가져온다.") @Test void getAccounts() throws JsonProcessingException { String expectedResult = objectMapper.writeValueAsString(accountResponses); mockServer.expect(requestTo(UPBIT_GET_ACCOUNTS_URL)) .andRespond(withSuccess(expectedResult, MediaType.APPLICATION_JSON)); List<AccountResponse> actual = accountClient.getAccounts(); assertThat(actual).hasSize(accountResponses.size()); } }



주로 RestTemplate를 사용할 때는 RestClientTest를 많이 사용합니다. RestClientTest는 Gson이나 Jackson과 같은 클래스를 빈으로 등록하고 RestTemplate를 사용하고 있는 객체를 빈으로 등록해 줍니다. 다만, RestTemplate 자체는 빈으로 등록해 주지 않아서 `@AutoConfigureWebClient(registerRestTemplate = true)` 속성을 넣어주어야 합니다.

이후 테스트는 mock 서버에 대해 원하는 결과 값을 미리 정의해 주고, mock 서버로 요청을 보내서 우리가 정의한 결과 값이랑 동일하게 나오는지 수행하는 방식으로 작성합니다. 참고로, AccountClient 내부에는 JwtTokenProvider가 존재하고, 이것은 @Component 방식으로 빈 등록하는 방식이므로 RestClientTest 수행 시 JwtTokenProvider가 빈으로 등록되지 않습니다. 따라서, 이럴 때는 @MockBean을 사용하여 해당 객체를 빈으로 등록하고, 해당 객체의 로직이 필요하다면 직접 정의해 주어야 합니다.

다만, RestClientTest는 특정 요청에 대해 응답을 정해주는 방법은 있지만 헤더를 검증하는 방법은 없는 것으로 알고 있습니다. (제가 못 찾은 거다보니까 방법이 있다면 댓글 부탁드립니다.) 그래서 JWT 토큰이 유효하지 않을 때 테스트하고 싶으시다면, JwtTokenProviderTest를 따로 만드는 것을 추천드립니다.

정리

지금까지 업비트 api를 활용하여 전체 계좌 정보를 가져오는 방법을 구현해 보았습니다. 크게 크게 기록하다보니까 이해가 잘 안가는 부분은 댓글로 남겨주시면 설명해 드리겠습니다.

Github 저장소 주소

아래 링크에서 개발을 진행하여 버전 관리를 수행하고 있습니다.

 

GitHub - pjy1368/coin-trading-used-upbit-api

Contribute to pjy1368/coin-trading-used-upbit-api development by creating an account on GitHub.

github.com

추천 글