프로젝트

[프로젝트 A] 자동매매 프로그램 만들기 (2. 화면 구축)

제익 2025. 4. 28. 17:45
반응형

이전글 : 

2025.03.02 - [프로젝트] - [프로젝트 A] 자동매매 프로그램 만들기 (1. 기본 준비)

 


이전 글에서는 업비트 API 를 사용해 내 계좌 정보를 가져오는 것까지 완료했다.

이제 MVC패턴으로 코드도 분류하고, 웹 화면도 구축을 해보자

 

일단 메인 페이지에 내가 보유한 코인 정보를 한눈에 볼 수 있도록 화면을 구축했다.

그냥 이클립스로 구축하다가 스프링에 대한 기억을 되살리고자 boot로 진행했다. 

 

패키지 구성은 일단 controller, model로 구분했고, model 안은 dto와 service로 나눴다.

다만, service는 impl을 따로 만들진 않았다.. 

패키지 구성


1. controller 

 일단 메인 페이지에 대한 컨트롤러만 만들었다. 별건 없고, 내 계좌 정보랑 코인 보유 정보만 조회하도록 했다. 그리고 해당 정보를 세션에 올리고 index.html 화면을 띄우는 걸로 설정했다.

package com.autotrading.project.main.controller;

import java.util.Arrays;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import com.autotrading.project.main.model.dto.Market;
import com.autotrading.project.main.model.dto.MyBank;
import com.autotrading.project.main.model.service.MainService;
import com.autotrading.project.order.model.dto.Coin;
import com.google.gson.Gson;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
public class MainController {
	
	@Autowired
	private MainService service;
	
	@RequestMapping("/")
	public String mainForward(Model model) {
		List<MyBank> mybank = null;
		Double myAccount = 0D;
		
		try {
			mybank = service.accounts(); 
			myAccount = service.krwHaving(mybank);
			log.info("보유 코인 정보 : " + new Gson().toJson(mybank));
			log.info("보유 현금 정보 : " + new Gson().toJson(myAccount));

		}catch(Exception e) {
			e.printStackTrace();
		}
		
		model.addAttribute("mybank", mybank);
		
		return "index";
	}
}

 


2. Service

좀더 구축한 뒤에 DB에 데이터를 저장해서 일지를 남기려고 하나, 아직은 화면 구축이 우선이기 때문에 service에서는 api로 보유 자산만 조회하고 controller로 보내준다. 그리고 좀 더 숫자를 이쁘게 보여주기 위한 처리도 해주었다.

package com.autotrading.project.main.model.service;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.Collections;
import java.util.List;

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 org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.UUID;

import com.autotrading.project.main.model.dto.Market;
import com.autotrading.project.main.model.dto.MyBank;
import com.autotrading.project.order.model.dto.Coin;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

@Service
public class MainService {

	@Value("${UPBIT_OPEN_API_SECRET_KEY}")
	private String secretKey;
	
	@Value("${UPBIT_OPEN_API_ACCESS_KEY}")
	private String accessKey;

	@Value("${UPBIT_OPEN_API_SERVER_URL}")
	private String serverUrl;
	
	// 계좌 정보 조회
	public List<MyBank> accounts() {

		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();
			
			List<MyBank> myBankList = new Gson().fromJson(EntityUtils.toString(entity, "UTF-8"), new TypeToken<List<MyBank>>() {}.getType());
			BigDecimal total = BigDecimal.ZERO;

			for (MyBank bank : myBankList) {
			    try {
			    	BigDecimal totalBuy;
			    	BigDecimal avgBuyPrice = new BigDecimal(bank.getAvg_buy_price());
			    	BigDecimal balance = new BigDecimal(bank.getBalance());
			    	
			    	if(bank.getCurrency().equals("KRW")){
			    		totalBuy = balance;
			    	}else {
			    		totalBuy = avgBuyPrice.multiply(balance);
			    	}
			    	
			        totalBuy = totalBuy.setScale(0, RoundingMode.HALF_UP);
			        
			        bank.setKorean_buy_price(new DecimalFormat("#,###").format(totalBuy));
			        total = total.add(totalBuy);
			        bank.setTempRawTotal(totalBuy); // 내부 계산용
			    } catch (NumberFormatException e) {
			        bank.setKorean_buy_price("0");
			        bank.setTempRawTotal(BigDecimal.ZERO);
			    }
			}

			for (MyBank bank : myBankList) {
			    BigDecimal rate = bank.getTempRawTotal()
			            .multiply(BigDecimal.valueOf(100))
			            .divide(total, 1, RoundingMode.HALF_UP);
			    bank.setBuyRate(rate.toPlainString()); // 예: "12.3"
			}
			
			
			return myBankList;

		} catch (IOException e) {
			return null;
		}
	}
	
	// 보유 현금 조회
	public static Double krwHaving(List<MyBank> myBankList) {
		Double krwHaving = myBankList.stream()
			            .filter(bank -> bank.getCurrency().equals("KRW"))
			            .mapToDouble(bank -> Double.parseDouble(bank.getBalance()))
			            .findFirst()
			            .orElse(0.0);
		return krwHaving;
	}

 


3. DTO

DTO는 그냥 업비트 개발자 API에서 응답으로 넘어오는 값들 + 추가로 필요한 정보들을 선언했다.

 

- 계좌 정보 DTO

package com.autotrading.project.main.model.dto;

import java.math.BigDecimal;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class MyBank {
	private String currency;
	private String balance;
	private String locked;
	private String avg_buy_price;
	private boolean avg_buy_price_modified;
	private String unit_currency;
	
	private String korean_name;
	private String korean_buy_price;
	private String buyRate;
	private transient BigDecimal tempRawTotal;

}

 

- 종목 정보 DTO

package com.autotrading.project.main.model.dto;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Market {
		private String market;
		private String korean_name;
		private String english_name;
}

 


4. HTML

이제 localhost로 접속하면 보여줄 화면을 index.html에 구축한다. 태그 구문은 타임리프 사용했고, 일단 되는 거 먼저 확인하고 디자인은 차차 수정하도록 하자.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>My Bank List</title>
    <link rel="stylesheet" th:href="@{/css/main.css}">
</head>

<header>
	<button>보유자산목록</button>
	<button>자동 매매</button>
	
</header>

<body>
    <h1>보유 자산</h1>
    
    <div>
	    <div class="donut" 
	    	style="background:conic-gradient(#FF6384 0% 30%, #36A2EB 30% 100%)">
		</div>
	
	    <div class="legend">
	        <div class="legend-item" th:each="bank, iterStat : ${mybank}">
	            <div class="color-box"></div>
	            <span th:text="${bank.currency} + ' (' + ${bank.buyRate} + '%)'"></span>
	        </div>
	    </div>
	</div>
    
    <table border="1">
        <thead>
            <tr>
                <th>통화</th>
                <th>잔고</th>
                <th>평균매입가</th>
                <th>매수 금액</th>
                <th>한글명</th>
                <th>한글명</th>
            </tr>
        </thead>
        <tbody>
            <tr th:each="bank : ${mybank}">
                <td th:text="${bank.currency}">통화</td>
                <td th:text="${bank.balance}">잔고</td>
                <td th:text="${bank.avg_buy_price}">평균매입가</td>
                <td th:text="${bank.korean_buy_price} + 원">평균매입가</td>
                <td th:text="${bank.korean_name}">한글명</td>
                <td th:text="${bank.buyRate}">한글명</td>
            </tr>
        </tbody>
    </table>
</body>
</html>

 

 


5. 결과 화면

일단.. 디자인은 너무 구리지만 원하는 정보는 다 가져오도록 만들었다. 이제 슬슬 디자인도 하고 자동매매 기능도 추가해보도록 하자.

반응형