[자동매매, SpringBoot] 3. 매도, 매수
2024.05.11 - [프로젝트/SpringBoot 업비트 자동매매 프로젝트] - [자동매매, SpringBoot] 2. 계좌 조회
이전 글에서 계좌 조회 기능을 만들었고, 매도 매수 기능을 추가 할 것이다.
업비트 개발자센터에서 API Reference를 보면
https://api.upbit.com/v1/orders 로 POST 요청을 보내 매도, 매수요청을 할 수 있고
Request Parameter 중 side 파라미터로 매수/매도 를 구분 한다.
나는 직관적으로 매수 / 매도 함수를 나눠서 만들것이다.
1. Util Class
매수 / 매도 요청을 보낼 때 필요한 Jwt Token에는 클레임에 query_hash, query_hash_alg 를 포함해야 한다.
기존에 만든 Util Class에 query_hash, query_hash_alg를 매개변수로 받는 generateAuthenticationToken를 추가해준다.
public String generateAuthenticationToken(String queryHash, String queryHashAlg) {
String ACCESS_KEY = config.getACCESS_KEY();
String SECRET_KEY = config.getSECRET_KEY();
Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
String jwtToken = JWT.create()
.withClaim("access_key", ACCESS_KEY)
.withClaim("nonce", UUID.randomUUID().toString())
.withClaim("query_hash", queryHash)
.withClaim("query_hash_alg", queryHashAlg)
.sign(algorithm);
return "Bearer " + jwtToken;
}
2. OrderService
주문 관련 서비스들은 OrderService에 추가 할 것이다.
매수 / 매도 둘 다 https://api.upbit.com/v1/orders 로 POST 요청을 보내야하는데
Request Param 에 side 로 매수 / 매도를 구분한다. 함수 내에서 구분해도 되지만
매수 함수인 bid, 매도 함수인 ask 로 나누어 추가했다.
매도는 시장가로 할 것이기 때문에 ord_type을 market으로 지정해주었다.
요청 header에는 위에서 추가한 Jwt Token을 담아서 날린다.
bid 함수 맨 위에 MIN_ORDER_AMOUNT는 주문 최소 금액을 Config에 정의해 놓은것인데 업비트는 주문 최소 금액이 5000원이다.
예외처리는 나중에 더 신경 쓰는 걸로..
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {
private final Util util;
private final RestTemplate restTemplate;
private final AccountService accountService;
/**
* 매수
*/
public void bid(String market, String price) throws NoSuchAlgorithmException, UnsupportedEncodingException {
if(Double.parseDouble(price) < Config.MIN_ORDER_AMOUNT) {
log.info("최소 주문 금액은 " + Config.MIN_ORDER_AMOUNT + " 입니다.");
return;
}
HashMap<String, String> params = new HashMap<>();
params.put("market", market);
params.put("side", "bid");
params.put("price", price);
params.put("ord_type", "price");
ArrayList<String> queryElements = new ArrayList<>();
for(Map.Entry<String, String> entity : params.entrySet()) {
queryElements.add(entity.getKey() + "=" + entity.getValue());
}
String queryString = String.join("&", queryElements.toArray(new String[0]));
MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(queryString.getBytes("UTF-8"));
String queryHash = String.format("%0128x", new BigInteger(1, md.digest()));
String authenticationToken = util.generateAuthenticationToken(queryHash, "SHA512");
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", authenticationToken);
headers.set("Content-Type", "application/json");
HttpEntity request = new HttpEntity(new Gson().toJson(params), headers);
ResponseEntity<String> response = restTemplate.exchange(SERVER_URL + "/v1/orders", HttpMethod.POST, request, String.class);
log.info("[" + market + "] 매수, 금액 : " + price);
}
/**
* 매도
*/
public void ask(String market, String volume) throws NoSuchAlgorithmException, UnsupportedEncodingException, JsonProcessingException {
double balance = accountService.getBalance(market);
if(balance < Double.parseDouble(volume)) {
log.info("매도 요청 수량이 보유 수량보다 많습니다. 보유 수량 : " + balance + ", 매도 요청 수량 : " + volume);
return;
}
HashMap<String, String> params = new HashMap<>();
params.put("market", market);
params.put("side", "ask");
params.put("ord_type", "market");
params.put("volume", volume);
ArrayList<String> queryElements = new ArrayList<>();
for(Map.Entry<String, String> entity : params.entrySet()) {
queryElements.add(entity.getKey() + "=" + entity.getValue());
}
String queryString = String.join("&", queryElements.toArray(new String[0]));
MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(queryString.getBytes("UTF-8"));
String queryHash = String.format("%0128x", new BigInteger(1, md.digest()));
String authenticationToken = util.generateAuthenticationToken(queryHash, "SHA512");
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", authenticationToken);
headers.set("Content-Type", "application/json");
HttpEntity request = new HttpEntity(new Gson().toJson(params), headers);
ResponseEntity<String> response = restTemplate.exchange(SERVER_URL + "/v1/orders", HttpMethod.POST, request, String.class);
log.info("[" + market + "] : 매도, 수량 : " + volume);
}
}