카테고리 없음

Part 5. 수익형 웹 서비스 구현하기(4) - 주식 투자 포트폴리오 만들기

digital520 2024. 4. 4. 17:28

5-1. 주식 투자 포트폴리오 미리보기

 

5-2. 주식 투자 포트폴리오의 프로그래밍적 원리 이해하기

Dictionary 자료형

JavaScript에는 DIctionary(딕셔너리, 사전)라는 자료형이 있습니다. 딕셔너리는 비슷한 형태의 많은 값들을 저장할 때 유리합니다. 우리는 여러 주식 종목을 딕셔너리를 사용해서 저장하려고 해요. 딕셔너리는 아래와 같이 사용합니다.

종목 평단가 보유수량

 

var stocks = {
    "삼성전자": {
        price: 20000,
        amount: 2
    },
    "아마존": {
        price: 30000,
        amount: 3
    },
    "애플": {
        price: 40000,
        amount: 4
    },
};

 

딕셔너리에 저장되어 있는 값을 가져오려면 다음과 같이 코드를 작성합니다. 만약 삼성전자의 정보를 가져오고 싶다면:

console.log(stocks["삼성전자"]);
// {price: 20000, amount: 2}

 

삼성전자의 주식 가격과 수량을 가져오고 싶다면 아래와 같이 작성합니다.

console.log(stocks["삼성전자"]["price"]);
// 20000
console.log(stocks["삼성전자"]["amount"]);
// 2

 

딕셔너리에 저장된 값을 변경하는 것도 가능합니다. 만약 애플 주식의 보유수량을 변경하고 싶다면 아래와 같이 작성해요.

stocks["애플"]["amount"] = 10;

console.log(stocks["애플"])

// {price: 40000, amount: 10}

 

총 보유주식 평가금액 계산하기

우리가 보유한 주식은 모두 딕셔너리에 저장되어 있습니다. 우리가 보유한 주식의 총 평가금액을 계산하기 위해서는 아래와 같은 코드를 작성합니다.

stocksPrice =
    stocks["삼성전자"]["price"] * stocks["삼성전자"]["amount"]
    + stocks["아마존"]["price"] * stocks["아마존"]["amount"]
    + stocks["애플"]["price"] * stocks["애플"]["amount"];

console.log(stocksPrice);

 

[심화] for 반복문 사용하기

위의 방법은 정말 직관적이지만 종목이 추가될 때마다 코드가 계속 길어진다는 단점이 있습니다. 그리고 종목 이름만 바뀔 뿐 연산하는 순서와 구조는 종목마다 완전히 동일하죠.

위의 코드를 사용해도 무관하나 조금 더 편한 방법은 JavaScript의 for 구문을 사용하는 것입니다. 아래처럼요.

var stocksPrice; // 총 보유주식 평가금액

stocksPrice = 0; // 0원 초기화

for (var event in stocks) { // 종목 개수만큼 아래 구문 반복
    var stock = stocks[event]; // 종목 선택

    stocksPrice = stocksPrice + stock["price"] * stock["amount"]; // 가격 계산 및 합산
}

console.log(stocksPrice); // 결과 출력

 

for 구문은 종목 단위로 순차적으로 계산합니다. 위 예제에서는 for 구문 안에 있는 코드가 종목 개수만큼 3번 반복됩니다. 차례로 한 종목씩 가격을 계산해 stocksPrice에 더해주고, 마지막에 stocksPrice 를 출력하면 보유 주식의 총 평가금액을 구할 수 있죠!

함수(Function) 사용하기

프로그래머들은 반복적인 일을 매우 싫어하여 함수라는 개념을 만들어 함수를 재활용합니다. 우리는 앞 파트에서도 이미 함수들을 사용하고 있었는데요. 조금만 더 깊게 공부해보죠.

function plus(a, b) {
		return a + b;
}

var result = plus(2, 3);

console.log(result);

 

위의 코드는 a와 b를 더해 반환하는 간단한 함수를 만들었습니다. 우리는 plus()라는 함수에 a=2, b=3 을 넣어 a와 b를 더하게끔 할 수 있죠. a와 b에 어떤 값을 넣을지는 여러분의 자유입니다.

5-3. HTML로 포트폴리오 틀 만들기

보일러플레이트

새로운 프로젝트를 시작할 때는 항상 보일러플레이트 코드부터 시작합니다.

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Hello, world!</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <main class="container">
        <h1>Hello, world!</h1>
    </main>

    <script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"></script>
    <script src="script.js"></script>
</body>
</html>

 

script.js

$(document).ready(function() {

});

 

style.css

/* 비어있음 */

 

현황판 만들기

보유자산, 평가손익, 수익률을 한 눈에 볼 수 있는 현황판을 작성합니다.

<h1>주식투자 포트폴리오</h1>

<div>총 보유자산 <span id="total">0</span> 원</div>

<div>총 보유현금 <span id="cash">0</span> 원</div>

<div>총 보유주식 <span id="stocks-price">0</span> 원</div>

<div>평가손익 <span id="profit">0</span> 원</div>

<div>수익률 <span id="percentage">0</span> %</div>

 

거래 기록 만들기

거래기록은 테이블(표)로 만들어주겠습니다. Bootstrap 문서를 참고해주세요.

Tables 문서: https://getbootstrap.com/docs/5.2/content/tables/

 

Tables

Documentation and examples for opt-in styling of tables (given their prevalent use in JavaScript plugins) with Bootstrap.

getbootstrap.com

💡 table class 안에 table-striped, table-hover를 넣어 더 예쁘게 꾸며줍니다. 문서에 보면 다양한 옵션들이 많아요!

<table class="table table-striped table-hover">
    <thead>
        <tr>
            <th scope="col">시간</th>
            <th scope="col">종목</th>
            <th scope="col">거래</th>
            <th scope="col">평단가</th>
            <th scope="col">수량</th>
            <th scope="col">평가금액</th>
        </tr>
    </thead>
    <tbody id="trade-history">
        <tr>
            <th scope="row">2022-11-17 12:00:00</th>
            <td>삼성전자</td>
            <td>매도</td>
            <td>10,000</td>
            <td>2</td>
            <td>20,000</td>
        </tr>
    </tbody>
</table>

 

사용자 입력 구현하기

사용자가 입력해야하는 항목은 아래와 같습니다.

  1. 거래 날짜와 시간 (datetime-local)
  2. 거래 종목 (select box)
  3. 평단가 (매수 평균가격, 매도 평균가격) (text)
  4. 거래 수량 (text)
  5. 매수 또는 매도 (버튼)
<input type="datetime-local" id="datetime" />

<select id="event">
    <option value="삼성전자">삼성전자</option>
    <option value="아마존">아마존</option>
    <option value="애플">애플</option>
</select>

<input type="text" id="price" placeholder="평단가" />
<input type="text" id="amount" placeholder="수량" />

<button type="button" id="buy">매수</button>
<button type="button" id="sell">매도</button>

 

전체 코드

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>주식투자 포트폴리오</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <main class="container">
        <h1>주식투자 포트폴리오</h1>

        <div>총 보유자산 <span id="total">0</span> 원</div>

        <div>총 보유현금 <span id="cash">0</span> 원</div>

        <div>총 보유주식 <span id="stocks-price">0</span> 원</div>

        <div>평가손익 <span id="profit">0</span> 원</div>

        <div>수익률 <span id="percentage">0</span> %</div>

        <table class="table table-striped table-hover">
            <thead>
                <tr>
                    <th scope="col">시간</th>
                    <th scope="col">종목</th>
                    <th scope="col">거래</th>
                    <th scope="col">평단가</th>
                    <th scope="col">수량</th>
                    <th scope="col">평가금액</th>
                </tr>
            </thead>
            <tbody id="trade-history">
                <tr>
                    <th scope="row">2022-11-17 12:00:00</th>
                    <td>삼성전자</td>
                    <td>매도</td>
                    <td>10,000</td>
                    <td>2</td>
                    <td>20,000</td>
                </tr>
            </tbody>
        </table>

        <input type="datetime-local" id="datetime" />

        <select id="event">
            <option value="삼성전자">삼성전자</option>
            <option value="아마존">아마존</option>
            <option value="애플">애플</option>
        </select>

        <input type="text" id="price" placeholder="평단가" />
        <input type="text" id="amount" placeholder="수량" />

        <button type="button" id="buy">매수</button>
        <button type="button" id="sell">매도</button>
    </main>

    <script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"></script>
    <script src="script.js"></script>
</body>
</html>

 

5-4. 포트폴리오 로직 만들기 1

초기 변수 지정하기

먼저 전역적으로 자주 사용할 변수들을 정의합니다.

  • initCash(최초 현금 보유량): 사용자가 최초 100만원의 현금으로 투자를 시작한다고 가정
  • initStocks(최초 주식 보유량): 사용자가 보유한 주식은 0개라고 가정
  • stocks(총 주식 보유량): 사용자가 현재 보유하고 있는 주식 수. initStock과 같은 형태를 가진다.
  • cash(총 현금 보유량): 사용자가 현재 보유한 현금
  • stocksPrice(총 보유주식 평가금액): 사용자가 보유한 주식을 현금 가치로 환산한 값
  • total(총 보유자산): 사용자가 보유한 현금과 사용자가 보유한 주식의 현금 가치의 합
  • tradeHistory(거래 기록): 사용자가 거래한 기록을 표 형태로 표시
var initCash = 1000000; // 최초 현금 보유량
var initStocks = {
    "삼성전자": {
        price: 0,
        amount: 0
    },
    "아마존": {
        price: 0,
        amount: 0
    },
    "애플": {
        price: 0,
        amount: 0
    },
}; // 최초 주식 보유량

var stocks; // 총 주식 보유량
var cash; // 총 현금 보유량
var stocksPrice; // 총 보유주식 평가금액
var total; // 총 보유자산 (현금 + 주식)
var tradeHistory; // 거래 기록

 

자주 사용할 함수 정의

앞으로 자주 사용할 코드를 함수로 작성하여 사용하기 간편하게 묶어주겠습니다.

  1. getStocksPrice() - 총 보유주식 평가금액을 계산하는 함수 (아래 두 방법 중 하나 선택)

a. for 구문을 사용하지 않은 경우

function getStocksPrice() {
		stocksPrice =
		    stocks["삼성전자"]["price"] * stocks["삼성전자"]["amount"]
		    + stocks["아마존"]["price"] * stocks["아마존"]["amount"]
		    + stocks["애플"]["price"] * stocks["애플"]["amount"];
}

 

b. for 구문을 사용한 경우

function getStocksPrice() {
    stocksPrice = 0;

    for (var event in stocks) {
        var stock = stocks[event];

        stocksPrice = stocksPrice + stock["price"] * stock["amount"];
    }
}

 

2.setStock(event, price, amount) - 거래가 발생하면 주식 평단가와 보유수량을 변경하여 딕셔너리와 로컬스토리지에 저장하는 함수

💡 로컬스토리지에는 문자열(string)만 저장할 수 있기 때문에 JSON.stringify() 함수를 사용하여 딕셔너리를 문자열로 변환합니다.

function setStock(event, price, amount) {
    var stock = stocks[event];

    stocks[event] = {
        price: price,
        amount: amount + stock["amount"]
    }

    localStorage.setItem("stocks", JSON.stringify(stocks));
}

 

3. showResult() - 현황판에 표시되는 값을 표시하는 함수

function showResult() {
    total = cash + stocksPrice;

    var profit = total - initCash;
    var percentage = profit / initCash * 100;

    percentage = Math.round(percentage);

    $("#total").html(total.toLocaleString());
    $("#cash").html(cash.toLocaleString());
    $("#stocks-price").html(stocksPrice.toLocaleString());
    $("#profit").html(profit.toLocaleString());
    $("#percentage").html(percentage);
    $("#trade-history").html(tradeHistory);
}

 

5-5. 포트폴리오 로직 만들기 2

로컬스토리지에 저장된 값 불러오기

우리는 로컬스토리지에 아래의 값을 저장하여 사용하려고 합니다.

  1. stocks: 사용자가 보유한 주식 수
  2. cash: 사용자가 보유한 현금
  3. tradeHistory: 사용자의 거래 기록 (표 형태)

이미 값들이 로컬스토리지에 저장되어 있다면 저장된 값을 사용하고 저장된 값이 없다면 초기값을 사용하도록 코드를 작성합니다.

💡 stocks 딕셔너리를 로컬스토리지에 저장할 때 JSON.stringify()를 사용하여 문자열로 저장했습니다. 저장된 값을 불러오면 문자열 값으로 불러오기 때문에 문자열을 다시 딕셔너리로 변경하기 위해 JSON.parse() 함수를 사용합니다.

  • JSON.stringify(): 딕셔너리 → 문자열
  • JSON.parse(): 문자열 → 딕셔너리
$(document).ready(function() {
    stocks = JSON.parse(localStorage.getItem("stocks"));

    if (stocks == null) {
        stocks = initStocks;
    }

    cash = localStorage.getItem("cash");

    if (cash == null) {
        cash = initCash;
    }

    cash = parseFloat(cash);

    tradeHistory = localStorage.getItem("tradeHistory");

    if (tradeHistory == null) {
        tradeHistory = "";
    }
});

 

현황판 보여주기

위에서 작성한 함수를 사용하여 총 보유주식 평가금액을 계산하고 현황판에 결과를 표시합니다. 아직 거래가 없어 0으로 표시될 거에요.

getStocksPrice();
showResult();

 

버튼 클릭 함수 연결하기

매수, 매도 버튼을 누르면 실행할 함수를 만들고 연결합니다.

$("#buy").click(buyHandler);
$("#sell").click(sellHandler);

 

[매수] 사용자 입력값 받아오기

  • datetime: 날짜와 시간
  • event: 종목
  • price: 평단가
  • amount: 수량
function buyHandler() {
		var datetime = $("#datetime").val();
    var event = $("#event").val();
    var price = $("#price").val();
    var amount = $("#amount").val();

    price = parseFloat(price);
    amount = parseFloat(amount);
}

 

[매수] 보유 현금 업데이트하기

사용자가 구매한 주식의 총 가격은 평단가(price) * 수량(amount) 입니다. 사용자가 가진 현금에서 매수한 가격만큼 빼서 cash에 저장합니다.

var buyPrice = price * amount;

cash = cash - buyPrice;

localStorage.setItem("cash", cash);

 

[매수] 거래 기록 업데이트하기

거래 기록은 표 형태로 나타내야하기 때문에 아래와 같이 백틱을 사용해서 tr 태그 안쪽을 작성합니다. 과거 기록을 누적시키기 위해 tradeHistory를 더해주는 것도 잊지말구요.

마지막으로 거래 기록 또한 로컬스토리지에 저장합니다.

tradeHistory = `<tr>
    <th scope="row">${datetime}</th>
    <td>${event}</td>
    <td>매수</td>
    <td>${price.toLocaleString()}</td>
    <td>${amount}</td>
    <td>${buyPrice.toLocaleString()}</td>
</tr>` + tradeHistory;

localStorage.setItem("tradeHistory", tradeHistory);

 

[매수] 주식 보유량 업데이트하기

사용자가 매수한 종목, 평단가, 수량을 setStock() 함수에 넘겨 평단가와 보유 수량을 업데이트합니다.

setStock(event, price, amount);

 

[매수] 현황판 업데이트하기

현금, 주식보유수에 따라 현황판도 함께 업데이트합니다.

getStocksPrice();
showResult();

 

[매도] 사용자 입력값 받아오기

  • datetime: 날짜와 시간
  • event: 종목
  • price: 평단가
  • amount: 수량
var datetime = $("#datetime").val();
var event = $("#event").val();
var price = $("#price").val();
var amount = $("#amount").val();

price = parseFloat(price);
amount = parseFloat(amount);

 

[매도] 보유 현금 업데이트하기

사용자가 판매한 주식의 총 가격은 평단가(price) * 수량(amount) 입니다. 사용자가 가진 현금에서 매도한 가격만큼 더해서 cash에 저장합니다.

var sellPrice = price * amount;

cash = cash + sellPrice;

localStorage.setItem("cash", cash);

 

[매도] 거래 기록 업데이트하기

매수때와 거의 동일한 코드를 사용합니다. 매수 → 매도, buyPrice → sellPrice 로 변경합니다.

tradeHistory = `<tr>
    <th scope="row">${datetime}</th>
    <td>${event}</td>
    <td>매도</td>
    <td>${price.toLocaleString()}</td>
    <td>${amount}</td>
    <td>${sellPrice.toLocaleString()}</td>
</tr>` + tradeHistory;

localStorage.setItem("tradeHistory", tradeHistory);

 

[매도] 주식 보유량 업데이트하기

사용자가 매도한 종목, 평단가, 수량을 setStock() 함수에 넘겨 평단가와 보유 수량을 업데이트합니다. 매도와 달리 수량에 -1을 곱해 음수로 만들어주어 판매한 만큼 수량을 차감합니다.

setStock(event, price, -1 * amount)

 

[매도] 현황판 업데이트하기

현금, 주식보유수에 따라 현황판도 함께 업데이트합니다.

getStocksPrice();
showResult();

 

 

전체 코드

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>주식투자 포트폴리오</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <main class="container">
        <h1>주식투자 포트폴리오</h1>

        <div>총 보유자산 <span id="total">0</span> 원</div>

        <div>총 보유현금 <span id="cash">0</span> 원</div>

        <div>총 보유주식 <span id="stocks-price">0</span> 원</div>

        <div>평가손익 <span id="profit">0</span> 원</div>

        <div>수익률 <span id="percentage">0</span> %</div>

        <table class="table table-striped table-hover">
            <thead>
                <tr>
                    <th scope="col">시간</th>
                    <th scope="col">종목</th>
                    <th scope="col">거래</th>
                    <th scope="col">평단가</th>
                    <th scope="col">수량</th>
                    <th scope="col">평가금액</th>
                </tr>
            </thead>
            <tbody id="trade-history">
                <tr>
                    <th scope="row">2022-11-17 12:00:00</th>
                    <td>삼성전자</td>
                    <td>매도</td>
                    <td>10,000</td>
                    <td>2</td>
                    <td>20,000</td>
                </tr>
            </tbody>
        </table>

        <input type="datetime-local" id="datetime" />

        <select id="event">
            <option value="삼성전자">삼성전자</option>
            <option value="아마존">아마존</option>
            <option value="애플">애플</option>
        </select>

        <input type="text" id="price" placeholder="평단가" />
        <input type="text" id="amount" placeholder="수량" />

        <button type="button" id="buy">매수</button>
        <button type="button" id="sell">매도</button>
    </main>

    <script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"></script>
    <script src="script.js"></script>
</body>
</html>
var initCash = 1000000; // 최초 현금 보유량
var initStocks = {
    "삼성전자": {
        price: 0,
        amount: 0
    },
    "아마존": {
        price: 0,
        amount: 0
    },
    "애플": {
        price: 0,
        amount: 0
    },
}; // 최초 주식 보유량

var stocks; // 총 주식 보유량
var cash; // 총 현금 보유량
var stocksPrice; // 총 보유주식 평가금액
var total; // 총 보유자산 (현금 + 주식)
var tradeHistory; // 거래 기록

function getStocksPrice() {
    stocksPrice = 0;

    for (var event in stocks) {
        var stock = stocks[event];

        stocksPrice = stocksPrice + stock["price"] * stock["amount"];
    }
}

function setStock(event, price, amount) {
    var stock = stocks[event];

    stocks[event] = {
        price: price,
        amount: amount + stock["amount"]
    }

    localStorage.setItem("stocks", JSON.stringify(stocks));
}

function showResult() {
    total = cash + stocksPrice;

    var profit = total - initCash;
    var percentage = profit / initCash * 100;

    percentage = Math.round(percentage);

    $("#total").html(total.toLocaleString());
    $("#cash").html(cash.toLocaleString());
    $("#stocks-price").html(stocksPrice.toLocaleString());
    $("#profit").html(profit.toLocaleString());
    $("#percentage").html(percentage);
    $("#trade-history").html(tradeHistory);
}

function buyHandler() {
    var datetime = $("#datetime").val();
    var event = $("#event").val();
    var price = $("#price").val();
    var amount = $("#amount").val();

    price = parseFloat(price);
    amount = parseFloat(amount);

    var buyPrice = price * amount;

    cash = cash - buyPrice;

    localStorage.setItem("cash", cash);

    tradeHistory = `<tr>
        <th scope="row">${datetime}</th>
        <td>${event}</td>
        <td>매수</td>
        <td>${price.toLocaleString()}</td>
        <td>${amount}</td>
        <td>${buyPrice.toLocaleString()}</td>
    </tr>` + tradeHistory;

    localStorage.setItem("tradeHistory", tradeHistory);

    setStock(event, price, amount);
    getStocksPrice();
    showResult();
}

function sellHandler() {
    var datetime = $("#datetime").val();
    var event = $("#event").val();
    var price = $("#price").val();
    var amount = $("#amount").val();

    price = parseFloat(price);
    amount = parseFloat(amount);

    var sellPrice = price * amount;

    cash = cash + sellPrice;

    localStorage.setItem("cash", cash);

    tradeHistory = `<tr>
        <th scope="row">${datetime}</th>
        <td>${event}</td>
        <td>매도</td>
        <td>${price.toLocaleString()}</td>
        <td>${amount}</td>
        <td>${sellPrice.toLocaleString()}</td>
    </tr>` + tradeHistory;

    localStorage.setItem("tradeHistory", tradeHistory);

    setStock(event, price, -1 * amount)
    getStocksPrice();
    showResult();
}

$(document).ready(function() {
    stocks = JSON.parse(localStorage.getItem("stocks"));

    if (stocks == null) {
        stocks = initStocks;
    }

    cash = localStorage.getItem("cash");

    if (cash == null) {
        cash = initCash;
    }

    cash = parseFloat(cash);

    tradeHistory = localStorage.getItem("tradeHistory");

    if (tradeHistory == null) {
        tradeHistory = "";
    }

    getStocksPrice();
    showResult();

    $("#buy").click(buyHandler);
    $("#sell").click(sellHandler);
});

 

사용자 입력 꾸미기

Input group 문서: https://getbootstrap.com/docs/5.2/forms/input-group/

 

Input group

Easily extend form controls by adding text, buttons, or button groups on either side of textual inputs, custom selects, and custom file inputs.

getbootstrap.com

<div class="input-group">
    <input type="datetime-local" id="datetime" class="form-control" />

    <select id="event" class="form-select">
        <option value="삼성전자">삼성전자</option>
        <option value="아마존">아마존</option>
        <option value="애플">애플</option>
    </select>

    <input type="text" id="price" placeholder="평단가" class="form-control" />
    <input type="text" id="amount" placeholder="수량" class="form-control" />

    <button type="button" id="buy" class="btn btn-success">매수</button>
    <button type="button" id="sell" class="btn btn-danger">매도</button>
</div>

 

전체 코드

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>주식투자 포트폴리오</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <main class="container">
        <h1>주식투자 포트폴리오</h1>

        <ul class="list-group">
            <li class="list-group-item">총 보유자산 <b id="total">0</b> 원</li>
            <li class="list-group-item">총 보유현금 <b id="cash">0</b> 원</li>
            <li class="list-group-item">총 보유주식 <b id="stocks-price">0</b> 원</li>
            <li class="list-group-item">평가손익 <b id="profit">0</b> 원</li>
            <li class="list-group-item">수익률 <b id="percentage">0</b> %</li>
        </ul>

        <table class="table table-striped table-hover">
            <thead>
                <tr>
                    <th scope="col">시간</th>
                    <th scope="col">종목</th>
                    <th scope="col">거래</th>
                    <th scope="col">평단가</th>
                    <th scope="col">수량</th>
                    <th scope="col">평가금액</th>
                </tr>
            </thead>
            <tbody id="trade-history">
                <tr>
                    <th scope="row">2022-11-17 12:00:00</th>
                    <td>삼성전자</td>
                    <td>매도</td>
                    <td>10,000</td>
                    <td>2</td>
                    <td>20,000</td>
                </tr>
            </tbody>
        </table>

        <div class="input-group">
            <input type="datetime-local" id="datetime" class="form-control" />

            <select id="event" class="form-select">
                <option value="삼성전자">삼성전자</option>
                <option value="아마존">아마존</option>
                <option value="애플">애플</option>
            </select>

            <input type="text" id="price" placeholder="평단가" class="form-control" />
            <input type="text" id="amount" placeholder="수량" class="form-control" />

            <button type="button" id="buy" class="btn btn-success">매수</button>
            <button type="button" id="sell" class="btn btn-danger">매도</button>
        </div>
    </main>

    <script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"></script>
    <script src="script.js"></script>
</body>
</html>

 

5-7. 오픈소스 라이브러리 사용하기

날짜 라이브러리 사용하기

거의 완성했습니다. 날짜 부분이 읽기 힘들어 눈에 조금 거슬리네요. 이것을 오픈소스 라이브러리를 사용해서 예쁘게 고쳐주겠습니다. 구글에 “jquery timeago”라고 검색하여 라이브러리의 GitHub 페이지로 들어갑니다.

jquery.timeago.js 파일을 다운로드하고 part5 폴더에 복사합니다. 추가적으로 한국어를 적용하기 위해 locales/jquery.timeago.ko.js 파일도 함께 다운로드하고 part5 폴더에 복사합니다.

 

아래와 같이 index.html에 불러오는 코드를 작성합니다. (순서 중요!)

<script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="jquery.timeago.js"></script>
<script src="jquery.timeago.ko.js"></script>
<script src="script.js"></script>

 

 

GitHub의 문서를 참고하여 buyHandler(), sellHandler() 안의 tradeHistory 변수에 들어가는 값을 아래와 같이 변경합니다

tradeHistory = `<tr>
    <th scope="row">
        <time class="timeago" datetime="${datetime}">${datetime}</time>
    </th>
    <td>${event}</td>
    <td>매수</td>
    <td>${price.toLocaleString()}</td>
    <td>${amount}</td>
    <td>${buyPrice.toLocaleString()}</td>
</tr>` + tradeHistory;

 

 

showResult() 함수 마지막에 다음 코드를 추가합니다.

$('time.timeago').timeago();

 

시간 위에 마우스 커서를 올려 자세한 시간을 툴팁으로 볼 수 있습니다.

5-8. [심화] 오픈소스 라이브러리 사용하기

그래프 라이브러리 사용하기

구글에 jquery graph 라고 검색하여 가장 상단에 나오는 jQuery Charts & Graphs | CanvasJS 글로 들어갑니다.

 

canvasJS라는 라이브러리에서 제공하는 많은 그래프 차트 중 동그란 모양의 파이차트를 사용하여 주식 보유량을 표시해보겠습니다.

 

클릭하여 들어가면 차트 미리보기와 샘플 코드를 제공합니다. 샘플 코드를 복사하여 script.js에 붙여넣기 합니다.

복사할 때는 var options = … 부터 $("#chartContainer").CanvasJSChart(options); 까지 우리가 필요한 부분만 복사합니다.

복사한 코드는 showResults() 함수의 가장 아래에 붙여넣기합니다.

 

var options = {
    title: {
        text: "Desktop OS Market Share in 2017"
    },
    subtitles: [{
        text: "As of November, 2017"
    }],
    animationEnabled: true,
    data: [{
        type: "pie",
        startAngle: 40,
        toolTipContent: "<b>{label}</b>: {y}%",
        showInLegend: "true",
        legendText: "{label}",
        indexLabelFontSize: 16,
        indexLabel: "{label} - {y}%",
        dataPoints: [
            { y: 48.36, label: "Windows 7" },
            { y: 26.85, label: "Windows 10" },
            { y: 1.49, label: "Windows 8" },
            { y: 6.98, label: "Windows XP" },
            { y: 6.53, label: "Windows 8.1" },
            { y: 2.45, label: "Linux" },
            { y: 3.32, label: "Mac OS X 10.12" },
            { y: 4.03, label: "Others" }
        ]
    }]
};

$("#chartContainer").CanvasJSChart(options);

코드를 자세히 보시면 #chartContainer 에 차트를 넣게끔 되어있는데 id가 chartContainer인 div를 HTML에 추가하겠습니다.

<div id="chartContainer"></div>

마지막으로 차트를 사용하기 위한 핵심 라이브러리를 로드하는 코드를 추가합니다. jquery 아래 script.js 위에 추가합니다.

<script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="jquery.timeago.js"></script>
<script src="jquery.timeago.ko.js"></script>
<script src="https://canvasjs.com/assets/script/jquery.canvasjs.min.js"></script>
<script src="script.js"></script>

보기 좋게 HTML을 손 보겠습니다.

<div class="row">
    <div class="col">
        <ul class="list-group">
            <li class="list-group-item">총 보유자산 <b id="total">0</b> 원</li>
            <li class="list-group-item">총 보유현금 <b id="cash">0</b> 원</li>
            <li class="list-group-item">총 보유주식 <b id="stocks-price">0</b> 원</li>
            <li class="list-group-item">평가손익 <b id="profit">0</b> 원</li>
            <li class="list-group-item">수익률 <b id="percentage">0</b> %</li>
        </ul>
    </div>
    <div class="col" style="height: 400px;">
        <div id="chartContainer"></div>
    </div>
</div>

마지막으로 script.js 에서 쓸데없는 코드를 삭제하고 실제 보유 주식을 보여주기 위한 코드를 수정합니다.

var options = {
    animationEnabled: true,
    data: [{
        type: "pie",
        dataPoints: [
            { y: stocks["삼성전자"]["price"] * stocks["삼성전자"]["amount"], 
            label: "삼성전자" },
            { y: stocks["아마존"]["price"] * stocks["아마존"]["amount"], label: "아마존" },
            { y: stocks["애플"]["price"] * stocks["애플"]["amount"], label: "애플" },
        ]
    }]
};

$("#chartContainer").CanvasJSChart(options);

 

거래목록 검색기능 구현하기

거래목록이 많아질 경우 보기 불편하니 검색기능 넣어보겠습니다. 구글에 “bootstrap jquery table filter” 라고 검색하여 들어갑니다.

Bootstrap Filters (Advanced): https://www.w3schools.com/bootstrap/bootstrap_filters.asp

 

Bootstrap Filters

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

 

 

위 사이트에서 직접 기능을 체험해볼 수도 있고 코드도 제공합니다. 코드를 복사하여 script.js에 붙여넣기 합니다. 코드를 자세히 보면 고쳐야 할 부분이 두 가지 있습니다. 먼저 사용자의 검색어 입력을 받는 부분(텍스트박스도 새로 만들어야겠군요)과 테이블의 id를 수정해야합니다.

  • 검색어 입력 id: myInput → search
  • 테이블의 id: myTable → trade-history
$("#search").on("keyup", function() {
    var value = $(this).val().toLowerCase();
    $("#trade-history tr").filter(function() {
        $(this).toggle($(this).text().toLowerCase().indexOf(value) > -1)
    });
});
<input type="text" class=form-control id="search" placeholder="검색" />

 

이렇게 간단하게 검색 기능을 만들 수 있습니다.

5-9. 기타 기능 구현하기

금액, 수량 체크해서 알림주기

이제 포트폴리오가 거의 완성되었지만 아직 버그가 있습니다. 아래 두 가지 사항에 대해서요.

  1. 매수 시 구입하려는 주식 금액이 보유한 현금보다 많을 때
  2. 매도 시 보유한 주식 수량보다 판매하려는 주식 수량이 많을 때

위 두 경우에 대해서는 거래가 일어나지 않도록 예외처리를 해주어야 합니다. 그리고 사용자에게 돈이 부족하다고 알림도 줘야겠지요. 한 번 구현해봅시다.yHandler() 안에 다음과 같이 작성합니다.

if (buyPrice > cash) {
    console.log("보유한 현금이 부족합니다.");
    return false;
}

구입하려는 주식 금액(buyPrice)이 현금(cash)보다 많으면 개발자 콘솔에 “보유한 현금이 부족합니다.” 메시지를 띄우고 중단합니다. 그러나 사용자들은 개발자 콘솔을 모르기 떄문에 HTML로 보여주는게 좋겠죠? Bootstrap의 Alerts 문서를 참고하여 만들어봅시다.

Alerts 문서: https://getbootstrap.com/docs/5.2/components/alerts/

 

Alerts

Provide contextual feedback messages for typical user actions with the handful of available and flexible alert messages.

getbootstrap.com

< style="display: none;” 을 입력하여 처음에 보이지 않도록 설정합니다.

 

<div class="alert alert-danger" role="alert" id="alert" style="display: none;">
    A simple danger alert—check it out!
</div>
jQuery의 show(), hide() 함수를 사용하여 메시지를 보여주거나 숨길 수 있습니다.
if (buyPrice > cash) {
    $("#alert").html("보유한 현금이 부족합니다.");
    $("#alert").show();
    return false;
}

$("#alert").hide();

마찬가지로 2번의 경우(매도 시 보유한 주식 수량보다 판매하려는 주식 수량이 많을 때)도 예외처리를 해주죠.

if (amount > stocks[event]["amount"]) {
    $("#alert").html("보유한 수량보다 더 많은 수량을 매도할 수 없습니다.");
    $("#alert").show();
    return false;
}

$("#alert").hide();

 

홈페이지와 연결하기

이제 홈페이지(main.html)와 연결해봅시다. 이제 혼자서 할 수 있겠죠?

 

전체 코드

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>주식투자 포트폴리오</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
        <div class="container-fluid">
            <a class="navbar-brand" href="/main.html">빵형의 홈페이지</a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarCollapse">
                <ul class="navbar-nav me-auto mb-2 mb-md-0">
                    <li class="nav-item">
                        <a class="nav-link" aria-current="page" href="/main.html">홈</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="/part2/index.html">사칙연산 계산기</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="/part3/index.html">대출이자 계산기</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="/part4/index.html">일기장</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link active" href="/part5/index.html">주식투자 포트폴리오</a>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

    <main class="container">
        <h1>주식투자 포트폴리오</h1>

        <ul class="list-group">
            <li class="list-group-item">총 보유자산 <b id="total">0</b> 원</li>
            <li class="list-group-item">총 보유현금 <b id="cash">0</b> 원</li>
            <li class="list-group-item">총 보유주식 <b id="stocks-price">0</b> 원</li>
            <li class="list-group-item">평가손익 <b id="profit">0</b> 원</li>
            <li class="list-group-item">수익률 <b id="percentage">0</b> %</li>
        </ul>

        <table class="table table-striped table-hover">
            <thead>
                <tr>
                    <th scope="col">시간</th>
                    <th scope="col">종목</th>
                    <th scope="col">거래</th>
                    <th scope="col">평단가</th>
                    <th scope="col">수량</th>
                    <th scope="col">평가금액</th>
                </tr>
            </thead>
            <tbody id="trade-history">
                <tr>
                    <th scope="row">2022-11-17 12:00:00</th>
                    <td>삼성전자</td>
                    <td>매도</td>
                    <td>10,000</td>
                    <td>2</td>
                    <td>20,000</td>
                </tr>
            </tbody>
        </table>

        <div class="alert alert-danger" role="alert" id="alert" style="display: none;">
            A simple danger alert—check it out!
        </div>

        <div class="input-group">
            <input type="datetime-local" id="datetime" class="form-control" />

            <select id="event" class="form-select">
                <option value="삼성전자">삼성전자</option>
                <option value="아마존">아마존</option>
                <option value="애플">애플</option>
            </select>

            <input type="text" id="price" placeholder="평단가" class="form-control" />
            <input type="text" id="amount" placeholder="수량" class="form-control" />

            <button type="button" id="buy" class="btn btn-success">매수</button>
            <button type="button" id="sell" class="btn btn-danger">매도</button>
        </div>
    </main>

    <script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"></script>
    <script src="jquery.timeago.js"></script>
    <script src="jquery.timeago.ko.js"></script>
    <script src="script.js"></script>
</body>
</html>

 

 

script.js

var initCash = 1000000; // 최초 현금 보유량
var initStocks = {
    "삼성전자": {
        price: 0,
        amount: 0
    },
    "아마존": {
        price: 0,
        amount: 0
    },
    "애플": {
        price: 0,
        amount: 0
    },
}; // 최초 주식 보유량

var stocks; // 총 주식 보유량
var cash; // 총 현금 보유량
var stocksPrice; // 총 보유주식 평가금액
var total; // 총 보유자산 (현금 + 주식)
var tradeHistory; // 거래 기록

function getStocksPrice() {
    stocksPrice = 0;

    for (var event in stocks) {
        var stock = stocks[event];

        stocksPrice = stocksPrice + stock["price"] * stock["amount"];
    }
}

function setStock(event, price, amount) {
    var stock = stocks[event];

    stocks[event] = {
        price: price,
        amount: amount + stock["amount"]
    }

    localStorage.setItem("stocks", JSON.stringify(stocks));
}

function showResult() {
    total = cash + stocksPrice;

    var profit = total - initCash;
    var percentage = profit / initCash * 100;

    percentage = Math.round(percentage);

    $("#total").html(total.toLocaleString());
    $("#cash").html(cash.toLocaleString());
    $("#stocks-price").html(stocksPrice.toLocaleString());
    $("#profit").html(profit.toLocaleString());
    $("#percentage").html(percentage);
    $("#trade-history").html(tradeHistory);

    $('time.timeago').timeago();
}

function buyHandler() {
    var datetime = $("#datetime").val();
    var event = $("#event").val();
    var price = $("#price").val();
    var amount = $("#amount").val();

    price = parseFloat(price);
    amount = parseFloat(amount);

    var buyPrice = price * amount;

    if (buyPrice > cash) {
        $("#alert").html("보유한 현금이 부족합니다.");
        $("#alert").show();
        return false;
    }

    $("#alert").hide();

    cash = cash - buyPrice;

    localStorage.setItem("cash", cash);

    tradeHistory = `<tr>
        <th scope="row">
            <time class="timeago" datetime="${datetime}">${datetime}</time>
        </th>
        <td>${event}</td>
        <td>매수</td>
        <td>${price.toLocaleString()}</td>
        <td>${amount}</td>
        <td>${buyPrice.toLocaleString()}</td>
    </tr>` + tradeHistory;

    localStorage.setItem("tradeHistory", tradeHistory);

    setStock(event, price, amount);
    getStocksPrice();
    showResult();
}

function sellHandler() {
    var datetime = $("#datetime").val();
    var event = $("#event").val();
    var price = $("#price").val();
    var amount = $("#amount").val();

    price = parseFloat(price);
    amount = parseFloat(amount);

    if (amount > stocks[event]["amount"]) {
        $("#alert").html("보유한 수량보다 더 많은 수량을 매도할 수 없습니다.");
        $("#alert").show();
        return false;
    }

    $("#alert").hide();

    var sellPrice = price * amount;

    cash = cash + sellPrice;

    localStorage.setItem("cash", cash);

    tradeHistory = `<tr>
        <th scope="row">
            <time class="timeago" datetime="${datetime}">${datetime}</time>
        </th>
        <td>${event}</td>
        <td>매도</td>
        <td>${price.toLocaleString()}</td>
        <td>${amount}</td>
        <td>${sellPrice.toLocaleString()}</td>
    </tr>` + tradeHistory;

    localStorage.setItem("tradeHistory", tradeHistory);

    setStock(event, price, -1 * amount)
    getStocksPrice();
    showResult();
}

$(document).ready(function() {
    stocks = JSON.parse(localStorage.getItem("stocks"));

    if (stocks == null) {
        stocks = initStocks;
    }

    cash = localStorage.getItem("cash");

    if (cash == null) {
        cash = initCash;
    }

    cash = parseFloat(cash);

    tradeHistory = localStorage.getItem("tradeHistory");

    if (tradeHistory == null) {
        tradeHistory = "";
    }

    getStocksPrice();
    showResult();

    $("#buy").click(buyHandler);
    $("#sell").click(sellHandler);
});

 

 

 

 

style.css
body {
    min-height: 75rem;
    padding-top: 4.5rem;
}