프로그래밍/잡다한코딩

flask로 포트폴리오 관리 사이트 만들기

hideh 2025. 3. 4. 14:06

python flask로 투자 포트폴리오를 관리하는 사이트를 만들어 보았다.

 

https://studystock.pythonanywhere.com/

 

1. 금융 정보 가져오기

원래 파이썬에서 yfinance를 사용했으나 최근 api가 막힌 것으로 보여 직접 코드를 가져오도록 구현하였다.

사실 requests도 그냥 보내면 막히기 때문에 헤더를 넣어 해결하였다.

import requests

def get_stock_price(ticker):
    try:
        url = f"https://query1.finance.yahoo.com/v8/finance/chart/{ticker}"
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
        }
        response = requests.get(url, headers=headers)
        data = response.json()
        close_price = data["chart"]["result"][0]["meta"]["regularMarketPrice"]
        return close_price
    except Exception as e:
        print(f"Error fetching {ticker}: {e}")
        return None
        
def get_exchange_rate():
    try:
        url = "https://api.exchangerate-api.com/v4/latest/USD"
        response = requests.get(url)
        data = response.json()
        return data["rates"].get("KRW", 1300)  
    except Exception as e:
        print(f"Error fetching exchange rate: {e}")
        return 1300

 

2. app.py 와 html 사이 연결고리 만들기

 

python의 flask로 처리한 정보를 html 에 보내기 위해서는 다음과 같이 연동시킬 수 있다.

 

- python

from flask import Flask, render_template, jsonify, request
import pandas as pd
import json
import requests

app = Flask(__name__)

@app.route('/site1') # /site1.html 사이트
def site1():
	...
    return render_template('site1.html' , 
    			param1 = var1,
                array1 = list1 )
    
@app.route('/endpoint1') #엔드포인트. html에서 사용가능한 함수같은 느낌
def endpoint1():
    
    df = pd.DataFrame( { "data1" : data1 , 
    						...
                            })
    ....
    
    return jsonify(json.loads(df.to_json(orient='records')))

 

- 대응하는 html

 

site1.html

<!DOCTYPE html>
<html>
<body>
<h2>전달 데이터</h2>
hello {{ param1 }} !!
{% for i in data %}
    {{ i }}
{% endfor %}

</body>
</html>

 

엔드포인트

<script>
        function endpoint1() 
        {
        	let total = 0 ;
            $.getJSON('/endpoint1', function(data) 
            	{
                data.forEach(row => {
                    total += row["col_index_name"];
            	});
            let a = data["another_index"] ;
        }

        $(document).ready(function() {
            some_functions();
        });
 </script>

 

 

 

25.03.04 기준 css를 제외한 전체 코드는 다음과 같다.

 

app.py

from flask import Flask, render_template, jsonify, request
import pandas as pd
import json
import requests

app = Flask(__name__)

port = pd.DataFrame([
    {"티커": "IBM", "개수": 7 , "달러 평단가": 243.08, "원화 평단가": 348026},
    {"티커": "NVD", "개수": 23, "달러 평단가": 30.5, "원화 평단가": 43911},
    {"티커": "CWEB", "개수": 9, "달러 평단가": 41.33, "원화 평단가": 59511},
    {"티커": "SOXS", "개수": 16, "달러 평단가":  23.99 , "원화 평단가": 34539},
    {"티커": "UCO", "개수": 15, "달러 평단가": 25.26, "원화 평단가": 36370},
    {"티커": "UVIX", "개수": 10, "달러 평단가": 33.45, "원화 평단가": 48158},
])

history = [
    {"날짜": "2024-03-03", "유형": "매수", "티커": "SOXS", "개수": 16, "가격": 23.99, "통화": "USD"},
    {"날짜": "2024-03-03", "유형": "매수", "티커": "NVD", "개수": 23, "가격": 30.5, "통화": "USD"},
    {"날짜": "2024-03-03", "유형": "매수", "티커": "UVIX", "개수": 10, "가격": 33.45, "통화": "USD"},
    {"날짜": "2024-03-01", "유형": "매수", "티커": "IBM", "개수": 7, "가격": 243.08, "통화": "USD"},
    {"날짜": "2024-03-01", "유형": "매수", "티커": "CWEB", "개수": 9, "가격": 41.33, "통화": "USD"},
    {"날짜": "2024-03-01", "유형": "매수", "티커": "UCO", "개수": 15, "가격": 25.26, "통화": "USD"},
    {"날짜": "2024-03-01", "유형": "입금", "금액": 3908.01, "메모": "초기 투자", "통화": "USD"},
]
history.sort(key = lambda x : x["날짜"])


def get_exchange_rate():
    try:
        url = "https://api.exchangerate-api.com/v4/latest/USD"
        response = requests.get(url)
        data = response.json()
        return data["rates"].get("KRW", 1300)  # KRW 환율 반환, 기본값 1300
    except Exception as e:
        print(f"Error fetching exchange rate: {e}")
        return 1300


exchange_rate = get_exchange_rate()

def calculate_cash():
    total_cash_usd = sum(item["금액"] for item in history if item["유형"] == "입금" and item["통화"] == "USD")
    total_cash_krw = sum(item["금액"] for item in history if item["유형"] == "입금" and item["통화"] == "KRW")
    total_cash_usd -= sum(item["개수"] * item["가격"] for item in history if item["유형"] == "매수" and item["통화"] == "USD")
    total_cash_krw -= sum(item["개수"] * item["가격"] for item in history if item["유형"] == "매수" and item["통화"] == "KRW")
    
    return total_cash_usd, total_cash_krw


def get_stock_price(ticker):
    try:
        url = f"https://query1.finance.yahoo.com/v8/finance/chart/{ticker}"
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
        }
        response = requests.get(url, headers=headers)
        data = response.json()
        close_price = data["chart"]["result"][0]["meta"]["regularMarketPrice"]
        return close_price
    except Exception as e:
        print(f"Error fetching {ticker}: {e}")
        return None

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/portfolio')
def get_portfolio():
    
    df = port.copy()
    for index, row in df.iterrows():
        ticker = row["티커"]
        current_price = get_stock_price(ticker)
        if current_price is None:
            continue
        df.at[index, "현재가(USD)"] = current_price
        df.at[index, "수익금(USD)"] = (current_price - row["달러 평단가"]) * row["개수"]
        df.at[index, "수익금(KRW)"] = (current_price * exchange_rate - row["원화 평단가"]) * row["개수"]
        df.at[index, "수익률(%)"] = (df.at[index, "수익금(USD)"] / (row["달러 평단가"] * row["개수"])) * 100
    
    return jsonify(json.loads(df.to_json(orient='records')))

@app.route('/return')
def get_return():
    total_cash_usd = sum(item["금액"] for item in history if item["유형"] == "입금" and item["통화"] == "USD")
    total_cash_krw = sum(item["금액"] for item in history if item["유형"] == "입금" and item["통화"] == "KRW")
        
    origin_value = total_cash_usd + total_cash_krw / exchange_rate  # 달러 기준
    total_cash_usd, total_cash_krw = calculate_cash()  

    # 현재 포트폴리오 가치 계산
    total_portfolio_value  = 0 
    df = port.copy()
    for index, row in df.iterrows():
        ticker = row["티커"]
        current_price = get_stock_price(ticker)
        if current_price is None:
            continue
        total_portfolio_value +=  row["개수"] * current_price
    
    total_value = total_portfolio_value + total_cash_usd + total_cash_krw / exchange_rate  # 총 현재 가치
    
    return_rate = ((total_value - origin_value) / origin_value * 100) if origin_value > 0 else 0  # 수익률 계산
    print(origin_value)
    return jsonify({
        "origin_value": origin_value,
        "total_value": total_value,
        "return_rate": return_rate,
        "total1" : total_value - origin_value,
        "total2" : (total_value - origin_value)* exchange_rate
    })


    
@app.route('/history')
def get_history():
    return jsonify(history)

@app.route('/cash')
def get_cash():
    total_cash_usd, total_cash_krw = calculate_cash()
    return jsonify({"현금(USD)": total_cash_usd, "현금(KRW)": total_cash_krw})

@app.route('/exchange_rate')
def get_current_exchange_rate():
    return jsonify({"환율(USD/KRW)": get_exchange_rate()})

if __name__ == '__main__':
    app.run()

 

index.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>포트폴리오</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <h1>투자 포트폴리오</h1>
    <button onclick="toggleCurrency()">USD/KRW 전환</button>

    <h2>포트폴리오 전체 가치: <span id="totalValue"></span></h2>
<h3>전체 수익률: <span id="portfolioReturn">로딩 중...</span>%</h3>
<h3>전체 수익금: <span id="portfolioReturn1">로딩 중...</span></h3>


    <h3>현재 환율 (USD/KRW): <span id="exchangeRate"></span></h3>

    <h2>포트폴리오</h2>
    <table id="portfolioTable">
        <thead>
            <tr>
                <th>티커</th><th>개수</th><th>평단가</th><th>현재가</th><th>수익률</th><th>수익금</th>
            </tr>
        </thead>
        <tbody></tbody>
        <tfoot>
            <tr>
                <td colspan="5"><strong>보유 현금</strong></td>
                <td id="cashDisplay"></td>
            </tr>
        </tfoot>
    </table>
    <!--
    <h2>포트폴리오 가치 변화</h2>
    <canvas id="portfolioChart"></canvas>
    -->
    <h2>거래 기록</h2>
    <table id="historyTable">
        <thead>
            <tr>
                <th>날짜</th><th>유형</th><th>티커</th><th>개수</th><th>가격</th><th>통화</th><th>메모</th>
            </tr>
        </thead>
        <tbody></tbody>
    </table>

    <script>
        let isUSD = true;
        let exchangeRate = 1300;
        let total = 0 ;
        function fetchCash() {
            $.getJSON('/cash', function(data) {
                let totalCash = isUSD ? data["현금(USD)"] + (data["현금(KRW)"] / exchangeRate) : (data["현금(USD)"] * exchangeRate) + data["현금(KRW)"];
                $('#cashDisplay').text(totalCash.toFixed(2) + (isUSD ? ' USD' : ' KRW'));

                $('#totalValue').text(
                    (parseFloat($('#cashDisplay').text().replace(/[^0-9.-]/g, '')) + total)+ (isUSD ? ' USD' : ' KRW')

                    )


            });
        }
        function fetchPortfolio() {
            $.getJSON('/portfolio', function(data) {
                let tableBody = $('#portfolioTable tbody');
                tableBody.empty();
                let totalValue = 0;
                data.forEach(row => {
                    let price = isUSD ? row["달러 평단가"] : row["원화 평단가"];
                    let currentPrice = isUSD ? row["현재가(USD)"] : row["현재가(USD)"] * exchangeRate;
                    let profit = isUSD ? row["수익금(USD)"] : row["수익금(KRW)"];
                    totalValue += currentPrice * row["개수"];
                    let rowHTML = `<tr>
                        <td>${row["티커"]}</td>
                        <td>${row["개수"]}</td>
                        <td>${price.toFixed(2)} ${isUSD ? 'USD' : 'KRW'}</td>
                        <td>${currentPrice.toFixed(2)} ${isUSD ? 'USD' : 'KRW'}</td>
                        <td>${row["수익률(%)"].toFixed(2)}%</td>
                        <td>${profit.toFixed(2)} ${isUSD ? 'USD' : 'KRW'}</td>
                    </tr>`;
                    tableBody.append(rowHTML);
                });
                total = totalValue;

                fetchCash();
            });
        }


        function fetchHistory() {
            $.getJSON('/history', function(data) {
                let tableBody = $('#historyTable tbody');
                tableBody.empty();
                let total = 0;
                let dates = [];
                let values = [];
                data.forEach(row => {
                    let rowHTML = `<tr>
                        <td>${row["날짜"]}</td>
                        <td>${row["유형"]}</td>
                        <td>${row["티커"] || '-'}</td>
                        <td>${row["개수"] || '-'}</td>
                        <td>${row["가격"] || '-'}</td>
                        <td>${row["통화"] || '-'}</td>
                        <td>${row["메모"] || '-'}</td>
                    </tr>`;
                    tableBody.append(rowHTML);

                    if (row["유형"] === "입금") {
                        total += row["금액"];
                    } else if (row["유형"] === "매수") {
                        total += row["개수"] * row["가격"];
                    }
                    dates.push(row["날짜"]);
                    values.push(total);
                });

                let ctx = document.getElementById('portfolioChart').getContext('2d');
                new Chart(ctx, {
                    type: 'line',
                    data: {
                        labels: dates,
                        datasets: [{
                            label: '포트폴리오 가치 (USD)',
                            data: values,
                            borderColor: 'blue',
                            fill: false
                        }]
                    }
                });
            });
        }

        function fetchExchangeRate() {
            $.getJSON('/exchange_rate', function(data) {
                exchangeRate = data["환율(USD/KRW)"];
                $('#exchangeRate').text(exchangeRate.toFixed(2));
                fetchPortfolio();
                fetchHistory();
            });
        }



    function fetchReturn() {
        $.getJSON('/return', function(data) {
            let originValue = data["origin_value"];
            let totalValue = data["total_value"];
            let portfolioReturn = data["return_rate"];
            let portfolioReturn1 =  isUSD ? data["total1"] : data['total2'] ;

            if (!isNaN(portfolioReturn)) {
                $('#portfolioReturn').text(portfolioReturn.toFixed(2));
				$('#portfolioReturn1').text(portfolioReturn1.toFixed(2) + (isUSD ? ' USD' : ' KRW'));
            } else {
                $('#portfolioReturn').text("데이터 없음");
				$('#portfolioReturn1').text("데이터 없음");
            }
        }).fail(function() {
            $('#portfolioReturn').text("오류 발생");
			$('#portfolioReturn1').text("오류 발생");
        });
    }

    $(document).ready(function() {
        fetchReturn();
    });

	        function toggleCurrency() {
            isUSD = !isUSD;
            fetchPortfolio();
			fetchReturn();
        }

        $(document).ready(function() {
            fetchExchangeRate();
        });
</script>




</body>
</html>

 


귀찮아서 html파일도 그렇고 데이터처리도 그렇고 그냥 하나에 다 집어넣었다. 대충 짠거라 기능이 많지는 않지만 만든 것에 의의를 두는거로. 업데이트는 나중에 시간이 나면 해야겠다.

'프로그래밍 > 잡다한코딩' 카테고리의 다른 글

유튜브 플레이리스트의 음악 다운로드  (0) 2025.02.06
wordle solver  (0) 2025.01.26