Spaces:
Running
Running
Upload 16 files
Browse files- app.py +1 -1
- interface/dollar_cost_averaging_interface.py +4 -4
- interface/retirement_planning_interface.py +3 -3
- modules/dollar_cost_averaging.py +23 -27
- modules/rebalancing.py +3 -11
- modules/retirement_planning.py +2 -8
- style.css +8 -3
app.py
CHANGED
@@ -105,7 +105,7 @@ def create_tab(tab_name, inputs, outputs, update_fn, examples, component_rows):
|
|
105 |
render_components(component_rows)
|
106 |
clear_buttons(inputs)
|
107 |
submit_buttons(inputs, update_fn, outputs)
|
108 |
-
with gr.Column(scale=
|
109 |
outputs.render()
|
110 |
with gr.Accordion("๐ Examples", open=False):
|
111 |
gr.Examples(examples=examples, cache_examples=False, inputs=inputs)
|
|
|
105 |
render_components(component_rows)
|
106 |
clear_buttons(inputs)
|
107 |
submit_buttons(inputs, update_fn, outputs)
|
108 |
+
with gr.Column(scale=3):
|
109 |
outputs.render()
|
110 |
with gr.Accordion("๐ Examples", open=False):
|
111 |
gr.Examples(examples=examples, cache_examples=False, inputs=inputs)
|
interface/dollar_cost_averaging_interface.py
CHANGED
@@ -2,20 +2,20 @@ import gradio as gr
|
|
2 |
from modules.dollar_cost_averaging import dollar_cost_averaging
|
3 |
|
4 |
examples = [
|
5 |
-
[
|
6 |
]
|
7 |
|
8 |
# Define the inputs
|
9 |
-
stock_code = gr.Textbox(label="Stock Code", value="368590")
|
10 |
first_purchase = gr.Markdown("<h3 class='h3_title'>FIRST PURCHASE</h3>")
|
11 |
old_price = gr.Number(label="Old Price", value=18153)
|
12 |
old_quantity = gr.Number(label="Quantity", value=122)
|
13 |
second_purchase = gr.Markdown("<h3 class='h3_title'>SECOND PURCHASE</h3>")
|
|
|
14 |
new_quantity = gr.Number(label="Quantity", value=1)
|
15 |
|
16 |
-
input = [
|
17 |
output = gr.HTML()
|
18 |
-
component_rows = [
|
19 |
|
20 |
# Define the update function
|
21 |
def update_output(*args):
|
|
|
2 |
from modules.dollar_cost_averaging import dollar_cost_averaging
|
3 |
|
4 |
examples = [
|
5 |
+
[200, 90, 100, 200]
|
6 |
]
|
7 |
|
8 |
# Define the inputs
|
|
|
9 |
first_purchase = gr.Markdown("<h3 class='h3_title'>FIRST PURCHASE</h3>")
|
10 |
old_price = gr.Number(label="Old Price", value=18153)
|
11 |
old_quantity = gr.Number(label="Quantity", value=122)
|
12 |
second_purchase = gr.Markdown("<h3 class='h3_title'>SECOND PURCHASE</h3>")
|
13 |
+
new_price = gr.Textbox(label="New Price", value="15000")
|
14 |
new_quantity = gr.Number(label="Quantity", value=1)
|
15 |
|
16 |
+
input = [old_price, old_quantity, new_price, new_quantity]
|
17 |
output = gr.HTML()
|
18 |
+
component_rows = [first_purchase, old_price, old_quantity, second_purchase, new_price, new_quantity]
|
19 |
|
20 |
# Define the update function
|
21 |
def update_output(*args):
|
interface/retirement_planning_interface.py
CHANGED
@@ -27,9 +27,9 @@ post_retirement_dividend_yield = gr.Number(label="Dividend Yield (Post-retiremen
|
|
27 |
input = [current_age, retirement_age, life_expectancy, monthly_income_required, inflation_rate, initial_investment, monthly_contribution, annual_increase_in_monthly_contribution, reinvest_dividends, pre_retirement_dividend_growth, post_retirement_dividend_growth, pre_retirement_dividend_yield, post_retirement_dividend_yield]
|
28 |
output = gr.HTML()
|
29 |
component_rows = [
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
]
|
34 |
|
35 |
# Define the update function
|
|
|
27 |
input = [current_age, retirement_age, life_expectancy, monthly_income_required, inflation_rate, initial_investment, monthly_contribution, annual_increase_in_monthly_contribution, reinvest_dividends, pre_retirement_dividend_growth, post_retirement_dividend_growth, pre_retirement_dividend_yield, post_retirement_dividend_yield]
|
28 |
output = gr.HTML()
|
29 |
component_rows = [
|
30 |
+
[current_age, retirement_age, life_expectancy],[monthly_income_required, inflation_rate],
|
31 |
+
[initial_investment, monthly_contribution, annual_increase_in_monthly_contribution], [reinvest_dividends],
|
32 |
+
[pre_retirement_dividend_growth, post_retirement_dividend_growth], [pre_retirement_dividend_yield, post_retirement_dividend_yield]
|
33 |
]
|
34 |
|
35 |
# Define the update function
|
modules/dollar_cost_averaging.py
CHANGED
@@ -2,20 +2,20 @@ import yfinance as yf
|
|
2 |
import FinanceDataReader as fdr
|
3 |
from modules.utils import load_css
|
4 |
|
5 |
-
def get_stock_price(stock_code):
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
|
20 |
def calculate_dollar_cost_averaging(old_avg_price, old_quantity, new_price, new_quantity):
|
21 |
old_avg_price = float(old_avg_price) if old_avg_price else 0.0
|
@@ -36,15 +36,15 @@ def calculate_dollar_cost_averaging(old_avg_price, old_quantity, new_price, new_
|
|
36 |
|
37 |
return new_avg_price, total_quantity, total_investment, new_return, additional_investment, old_return
|
38 |
|
39 |
-
def dollar_cost_averaging(
|
40 |
css = load_css()
|
41 |
|
42 |
# ์ฃผ์ ๊ฐ๊ฒฉ ๊ฐ์ ธ์ค๊ธฐ
|
43 |
-
new_price = get_stock_price(stock_code)
|
44 |
|
45 |
-
new_price = float(new_price) if new_price else 0.0
|
46 |
old_avg_price = float(old_avg_price) if old_avg_price else 0.0
|
47 |
old_quantity = float(old_quantity) if old_quantity else 0.0
|
|
|
48 |
new_quantity = float(new_quantity) if new_quantity else 0.0
|
49 |
|
50 |
new_avg_price, total_quantity, total_investment, new_return, additional_investment, old_return = calculate_dollar_cost_averaging(old_avg_price, old_quantity, new_price, new_quantity)
|
@@ -52,9 +52,9 @@ def dollar_cost_averaging(stock_code, old_avg_price, old_quantity, new_quantity)
|
|
52 |
emoji_return = ""
|
53 |
new_return_class = ""
|
54 |
old_return_class = ""
|
55 |
-
if
|
56 |
emoji_return = "๐ง"
|
57 |
-
elif
|
58 |
emoji_return = "๐ฅ"
|
59 |
else:
|
60 |
emoji_return = ""
|
@@ -77,18 +77,14 @@ def dollar_cost_averaging(stock_code, old_avg_price, old_quantity, new_quantity)
|
|
77 |
<div class="wrap-text">
|
78 |
<div>
|
79 |
<div style="margin-bottom: 1.5rem;">
|
80 |
-
<div style="font-size:
|
81 |
-
<div style="font-size: 1.5rem; color: #1c75bc;">
|
82 |
-
<span style='color: #1678fb; font-weight: bold;'>{new_price:,}</span>
|
83 |
-
</div>
|
84 |
-
<hr style="margin: 1.5rem 0;">
|
85 |
</div>
|
86 |
</div>
|
87 |
<div>
|
88 |
<div style="margin-bottom: 1.5rem;">
|
89 |
<div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Old Price</div>
|
90 |
<div style="font-size: 1.5rem; color: #1c75bc;">
|
91 |
-
<span style='color: #1678fb; font-weight: bold;'>{old_avg_price:,.
|
92 |
<span style='font-size: 1rem;'>{old_return_class}</span>
|
93 |
</div>
|
94 |
<hr style="margin: 1.5rem 0;">
|
@@ -98,7 +94,7 @@ def dollar_cost_averaging(stock_code, old_avg_price, old_quantity, new_quantity)
|
|
98 |
<div style="margin-bottom: 1.5rem;">
|
99 |
<div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Average Price</div>
|
100 |
<div style="font-size: 1.5rem; color: #1c75bc;">
|
101 |
-
<span style='color: #1678fb; font-weight: bold;'>{new_avg_price:,.
|
102 |
<span style='font-size: 1rem;'>{new_return_class}</span>
|
103 |
</div>
|
104 |
<hr style="margin: 1.5rem 0;">
|
@@ -108,7 +104,7 @@ def dollar_cost_averaging(stock_code, old_avg_price, old_quantity, new_quantity)
|
|
108 |
<div style="margin-bottom: 1.5rem;">
|
109 |
<div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Additional Investment</div>
|
110 |
<div style="font-size: 1.5rem; font-weight: bold; color: #1c75bc;">
|
111 |
-
<span style='color: #1678fb'>{additional_investment:,.
|
112 |
</div>
|
113 |
<hr style="margin: 1.5rem 0;">
|
114 |
</div>
|
|
|
2 |
import FinanceDataReader as fdr
|
3 |
from modules.utils import load_css
|
4 |
|
5 |
+
# def get_stock_price(stock_code):
|
6 |
+
# try:
|
7 |
+
# # ํ๊ตญ ์ฃผ์ ์ฝ๋๊ฐ ์ซ์๋ง ์๋์ง ํ์ธ (ํ๊ตญ ์ฃผ์์ ๋ณดํต ์ซ์ ์ฝ๋)
|
8 |
+
# if stock_code.isdigit():
|
9 |
+
# df = fdr.DataReader(stock_code)
|
10 |
+
# return df['Close'].iloc[-1]
|
11 |
+
# else:
|
12 |
+
# # ํ๊ตญ ์ธ ์ฃผ์์ yfinance๋ก ์กฐํ
|
13 |
+
# stock = yf.Ticker(stock_code)
|
14 |
+
# df = stock.history(period="1d")
|
15 |
+
# return df['Close'].iloc[-1]
|
16 |
+
# except Exception as e:
|
17 |
+
# # ์๋ฌ ๋ฐ์ ์ 0.0์ผ๋ก ์ค์ ํ๊ณ , ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๋ฐํ
|
18 |
+
# return None
|
19 |
|
20 |
def calculate_dollar_cost_averaging(old_avg_price, old_quantity, new_price, new_quantity):
|
21 |
old_avg_price = float(old_avg_price) if old_avg_price else 0.0
|
|
|
36 |
|
37 |
return new_avg_price, total_quantity, total_investment, new_return, additional_investment, old_return
|
38 |
|
39 |
+
def dollar_cost_averaging(old_avg_price, old_quantity, new_price, new_quantity):
|
40 |
css = load_css()
|
41 |
|
42 |
# ์ฃผ์ ๊ฐ๊ฒฉ ๊ฐ์ ธ์ค๊ธฐ
|
43 |
+
# new_price = get_stock_price(stock_code)
|
44 |
|
|
|
45 |
old_avg_price = float(old_avg_price) if old_avg_price else 0.0
|
46 |
old_quantity = float(old_quantity) if old_quantity else 0.0
|
47 |
+
new_price = float(new_price) if new_price else 0.0
|
48 |
new_quantity = float(new_quantity) if new_quantity else 0.0
|
49 |
|
50 |
new_avg_price, total_quantity, total_investment, new_return, additional_investment, old_return = calculate_dollar_cost_averaging(old_avg_price, old_quantity, new_price, new_quantity)
|
|
|
52 |
emoji_return = ""
|
53 |
new_return_class = ""
|
54 |
old_return_class = ""
|
55 |
+
if new_price < old_avg_price:
|
56 |
emoji_return = "๐ง"
|
57 |
+
elif new_price > old_avg_price:
|
58 |
emoji_return = "๐ฅ"
|
59 |
else:
|
60 |
emoji_return = ""
|
|
|
77 |
<div class="wrap-text">
|
78 |
<div>
|
79 |
<div style="margin-bottom: 1.5rem;">
|
80 |
+
<div style="font-size: 2.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">{emoji_return}</div>
|
|
|
|
|
|
|
|
|
81 |
</div>
|
82 |
</div>
|
83 |
<div>
|
84 |
<div style="margin-bottom: 1.5rem;">
|
85 |
<div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Old Price</div>
|
86 |
<div style="font-size: 1.5rem; color: #1c75bc;">
|
87 |
+
<span style='color: #1678fb; font-weight: bold;'>{old_avg_price:,.2f}</span>
|
88 |
<span style='font-size: 1rem;'>{old_return_class}</span>
|
89 |
</div>
|
90 |
<hr style="margin: 1.5rem 0;">
|
|
|
94 |
<div style="margin-bottom: 1.5rem;">
|
95 |
<div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Average Price</div>
|
96 |
<div style="font-size: 1.5rem; color: #1c75bc;">
|
97 |
+
<span style='color: #1678fb; font-weight: bold;'>{new_avg_price:,.2f}</span>
|
98 |
<span style='font-size: 1rem;'>{new_return_class}</span>
|
99 |
</div>
|
100 |
<hr style="margin: 1.5rem 0;">
|
|
|
104 |
<div style="margin-bottom: 1.5rem;">
|
105 |
<div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Additional Investment</div>
|
106 |
<div style="font-size: 1.5rem; font-weight: bold; color: #1c75bc;">
|
107 |
+
<span style='color: #1678fb'>{additional_investment:,.2f}</span>
|
108 |
</div>
|
109 |
<hr style="margin: 1.5rem 0;">
|
110 |
</div>
|
modules/rebalancing.py
CHANGED
@@ -192,17 +192,9 @@ def generate_rebalancing_analysis(portfolio, target_ratios, total_value, main_cu
|
|
192 |
|
193 |
# HTML ์์ฑ
|
194 |
rebalancing_analysis = css + f"""
|
195 |
-
<div
|
196 |
-
|
197 |
-
|
198 |
-
<div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Re-Balancing Analysis | Your Portfolio Holdings as of {current_time}</div>
|
199 |
-
<span style='font-size: 1.5rem; font-weight: bold; color: #1678fb'>{currency_symbol}{format_value(sum(adj['new_value'] for adj in adjustments))}</span>
|
200 |
-
(After Trades)
|
201 |
-
<hr style="margin: 1.5rem 0;">
|
202 |
-
<br>
|
203 |
-
</div>
|
204 |
-
</div>
|
205 |
-
<div class='table-container wrap-text'>
|
206 |
<table>
|
207 |
<thead>
|
208 |
<tr>
|
|
|
192 |
|
193 |
# HTML ์์ฑ
|
194 |
rebalancing_analysis = css + f"""
|
195 |
+
<div>
|
196 |
+
|
197 |
+
<div class='table-container'>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
198 |
<table>
|
199 |
<thead>
|
200 |
<tr>
|
modules/retirement_planning.py
CHANGED
@@ -229,22 +229,16 @@ def retirement_planning(
|
|
229 |
# HTML ๊ฒฐ๊ณผ ์์ฑ
|
230 |
result_html = css + f"""
|
231 |
<div class="wrap-text">
|
232 |
-
<div>
|
233 |
-
<div style="margin-bottom: 1.5rem;">
|
234 |
-
<div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Total Shortfall</div>
|
235 |
-
<span style='font-size: 1.5rem; font-weight: bold; color: #1678fb'>{abs(negative_differences_sum):,.0f}</span>
|
236 |
-
<hr style="margin: 1.5rem 0;">
|
237 |
-
</div>
|
238 |
<div style="margin-bottom: 1.5rem;">
|
239 |
<div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Needs Overview</div>
|
240 |
<iframe src="data:text/html;base64,{svg_base64}" style="width: 100%; height: 400px; border: none;"></iframe>
|
241 |
-
<hr style="margin: 1.5rem 0;">
|
242 |
</div>
|
243 |
</div>
|
244 |
</div>
|
245 |
<div style="margin-bottom: 2rem;"></div>
|
246 |
<div style="display: flex; align-items: center; justify-content: space-between;">
|
247 |
-
<h3>Retirement Plan Overview</h3>
|
248 |
<a href="data:text/csv;base64,{csv_base64}" download="retirement_planning.csv" style="padding: 10px 20px; border: 1px solid; border-radius: 5px; background-color: #1678fb; color: white; text-decoration: none;">Download CSV</a>
|
249 |
</div>
|
250 |
""" + table_html + """
|
|
|
229 |
# HTML ๊ฒฐ๊ณผ ์์ฑ
|
230 |
result_html = css + f"""
|
231 |
<div class="wrap-text">
|
232 |
+
<div>
|
|
|
|
|
|
|
|
|
|
|
233 |
<div style="margin-bottom: 1.5rem;">
|
234 |
<div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Needs Overview</div>
|
235 |
<iframe src="data:text/html;base64,{svg_base64}" style="width: 100%; height: 400px; border: none;"></iframe>
|
|
|
236 |
</div>
|
237 |
</div>
|
238 |
</div>
|
239 |
<div style="margin-bottom: 2rem;"></div>
|
240 |
<div style="display: flex; align-items: center; justify-content: space-between;">
|
241 |
+
<h3>Retirement Plan Overview (Total Shortfall :<span style='font-weight: bold; color: #1678fb'>{abs(negative_differences_sum):,.0f}</span>)</h3>
|
242 |
<a href="data:text/csv;base64,{csv_base64}" download="retirement_planning.csv" style="padding: 10px 20px; border: 1px solid; border-radius: 5px; background-color: #1678fb; color: white; text-decoration: none;">Download CSV</a>
|
243 |
</div>
|
244 |
""" + table_html + """
|
style.css
CHANGED
@@ -64,6 +64,9 @@ body {
|
|
64 |
margin: 0 auto;
|
65 |
max-width: 100%;
|
66 |
font-family: 'Quicksand', 'ui-sans-serif', 'system-ui', 'sans-serif';
|
|
|
|
|
|
|
67 |
}
|
68 |
|
69 |
.h3_title {
|
@@ -86,9 +89,11 @@ body {
|
|
86 |
}
|
87 |
|
88 |
.wrap-text {
|
89 |
-
|
90 |
-
|
91 |
-
|
|
|
|
|
92 |
}
|
93 |
|
94 |
.buy-sell {
|
|
|
64 |
margin: 0 auto;
|
65 |
max-width: 100%;
|
66 |
font-family: 'Quicksand', 'ui-sans-serif', 'system-ui', 'sans-serif';
|
67 |
+
word-wrap: break-word;
|
68 |
+
overflow-wrap: break-word;
|
69 |
+
white-space: normal;
|
70 |
}
|
71 |
|
72 |
.h3_title {
|
|
|
89 |
}
|
90 |
|
91 |
.wrap-text {
|
92 |
+
margin: auto; /* ๊ฐ์ด๋ฐ ์ ๋ ฌ */
|
93 |
+
padding: 20px;
|
94 |
+
border: 1px solid #e5e5e5; /* ์์ ํ
๋๋ฆฌ */
|
95 |
+
border-radius: 10px; /* ๋ชจ์๋ฆฌ ๋ฅ๊ธ๊ฒ */
|
96 |
+
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); /* ๊ทธ๋ฆผ์ */
|
97 |
}
|
98 |
|
99 |
.buy-sell {
|