일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- ES
- 읽기쉬운코드
- 애자일
- 그리디알고리즘
- database
- 코딩테스트
- Elasticsearch
- 알고리즘
- 그리디
- 개발자
- Java
- framework
- 백준
- 프레임워크
- Baekjoon
- 코딩
- JPA
- 스프링
- spring boot
- Spring
- 애자일기법
- 엘라스틱서치
- 자바
- cleancode
- 애자일프로그래밍
- 클린코드
- API
- 데이터베이스
- 코드
- 개발
- Today
- Total
튼튼발자 개발 성장기🏋️
#4. 코인 자동 매매 프로그램 만들기 본문
※ 본 '코인 자동 매매 프로그램 만들기' 시리즈 포스팅은 개인적인 학습용으로 개발하게 되는 프로그램입니다.
투자의 책임은 투자자 본인에게 있음을 알려드립니다.
다른건 제외하고 핵심인 매수/매도 중에서 포인트만 포스팅을 해볼까 한다.
각 투자자들은 자신만의 매수점과 매도점을 계산할때 사용하는 지표들이 다를 것이다.
나는 참고로 RSI를 좋아해서 RSI를 계산하여 매수점과 매도점을 예측한다.
if(rsi < BUY_RSI_STANDARD) {
// 호가조회
Map<String,String> orderBook = nonAuthAPI.getCoinOrderBook(coinName).get(0);
List<Map<String,String>> orderBookUnits = JsonUtil.jsonString2List(String.valueOf(orderBook.get(APIKeyConst.ORDERBOOK_UNITS))
.replaceAll("\\{", "{\"")
.replaceAll("=", "\":\"")
.replaceAll("[^}], ", "\", \"")
.replaceAll("}", "\"}")
);
// 호가매수
Map<String,String> orderBookUnit = Optional.ofNullable(orderBookUnits)
.map(o -> o.get(2))
.orElseThrow(() -> new NullPointerException(coinName + " - 호가조회 실패"));
double buyPrice = Double.parseDouble(orderBookUnit.get(APIKeyConst.BID_PRICE));
int enableBuyAmount = Calculate.getEnableIntAmount(String.valueOf(authAPI.getMyKRW()), BUY_PERCENTAGE);
if(enableBuyAmount >= MIN_BUY_AMOUNT) {
double count = enableBuyAmount / buyPrice;
Map<String,String> buyResult = JsonUtil.jsonString2Map(authAPI.buy(coinName, String.valueOf(count), String.valueOf(buyPrice)));
String uuid = Optional.ofNullable(buyResult)
.map(b -> b.get(APIKeyConst.UUID))
.orElseThrow(() -> new Exception(coinName + "매수 주문 실패"));
message.append(coinName)
.append(" - ")
.append(Calculate.KRW_FORMAT.format( buyPrice))
.append("원에 ")
.append(count)
.append("개 매수 주문 (총 ")
.append(Calculate.KRW_FORMAT.format(count * buyPrice))
.append("원 매수)\n");
database.saveOrderTable(OrderCoins.builder()
.coin(coinName.substring(4))
.orderDate(new Date())
.uuid(uuid)
.build());
} else {
break;
}
}
한 가지 아쉬운 점은 업비트 API의 response가 json 짭(?)이라는 것이다. 완전히 json 문법에 맞는 형식이 아니라서 body를 파싱해서 사용하려면 replaceAll()을 통해 맞춰줘야한다..
BUY_RSI_STANDARD라고하는 매수 기준 RSI 값을 설정하고 그 값보다 작다면 호가 조회를 통해 선호하는 호가로 매수 주문을 넣는다.
나는 MIN_BUY_AMOUNT보다 적다면 주문을 넣지 않는다. 만약 내가 투자금이 100만원인데 매수 총 금액이 1만원이라면, 의미가 없기 때문에 이 제한을 두었다.
매수 주문을 넣은 후에 데이터베이스에 내가 어떤 거래ID로 어떤 코인을 언제 주문을 넣었는지 저장을 할텐데, 데이터베이스 접근은 async하게 접근한다.
@Async
public void saveOrderTable(OrderCoins order) {
orderRepository.save(order);
}
하나의 스레드 내에서 수행하기엔 불필요하고, 하나의 스레드 내에서 처리한다면 다음 코인의 매수 시점이 계속해서 느려지기 때문이다. (코인 투자를 해보신 분들은 진짜 1분, 1초가 소중하다는 것을 알 것이다.)
if(rsi > SELL_RSI_STANDARD) {
Map<String,String> orderBook = nonAuthAPI.getCoinOrderBook("KRW-" + coin).get(0);
List<Map<String,String>> orderBookUnits = JsonUtil.jsonString2List(String.valueOf(orderBook.get(APIKeyConst.ORDERBOOK_UNITS))
.replaceAll("\\{", "{\"")
.replaceAll("=", "\":\"")
.replaceAll("[^}], ", "\", \"")
.replaceAll("}", "\"}")
);
Map<String,String> orderBookUnit = Optional.ofNullable(orderBookUnits)
.map(o -> o.get(2))
.orElseThrow(() -> new NullPointerException(coin + " - 호가조회 실패"));
String sellPrice = orderBookUnit.get(APIKeyConst.ASK_PRICE);
double count = Calculate.getEnableDoubleAmount(wallet.get(APIKeyConst.BALANCE), SELL_PERCENTAGE);
Map<String,String> sellResult = JsonUtil.jsonString2Map(authAPI.sell("KRW-" + coin, String.valueOf(count), sellPrice));
String uuid = Optional.ofNullable(sellResult)
.map(b -> b.get(APIKeyConst.UUID))
.orElseThrow(() -> new Exception(coin + "매수 주문 실패"));
Map<String,String> market = nonAuthAPI.getCoinDetail(coin).get(0);
double rateOfReturn = Calculate.getRateOfReturn(wallet, market);
message.append(coin)
.append(" - ")
.append(Calculate.KRW_FORMAT.format( Integer.parseInt(sellPrice)))
.append("원에 ")
.append(count)
.append("개 매도 (수익률 : ")
.append(rateOfReturn)
.append("%)\n");
database.saveOrderTable(OrderCoins.builder()
.coin(coin)
.orderDate(new Date())
.uuid(uuid)
.build());
}
SELL_PERCENTAGE라는 녀석으로 가진거에서 몇 퍼센트를 매도할지 계산해서 매도 주문을 넣는다. 예를들어, 내가 100코인을 가지고 있는데 난 항상 매도할떄 30%만 팔겠다고한다면 30코인만 매도가 되고 나머지 60코인은 두 번쨰 매도 스케줄러가 돌때 또 30%로 처리된다. (3번째 매도시에는 전량 매도를 하려 했지만 구현하지 않았다.)
List<OrderCoins> orders = orderRepository.findAll();
for(OrderCoins order : orders) {
try {
Map<String,String> orderDetail = JsonUtil.jsonString2Map(authAPI.getOrderSignDetail(order.getUuid()));
Date orderDate = order.getOrderDate();
Date now = new Date();
long diffSec = (orderDate.getTime() - now.getTime()) / 1000;
final int DELETE_STANDARD = 86400 * 3; // 3일
if(diffSec >= DELETE_STANDARD) {
authAPI.deleteOrder(order.getUuid());
orderRepository.delete(order);
SlackClient.sendSlack(order.getCoin() + " 3일 경과로 인한 주문 취소.");
continue;
}
String remainVolume = Optional.ofNullable(orderDetail).map(o -> o.get(APIKeyConst.REMAINING_VOLUME)).orElseThrow(() -> new NullPointerException());
if(Double.parseDouble(String.valueOf(remainVolume)) == 0.0) {
orderRepository.delete(order);
}
if(orderDetail.get(APIKeyConst.SIDE).equals("bid")) {
// 매수건
MyCoins myCoin = walletRepository.findByCoin(order.getCoin());
if(Objects.isNull(myCoin)) {
// buy 건
if(Objects.isNull(orderDetail.get(APIKeyConst.TRADES))) {
// 체결된게 없는 것
continue;
} else {
// 하나라도 체결이 되었다면
database.saveWalletTable(MyCoins.builder()
.coin(order.getCoin())
.buyCount(1)
.firstBuyKRW((long) (Double.parseDouble(orderDetail.get(APIKeyConst.PRICE)) * Double.parseDouble(orderDetail.get(APIKeyConst.VOLUME))))
.build());
}
} else {
// reBuy 건
myCoin.setBuyCount(myCoin.getBuyCount() + 1);
database.saveWalletTable(myCoin);
}
} else {
// 매도건
MyCoins myCoin = walletRepository.findByCoin(order.getCoin());
database.deleteWalletTable(myCoin);
}
} catch (Exception e) {
log.error ("exception msg", e);
SlackClient.sendSlack(e.toString());
}
}
매수주문과 매도주문 시에 데이터베이스에 쌓은 내용을 기반으로 주문이 체결되었는지 주기적으로 확인이 필요하다.
그래야 진짜 체결이 된 것일테고 내가 코인을 사거나 판게 되니까.
DB에서 나의 매수/매도 주문건을 UUID에 맵핑이 되어 있을 것이다. 체결이 안된 리스트를 전부 READ해와서 각각 매수건인지 재매수건인지 매도건인지 등 분기할 수밖에 없다. 이 부분에 대해서 SQL query(JSP) where절로 다른 리스트를 가지고와서 각각 다른 스레드에서 돌릴까도 생각해보았지만 그러기엔 1-2line을 수행만 해도 되는 부분이라 그냥 분기치는 지저분한 스타일로 개발했다ㅠㅠ(전혀 클린하지 않은 코드 ㅋㅋ)
'프로젝트 > 토이프로젝트' 카테고리의 다른 글
푸르지오 스마트홈 가전제어를 언어로 손쉽게 (ios) (0) | 2025.01.07 |
---|---|
#3. 코인 자동 매매 프로그램 만들기 (0) | 2022.02.07 |
#2. 코인 자동 매매 프로그램 만들기 (0) | 2022.02.07 |
#1. 코인 자동 매매 프로그램 만들기 (0) | 2022.01.28 |