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 |