본문 바로가기
Back_end/Api (업비트) 자동매매

[Spring Project] 코인 자동매매 프로그램 만들기 - 3. 업비트 Websocket 클라이언트 + 화면제작

by 8Antony 2022. 10. 24.

 

Websocket 이란?

 

 

실시간 데이터를 받는것 (채팅)

 

 

  • 필요한 라이브러리 추가 

 

 

ticker의 값 중 UUID로 호출하면 됩니다.

파라미터 객체는 어떤 타입(현재가, 체결, 호가)으로 호출하고 어떤 코드(마켓)에 해당하는 데이터를 수신할 것인지를 전달해주어야 합니다.

 

로직은 웹소켓 컨트롤러를 이용해 연결을 하고, 데이터를 화면에 출력

 

@Scheduler 이란?

 

일정한 시간 간격으로, 혹은 특정 일정에 코드가 실행되는 것

 

코인은 매일 9시마다 전일대비%가 업데이트 갱신되기 때문에 스케쥴러를 사용하였습니다.

 

 

 

결과는 0.1초에 한번 upbitMarket 메소드를 실행 

 


 

Websocket 실시간 시세 가져오기 

 

 

1. Task 파일에 내 시세 현재가 불러오기 (Java application)

 

 

@Component
public class Task {		
	
	@Autowired private UpbitService upbitService;
	
	private static int running_cnt = 0;
	private static List<MarketVo> listMarketVo;
	private static Api api;
	private static boolean is_running = false;
	
	@Scheduled(fixedDelay = 100, initialDelay = 0)
	public void upbitMarket() throws IOException {		
		try {
			if(!is_running) {
				is_running = true;
				if(running_cnt == 0) {
					// Cmd 생성
					api = new Api();
					
					// 모든 마켓 정보 수신
					listMarketVo =  api.markets();
				}
				
				// 모든 마켓 실시간 수신
				for (MarketVo v : listMarketVo) {
					if(v.getMarket().substring(0, 3).equals("KRW")) {
						Thread.sleep(100);
						List<TickerVo> listTickerVo = api.ticker(v.getMarket());
						TickerVo tickerVo = listTickerVo.get(0);
					
					}
				}
				
				running_cnt++;
				is_running = false;
			}
		} catch (Exception e) {

		}					
	} 
}

 

 

 

2. WebSocketChatHandler 클래스

 

 

 

@Component
public class WebSockChatHandler extends TextWebSocketHandler {
	private List<WebSocketSession> sessionList = new ArrayList<WebSocketSession>();

	// 클라이언트가 서버로 메세지 전송 처리
	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
		String msg = message.getPayload();
		ChatMsg chatMsg = new Gson().fromJson(msg, ChatMsg.class);
		
		if(chatMsg.getType().equals("market")) {
			Api api = new Api();
			String ret = new Gson().toJson(api.markets());
			for (int i = 0; i < sessionList.size(); i++) {
				WebSocketSession s = sessionList.get(i);
				s.sendMessage(new TextMessage(ret));
			}
		}
		
		if(chatMsg.getType().equals("price")) {
			Api api = new Api();
			String ret = new Gson().toJson(api.markets());
			for (int i = 0; i < sessionList.size(); i++) {
				WebSocketSession s = sessionList.get(i);
				s.sendMessage(new TextMessage(new Gson().toJson(CashMemory.CashMemoryListTickerVo)));
			}
		}
	}

	// 클라이언트가 연결을 끊음 처리
	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {

		// 채팅방에서 퇴장한 사용자 세션을 리스트에서 제거
		sessionList.remove(session);

		// 모든 세션에 채팅 전달
		for (int i = 0; i < sessionList.size(); i++) {
			WebSocketSession s = sessionList.get(i);
			//s.sendMessage(new TextMessage(session.getId() + "님이 퇴장 했습니다."));
		}
	}

 

 

화면에 market, ticker에대한 정보들을 보낼 메소드 생성

 

 

 

 

3. 화면 (jsp) 

 

<script type="text/javascript">

//MarketVO 정보 받기
	function onMessage(msg) {
		var s = '';
		var ob = $.parseJSON(msg.data);
		if(status == 'open'){
			console.log(ob.message);
		}else if(status == 'market'){
			for (var i = 0; i < ob.length; i++) {
				var market = ob[i].market;
				var korean_name = ob[i].korean_name;
				var english_name = ob[i].english_name;
				if(market.substring(0,3) =='KRW'){
					s+='<tr>';
					s+='    <td>'+korean_name+'['+market+']</td>';
					s+='    <td class="'+market+'"><span style ="color:black">0</span></td>';
					s+='    <td class="'+market+'_rate">0</td>';
					s+='</tr>';
				}
			}
			$('tbody').html(s);
			status = 'price';
			setInterval(function() {
				sock.send('{"type":"price"}');
			}, 5000);
       }

 

 

//tickerVO에 대한 정보
        if(isFirstPrice){				
            for (var i = 0; i < tickers.length; i++) {
                var market = tickers[i].market;
                var trade_price = tickers[i].trade_price;
                var signed_change_rate = (Number(tickers[i].signed_change_rate)*100).toFixed(2);

                $('.'+market).html('<span style ="color:black">'+trade_price+'</span>');
                if(signed_change_rate == 0){
                    $('.'+market+'_rate').html('<span style ="color:black">'+signed_change_rate+'%</span>');
                }else if(signed_change_rate > 0){
                    $('.'+market+'_rate').html('<span style ="color:red">+'+signed_change_rate+'%</span>');
                }else{
                    $('.'+market+'_rate').html('<span style ="color:blue">'+signed_change_rate+'%</span>');
                }					
            }
            isFirstPrice = false;
        }

 

 

컨트롤러에서 쏜 MarketVO와 TickerVO를 메소드를 ajax로 받는다. 

 

 

 

4. 출력 결과

 

 

 

 


 

Websocket 내 자산 불러오기 

 

 

1. Test 파일 

 

		Api api = new Api();
		//해당 account 호출문 이용
		List<MyBankVo> listMyBankVo = api.accounts();
        
        
		String markets = "";
		int i = 0;
		List<MarketVo> listMarketVo =  api.markets();
		for (MarketVo v : listMarketVo) {
			if(v.getMarket().substring(0, 3).equals("KRW")) {
				//System.out.println("["+v.getMarket()+"]["+v.getEnglish_name()+"]["+v.getKorean_name()+"]");
				if(i==0) markets += v.getMarket();
				else markets += ","+v.getMarket();
				i++;
			}
		}
		System.out.println(new Gson().toJson(listMyBankVo));

 

 

해당 account 호출 + for 문으로 마켓정보 띄우기

 

 

 

2. Task.java (메모리에 저장된 정보들을 전송)

 

 

	@Scheduled(fixedDelay = 100, initialDelay = 0)
	public void upbitMarket() throws IOException {		
		try {
			if(!is_running) {
				is_running = true;
				if(running_cnt == 0) {
					// Cmd 생성
					api = new Api();
					
					// 모든 마켓 정보 수신
					listMarketVo =  api.markets();
				}
				
				// 나의 계좌 실시간 수신
				List<MyBankVo> accounts = api.accounts();
				double krw_money = 0;
				double market_money = 0;
				for (MyBankVo account : accounts) {
					Thread.sleep(100);
					if(account.getCurrency().equals("KRW")) krw_money = Double.parseDouble(account.getBalance());
					else market_money += Double.parseDouble(account.getBalance()) * Double.parseDouble(account.getAvg_buy_price());
					System.out.println(account.getAvg_buy_price());
				}
				CashMemory.krw_money = new NumberHelper().doubleDotdouble(krw_money,3);
				CashMemory.all_money = new NumberHelper().doubleDotdouble(market_money + krw_money,3);
				
                running_cnt++;
				is_running = false;
			}
		} catch (Exception e) {
			System.out.println(e.toString());
		}					
	}

 

 

 

cashmemory에 나의 계좌 정보 삽입 

 

 

 

3. VO

 

 

public class UpbitClient {
	private double krw_money;
	private double all_money;
	private List<TickerVo> CashMemoryListTickerVo;
	private List<MyBankVo> accounts;

 

 

 

 

4. WebSocketChatHandler 클래스

 

 

	// 클라이언트가 서버로 메세지 전송 처리
	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
		String msg = message.getPayload();
		ChatMsg chatMsg = new Gson().fromJson(msg, ChatMsg.class);
		
		UpbitClient upbitClient = new UpbitClient();
		if(chatMsg.getType().equals("price")) {
			upbitClient.setKrw_money(CashMemory.krw_money);
			upbitClient.setAll_money(CashMemory.all_money);
			upbitClient.setCashMemoryListTickerVo(CashMemory.CashMemoryListTickerVo);
			upbitClient.setAccounts(CashMemory.accounts);
			
			for (int i = 0; i < sessionList.size(); i++) {
				WebSocketSession s = sessionList.get(i);
				s.sendMessage(new TextMessage(new Gson().toJson(upbitClient)));				
			}
		}
     }

 

 

UpbitClient에 대한 정보들을 보낼 메소드 생성

 

 

 

5. 화면 

 

//UpbitclientVO 호출
			var krw_money = ob.krw_money;
			var all_money = ob.all_money;
			var tickers = ob.CashMemoryListTickerVo;
			var accounts = ob.accounts;
			
			$('#krw_money').text('보유 KRW : '+krw_money+' KRW');
			$('#all_money').text('총 보유자산 : '+all_money+' KRW');

 

 

// 나의 코인 정보 MybankVO 호출
			var s = '';
			for (var i = 0; i < accounts.length; i++) {
				var o = accounts[i];
				var avg_buy_price = o.avg_buy_price;
				var avg_buy_price_modified = o.avg_buy_price_modified;
				var balance = o.balance;
				var currency = o.currency;
				var locked = o.locked;
				var unit_currency = o.unit_currency;
				var korean_name = o.korean_name;
				
				if(currency != 'KRW'){
					s+='<div class="card shadow mb-4">';
					s+='    <div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">';
					s+='        <h6 class="m-0 font-weight-bold text-primary">'+unit_currency+'-'+currency+'['+korean_name+']</h6>';
					s+='    </div>';
					s+='    <div class="card-body">';
					s+='        [평가손익:-12.3%][평가금액:-234,340]';
					s+='    </div>';
					s+='    <div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">';
					s+='        <h6 class="m-0 font-weight-bold text-primary">[보유수량:41][매수평균가:178원][매수금액:345,459][평가금액:980,023]</h6>';
					s+='    </div>';
					s+='</div>';
				}
			}
			$('#coin_box').html(s);

 

 

총 보유 자산 계산을 위해 Cashmemory에 담겨있는 unit_currency - currency를 한다. 

 

 

 

6. 출력 결과

 

 

 

 

 

현재 모두 매도처리해서 0원

 


 

Appendix

 

static(정적) 의미 : 정적 필드와 정적 메소드는 객체(인스턴스)에 소속된 멤버가 아니라 클래스에 고정된 멤버입니다.

 

싱글톤이 뭔지? : 싱글톤(Singleton) 패턴의 정의는 객체의 인스턴스가 오직 1개만 생성되는 패턴

 

파라미터 : 

 

즉 메소드 입력값

 

댓글