cryman38 commited on
Commit
34087b9
Β·
verified Β·
1 Parent(s): aed90c0

Delete modules

Browse files
modules/.DS_Store DELETED
Binary file (6.15 kB)
 
modules/__init__.py DELETED
File without changes
modules/dollar_cost_averaging.py DELETED
@@ -1,101 +0,0 @@
1
- from modules.utils import load_css
2
-
3
- def calculate_dollar_cost_averaging(old_avg_price, old_quantity, new_price, new_quantity):
4
- # μž…λ ₯값을 숫자둜 λ³€ν™˜ (각 μž…λ ₯값이 None일 경우 0.0으둜 μ„€μ •)
5
- old_avg_price = float(old_avg_price) if old_avg_price else 0.0
6
- old_quantity = float(old_quantity) if old_quantity else 0.0
7
- new_price = float(new_price) if new_price else 0.0
8
- new_quantity = float(new_quantity) if new_quantity else 0.0
9
-
10
- # ν˜„μž¬ 투자 κΈˆμ•‘ 계산 (이전 평균 가격 * 이전 μˆ˜λŸ‰)
11
- current_investment = old_avg_price * old_quantity
12
- # μΆ”κ°€ 투자 κΈˆμ•‘ 계산 (μƒˆ 가격 * μƒˆ μˆ˜λŸ‰)
13
- additional_investment = new_price * new_quantity
14
- # 총 투자 κΈˆμ•‘ 계산 (ν˜„μž¬ 투자 κΈˆμ•‘ + μΆ”κ°€ 투자 κΈˆμ•‘)
15
- total_investment = current_investment + additional_investment
16
- # 총 주식 수 계산 (이전 μˆ˜λŸ‰ + μƒˆ μˆ˜λŸ‰)
17
- total_quantity = old_quantity + new_quantity
18
- # μƒˆ 평균 가격 계산 (총 투자 κΈˆμ•‘ / 총 주식 수)
19
- new_avg_price = total_investment / total_quantity if total_quantity != 0 else 0.0
20
- # 이전 수읡λ₯  계산 (이전 평균 가격을 κΈ°μ€€μœΌλ‘œ)
21
- old_return = (new_price / old_avg_price - 1) * 100 if old_avg_price != 0 else 0.0
22
- # μƒˆλ‘œμš΄ 수읡λ₯  계산 (μƒˆ 가격 / μƒˆ 평균 가격 - 1) * 100
23
- new_return = (new_price / new_avg_price - 1) * 100 if new_avg_price != 0 else 0.0
24
-
25
- # μƒˆ 평균 가격, 총 μˆ˜λŸ‰, 총 투자 κΈˆμ•‘, μƒˆλ‘œμš΄ 수읡λ₯ , μΆ”κ°€ 투자 κΈˆμ•‘, 이전 수읡λ₯  λ°˜ν™˜
26
- return new_avg_price, total_quantity, total_investment, new_return, additional_investment, old_return
27
-
28
- def dollar_cost_averaging(old_avg_price, old_quantity, new_price, new_quantity):
29
- css = load_css()
30
-
31
- # μž…λ ₯값을 숫자둜 λ³€ν™˜
32
- old_avg_price = float(old_avg_price) if old_avg_price else 0.0
33
- old_quantity = float(old_quantity) if old_quantity else 0.0
34
- new_price = float(new_price) if new_price else 0.0
35
- new_quantity = float(new_quantity) if new_quantity else 0.0
36
-
37
- # 평균 가격, 총 μˆ˜λŸ‰, 총 투자 κΈˆμ•‘, 수읡λ₯  및 μΆ”κ°€ 투자 κΈˆμ•‘ 계산
38
- 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)
39
-
40
- # 수읡λ₯ μ— λ”°λ₯Έ 클래슀 μ„€μ •
41
- emoji_return = ""
42
- new_return_class = ""
43
- old_return_class = ""
44
- if new_return > old_return:
45
- emoji_return = "πŸ’§"
46
- elif new_return < old_return:
47
- emoji_return = "πŸ”₯"
48
- else:
49
- emoji_return = ""
50
-
51
- if new_return > 0:
52
- new_return_class = f"<span style='color: #4caf50; font-weight: bold;'>{new_return:+,.2f}%</span>"
53
- elif new_return < 0:
54
- new_return_class = f"<span style='color: #f44336; font-weight: bold;'>{new_return:,.2f}%</span>"
55
- else:
56
- new_return_class = f"<span><strong>0</strong></span>"
57
-
58
- if old_return > 0:
59
- old_return_class = f"<span style='color: #4caf50; font-weight: bold;'>{old_return:+,.2f}%</span>"
60
- elif old_return < 0:
61
- old_return_class = f"<span style='color: #f44336; font-weight: bold;'>{old_return:,.2f}%</span>"
62
- else:
63
- old_return_class = f"<span><strong>0</strong></span>"
64
-
65
- # HTML κ²°κ³Ό 생성
66
- result_html = css + f"""
67
- <div class="wrap-text">
68
- <div>
69
- <div style="margin-bottom: 1.5rem;">
70
- <!-- μƒˆλ‘œμš΄ 수읡λ₯  ν‘œμ‹œ -->
71
- <div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">{emoji_return}</div>
72
- <div style="font-size: 1.5rem;">
73
- {old_return_class} ➜ {new_return_class}
74
- </div>
75
- <hr style="margin: 1.5rem 0;">
76
- </div>
77
- </div>
78
- <div>
79
- <div style="margin-bottom: 1.5rem;">
80
- <!-- 평균 가격 ν‘œμ‹œ -->
81
- <div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Average Price</div>
82
- <div style="font-size: 1.5rem; font-weight: bold; color: #1c75bc;">
83
- <span style='color: #1678fb'>{new_avg_price:,.0f}</span>
84
- </div>
85
- <hr style="margin: 1.5rem 0;">
86
- </div>
87
- </div>
88
- <div>
89
- <div style="margin-bottom: 1.5rem;">
90
- <!-- μΆ”κ°€ 투자 κΈˆμ•‘ ν‘œμ‹œ -->
91
- <div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Transaction</div>
92
- <div style="font-size: 1.5rem; font-weight: bold; color: #1c75bc;">
93
- <span style='color: #1678fb'>{additional_investment:,.0f}</span>
94
- </div>
95
- <hr style="margin: 1.5rem 0;">
96
- </div>
97
- </div>
98
-
99
- </div>
100
- """
101
- return result_html
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
modules/rebalancing.py DELETED
@@ -1,261 +0,0 @@
1
- import math
2
- import FinanceDataReader as fdr
3
- import yfinance as yf
4
- from concurrent.futures import ThreadPoolExecutor
5
- from modules.utils import load_css, get_currency_symbol, format_quantity, plot_treemap
6
-
7
- def parse_input(holdings, cash_amount):
8
- try:
9
- lines = holdings.strip().split(',')
10
- stock_inputs = []
11
- total_target_ratio = 0
12
-
13
- for line in lines:
14
- parts = line.split()
15
- if len(parts) == 4:
16
- stock_code, currency_code, quantity_expr, target_ratio_expr = parts
17
- # μˆ˜λŸ‰κ³Ό 비쀑을 숫자둜 λ³€ν™˜
18
- quantity = float(quantity_expr.replace(' ', ''))
19
- # λͺ©ν‘œ λΉ„μœ¨μ΄ 빈 λŒ€κ΄„ν˜ΈμΌ 경우 0으둜 처리
20
- if target_ratio_expr.strip() == '[]':
21
- target_ratio = 0
22
- else:
23
- target_ratio = float(eval(target_ratio_expr.strip('[]').replace(' ', '')))
24
-
25
- # μˆ˜λŸ‰μ„ μ •μˆ˜λ‘œ λ‚΄λ¦Ό 처리
26
- quantity = math.floor(quantity)
27
-
28
- stock_inputs.append((currency_code.upper(), stock_code, quantity, target_ratio))
29
- total_target_ratio += target_ratio
30
-
31
- return stock_inputs, cash_amount
32
- except Exception as e:
33
- raise ValueError(f"Input parsing error: {e}")
34
-
35
- def get_portfolio_exchange_rate(currency_code, main_currency):
36
- try:
37
- if currency_code.lower() == main_currency.lower():
38
- return 1.0
39
-
40
- ticker = f"{currency_code.upper()}{main_currency.upper()}=X"
41
- data = yf.download(ticker, period='1d', progress=False)
42
- if not data.empty:
43
- return data['Close'].iloc[0]
44
- else:
45
- raise ValueError("<p style='color: red;'>Failed to retrieve exchange rate data.</p>")
46
- except Exception as e:
47
- raise ValueError(f"<p style='color: red;'>Exchange rate retrieval error: {e}</p>")
48
-
49
- def get_portfolio_exchange_reflected_stock_price(stock_code, currency_code, main_currency):
50
- try:
51
- new_price = get_portfolio_current_stock_price(stock_code)
52
- exchange_rate = get_portfolio_exchange_rate(currency_code, main_currency)
53
- return math.floor(new_price * exchange_rate)
54
- except Exception as e:
55
- raise ValueError(f"<p style='color: red;'>Exchange reflected stock price error: {e}</p>")
56
-
57
- def get_portfolio_current_stock_price(stock_code):
58
- try:
59
- df = fdr.DataReader(stock_code)
60
- return df['Close'].iloc[-1]
61
- except Exception as e:
62
- raise ValueError(f"<p style='color: red;'>Current stock price retrieval error: {e}</p>")
63
-
64
- def build_portfolio(stock_inputs, main_currency):
65
- portfolio = {}
66
- target_ratios = {}
67
-
68
- with ThreadPoolExecutor() as executor:
69
- results = executor.map(lambda x: (x[1], get_portfolio_exchange_reflected_stock_price(x[1], x[0], main_currency), x[2], x[3], x[0]), stock_inputs)
70
-
71
- total_value = 0
72
- for stock_code, new_price, quantity, target_ratio, currency_code in results:
73
- portfolio[stock_code] = {'quantity': quantity, 'price': new_price, 'currency': currency_code}
74
- target_ratios[stock_code] = target_ratio
75
- total_value += new_price * quantity
76
-
77
- # λͺ©ν‘œ λΉ„μœ¨μ˜ 합이 0인 경우, κΈ°λ³Έ λΉ„μœ¨ μ„€μ •
78
- total_target_ratio = sum(target_ratios.values())
79
- if total_target_ratio == 0:
80
- # κΈ°λ³Έ λΉ„μœ¨μ„ μ„€μ •ν•˜κ±°λ‚˜, κ· λ“± λΆ„λ°°
81
- num_stocks = len(stock_inputs)
82
- default_ratio = num_stocks / num_stocks
83
- for stock_code in target_ratios:
84
- target_ratios[stock_code] = default_ratio
85
-
86
- return portfolio, target_ratios, total_value
87
-
88
- def generate_portfolio_info(portfolio, target_ratios, total_value, main_currency, cash_amount):
89
- css = load_css()
90
- currency_symbol = get_currency_symbol(main_currency)
91
-
92
- # ν˜„μž¬ μƒνƒœ 차트
93
- current_weights = {stock_code: (stock['price'] * stock['quantity'] / total_value) * 100 for stock_code, stock in portfolio.items()}
94
- current_chart = plot_treemap(current_weights)
95
-
96
- portfolio_info = css + f"""
97
- <div class="wrap-text">
98
- <div>
99
- <div style="margin-bottom: 1.5rem;">
100
- <div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Your Current Portfolio (Before Trades)</div>
101
- <span style='font-size: 1.5rem; font-weight: bold; color: #1678fb'>{currency_symbol}{format_quantity(total_value)}</span>
102
- <hr style="margin: 1.5rem 0;">
103
- {current_chart}
104
- </div>
105
- </div>
106
- <br>
107
- """
108
-
109
- # ν˜„μž¬ λΉ„μœ¨ 및 κ°€μΉ˜λ₯Ό 계산
110
- current_values = {stock_code: stock['price'] * stock['quantity'] for stock_code, stock in portfolio.items()}
111
- sorted_stocks = current_values.items()
112
-
113
- # ν˜„μž¬ λ³΄μœ λŸ‰ 및 κ°€μΉ˜ μ„Ήμ…˜ ν‘œμ‹œ
114
- current_info_html = "<h3>Your Portfolio Holdings</h3><div class='table-container'><table>"
115
- current_info_html += "<thead><tr><th>Stock Code</th><th>Current Weight (%)</th><th>Current Value</th></tr></thead><tbody>"
116
- for stock_code, current_value in sorted_stocks:
117
- weight = (current_value / total_value) * 100
118
- current_info_html += (
119
- f"<tr>"
120
- f"<td>{stock_code.upper()}</td>"
121
- f"<td>{weight:.1f}%</td>"
122
- f"<td>{currency_symbol}{current_value:,.0f}</td>"
123
- f"</tr>"
124
- )
125
- current_info_html += "</tbody></table></div><br>"
126
-
127
- # 톡화별 포트폴리였 μš”μ•½ 생성
128
- currency_totals = {currency: {'amount': 0, 'weight': 0} for currency in set(stock_data['currency'] for stock_data in portfolio.values())}
129
- for stock_code, stock_data in portfolio.items():
130
- currency = stock_data['currency']
131
- current_value = stock_data['price'] * stock_data['quantity']
132
- currency_totals[currency]['amount'] += current_value
133
- currency_totals[currency]['weight'] += current_value / total_value
134
-
135
- sorted_currencies = currency_totals.items()
136
-
137
- # 톡화별 μš”μ•½ ν…Œμ΄λΈ” 생성
138
- currency_table = "<h3>Your Portfolio by Currency</h3><div class='table-container wrap-text'><table>"
139
- currency_table += "<thead><tr><th>Currency</th><th>Total Weight (%)</th><th>Total Value</th></tr></thead><tbody>"
140
- for currency, data in sorted_currencies:
141
- currency_table += (
142
- f"<tr>"
143
- f"<td>{currency.upper()}</td>"
144
- f"<td>{data['weight'] * 100:.1f}%</td>"
145
- f"<td>{currency_symbol}{data['amount']:,}</td>"
146
- f"</tr>"
147
- )
148
- currency_table += "</tbody></table></div><br>"
149
-
150
- return portfolio_info + current_info_html + currency_table
151
-
152
- def generate_rebalancing_analysis(portfolio, target_ratios, total_value, main_currency, cash_amount, allow_selling):
153
- currency_symbol = get_currency_symbol(main_currency)
154
-
155
- total_trade_value = 0
156
- adjustments = []
157
- new_total_value = cash_amount + total_value
158
-
159
- for stock_code, stock_data in portfolio.items():
160
- current_value = stock_data['price'] * stock_data['quantity']
161
- target_ratio = target_ratios.get(stock_code, 0)
162
- target_weight = target_ratio / sum(target_ratios.values())
163
- target_value = new_total_value * target_weight
164
- difference = target_value - current_value
165
-
166
- if not allow_selling and difference < 0:
167
- # 맀도λ₯Ό ν—ˆμš©ν•˜μ§€ μ•ŠλŠ” 경우, λ§€λ„λŠ” μƒλž΅
168
- trade_quantity = 0
169
- else:
170
- trade_quantity = math.floor(difference / stock_data['price']) if difference > 0 else -math.ceil(-difference / stock_data['price'])
171
-
172
- # κ±°λž˜κ°€ κ°€λŠ₯ν•œμ§€ 확인 (ν˜„κΈˆ μž”κ³  확인 포함)
173
- if trade_quantity > 0 and not allow_selling:
174
- trade_quantity = min(trade_quantity, cash_amount // stock_data['price'])
175
-
176
- new_quantity = trade_quantity + stock_data['quantity']
177
- new_value = new_quantity * stock_data['price']
178
- trade_value = trade_quantity * stock_data['price']
179
- total_trade_value += abs(trade_value)
180
-
181
- current_value_pct = (current_value / total_value) * 100
182
- adjustments.append((difference, current_value, target_ratio, current_value_pct, trade_quantity, stock_code, stock_data['price'], new_value, trade_value, stock_data['quantity'], new_quantity, target_weight, stock_data['currency']))
183
-
184
- total_new_stock_value = sum(adj[7] for adj in adjustments)
185
- new_weights = {stock_code: (new_value / total_new_stock_value) * 100 for _, _, _, _, _, stock_code, _, new_value, _, _, _, _, _ in adjustments}
186
- new_chart = plot_treemap(new_weights)
187
-
188
- for i in range(len(adjustments)):
189
- (difference, current_value, target_ratio, current_value_pct, trade_quantity, stock_code, price, new_value, trade_value, old_quantity, new_quantity, target_weight, currency) = adjustments[i]
190
- new_value_pct = (new_value / total_new_stock_value) * 100
191
- adjustments[i] = (difference, current_value, target_ratio, current_value_pct, trade_quantity, stock_code, price, new_value, trade_value, old_quantity, new_quantity, target_weight, new_value_pct, currency)
192
-
193
- cash_balance = new_total_value - total_new_stock_value
194
-
195
- result_message = f"""
196
- <div>
197
- <div style="margin-bottom: 1.5rem;">
198
- <div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Your Adjusted Portfolio (After Trades)</div>
199
- <span style='font-size: 1.5rem; font-weight: bold; color: #1678fb'>{currency_symbol}{format_quantity(total_new_stock_value)}</span>
200
- Adjusted Portfolio
201
- <div></div>
202
- <span style='font-size: 1.5rem; font-weight: bold; color: #1678fb'>{currency_symbol}{format_quantity(cash_balance)}</span>
203
- cash balance
204
- <hr style="margin: 1.5rem 0;">
205
- {new_chart}
206
- </div>
207
- </div>
208
- <br>
209
- <h3>Trades to Re-Balance Your Portfolio</h3>
210
- <div class='table-container wrap-text'><table>"""
211
- result_message += "<thead><tr><th>Stock Code</th><th>Current Weight (%)</th><th>Target Ratio</th><th>Target Weight (%)</th><th>Buy or Sell?</th><th>Trade Amount</th><th>Current Price per Share</th><th>Estimated # of<br> Shares to Buy or Sell</th><th>Quantity of Units</th><th>Market Value</th><th>% Asset Allocation</th></tr></thead><tbody>"
212
-
213
- for (difference, current_value, target_ratio, current_value_pct, trade_quantity, stock_code, price, new_value, trade_value, old_quantity, new_quantity, target_weight, new_value_pct, currency) in adjustments:
214
- Buy_or_Sell = ""
215
- if trade_quantity > 0:
216
- Buy_or_Sell = f"<span class='buy-sell buy'>Buy</span>"
217
- elif trade_quantity < 0:
218
- Buy_or_Sell = f"<span class='buy-sell sell'>Sell</span>"
219
- else:
220
- Buy_or_Sell = f"<span></span>"
221
-
222
- current_value_pct_str = f"{current_value_pct:.1f}%"
223
- target_ratio_str = f"<span class='highlight-edit'>{target_ratio}</span>"
224
- target_weight_str = f"{target_weight * 100:.1f}%"
225
- trade_value_str = f"<span class='highlight-sky'>{format_quantity(trade_value)}</span>" if trade_value != 0 else ''
226
- price_str = f"{currency_symbol}{price:,.0f}"
227
- trade_quantity_str = f"<span class='highlight-sky'>{format_quantity(trade_quantity)}</span>" if trade_value != 0 else ''
228
- old_quantity_str = f"{old_quantity:,.0f} β†’ {new_quantity:,.0f}"
229
- new_value_str = f"{currency_symbol}{new_value:,.0f}"
230
- new_value_pct_str = f"{new_value_pct:.1f}%"
231
-
232
- result_message += (
233
- f"<tr>"
234
- f"<td>{stock_code.upper()}</td>"
235
- f"<td>{current_value_pct_str}</td>"
236
- f"<td>{target_ratio_str}</td>"
237
- f"<td>{target_weight_str}</td>"
238
- f"<td>{Buy_or_Sell}</td>"
239
- f"<td>{trade_value_str}</td>"
240
- f"<td>{price_str}</td>"
241
- f"<td>{trade_quantity_str}</td>"
242
- f"<td>{old_quantity_str}</td>"
243
- f"<td>{new_value_str}</td>"
244
- f"<td>{new_value_pct_str}</td>"
245
- f"</tr>"
246
- )
247
-
248
- result_message += "</tbody></table><br></div></div>"
249
-
250
- return result_message
251
-
252
- def rebalancing_tool(main_currency, holdings, cash_amount, allow_selling):
253
- try:
254
- stock_inputs, cash_amount = parse_input(holdings, cash_amount)
255
- portfolio, target_ratios, total_value = build_portfolio(stock_inputs, main_currency)
256
- portfolio_info = generate_portfolio_info(portfolio, target_ratios, total_value, main_currency, cash_amount)
257
- rebalancing_analysis = generate_rebalancing_analysis(portfolio, target_ratios, total_value, main_currency, cash_amount, allow_selling)
258
-
259
- return portfolio_info + rebalancing_analysis
260
- except Exception as e:
261
- return f"<p style='color: red;'>An error occurred: {e}</p>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
modules/retirement_planning.py DELETED
@@ -1,222 +0,0 @@
1
- import base64
2
- import csv
3
- from io import StringIO
4
- import matplotlib.pyplot as plt
5
- import io
6
- from modules.utils import load_css
7
-
8
- def retirement_planning(
9
- current_age=None,
10
- retirement_age=None,
11
- life_expectancy=None,
12
- monthly_income_required=None,
13
- inflation_rate=None,
14
- current_investment=None,
15
- monthly_investment=None,
16
- annual_increase_in_monthly_investment=None, # μΆ”κ°€λœ μž…λ ₯
17
- reinvest_dividends=None,
18
- pre_retirement_roi=None,
19
- post_retirement_roi=None,
20
- pre_retirement_dividend_yield=None,
21
- post_retirement_dividend_yield=None
22
- ):
23
- # NoneType일 λ•Œ 0으둜 처리
24
- current_age = current_age if current_age is not None else 0
25
- retirement_age = retirement_age if retirement_age is not None else 0
26
- current_investment = current_investment if current_investment is not None else 0
27
- monthly_investment = monthly_investment if monthly_investment is not None else 0
28
- annual_increase_in_monthly_investment = annual_increase_in_monthly_investment if annual_increase_in_monthly_investment is not None else 0
29
- pre_retirement_roi = pre_retirement_roi if pre_retirement_roi is not None else 0
30
- post_retirement_roi = post_retirement_roi if post_retirement_roi is not None else 0
31
- pre_retirement_dividend_yield = pre_retirement_dividend_yield if pre_retirement_dividend_yield is not None else 0
32
- post_retirement_dividend_yield = post_retirement_dividend_yield if post_retirement_dividend_yield is not None else 0
33
- life_expectancy = life_expectancy if life_expectancy is not None else 0
34
- monthly_income_required = monthly_income_required if monthly_income_required is not None else 0
35
- inflation_rate = inflation_rate if inflation_rate is not None else 0
36
-
37
- # 은퇴 μ „ν›„μ˜ λ…„ 수 계산
38
- if retirement_age > life_expectancy:
39
- return "<p style='color: red;'>Error: Retirement age cannot be greater than life expectancy.</p>"
40
-
41
- if retirement_age < current_age:
42
- return "<p style='color: red;'>Error: Retirement age cannot be less than current age.</p>"
43
-
44
- years_to_retirement = retirement_age - current_age
45
- post_retirement_years = life_expectancy - retirement_age
46
-
47
- # ν˜„μž¬ νˆ¬μžμ•‘μœΌλ‘œ 초기 투자 μ„€μ •
48
- total_investment = current_investment
49
-
50
- # 은퇴 μ „ μ›”κ°„ 이자율 계산
51
- monthly_return_pre = (1 + pre_retirement_roi / 100) ** (1 / 12) - 1
52
-
53
- # 은퇴 μ‹œμ μ˜ 투자 계산
54
- for year in range(years_to_retirement):
55
- for month in range(12):
56
- # μ›”κ°„ νˆ¬μžμ•‘κ³Ό μ΄μžμœ¨μ„ μ μš©ν•˜μ—¬ 총 νˆ¬μžμ•‘ κ°±μ‹ 
57
- total_investment = (total_investment + monthly_investment) * (1 + monthly_return_pre)
58
- # λ°°λ‹ΉκΈˆμ„ μž¬νˆ¬μžν•  경우 λ°°λ‹ΉκΈˆ μΆ”κ°€
59
- if reinvest_dividends:
60
- total_investment += total_investment * (pre_retirement_dividend_yield / 100 / 12)
61
- # μ—°κ°„ 증가앑을 μ›” νˆ¬μžμ•‘μ— μΆ”κ°€
62
- monthly_investment += annual_increase_in_monthly_investment
63
-
64
- # 은퇴 μ‹œμž‘ μ‹œμ μ˜ 총 νˆ¬μžμ•‘κ³Ό μ—°κ°„ λ°°λ‹Ή 수읡 μ €μž₯
65
- investment_at_retirement = total_investment
66
- annual_dividend_at_retirement = investment_at_retirement * (pre_retirement_dividend_yield / 100)
67
- monthly_dividend_at_retirement = annual_dividend_at_retirement / 12
68
-
69
- # 은퇴 ν›„ μ›”κ°„ 이자율 계산
70
- monthly_return_post = (1 + post_retirement_roi / 100) ** (1 / 12) - 1
71
-
72
- # μ—°κ°„ λ¬Όκ°€μƒμŠΉλ₯ μ„ λ°˜μ˜ν•œ μ›” μƒν™œλΉ„ 계산
73
- monthly_income_required_inflated = monthly_income_required
74
- monthly_income_required_over_time = []
75
- for age in range(current_age, life_expectancy + 1):
76
- if age >= retirement_age:
77
- monthly_income_required_over_time.append((age, monthly_income_required_inflated))
78
- monthly_income_required_inflated *= (1 + inflation_rate / 100 / 12) ** 12
79
-
80
- annual_income_required_at_retirement = monthly_income_required_over_time[0][1] * 12
81
- monthly_income_required_at_retirement = monthly_income_required_over_time[0][1]
82
-
83
- # 은퇴 ν›„ 투자 λͺ©λ‘ μ΄ˆκΈ°ν™”
84
- post_retirement_investments = [(retirement_age, investment_at_retirement, annual_income_required_at_retirement, annual_dividend_at_retirement, monthly_dividend_at_retirement, monthly_income_required_at_retirement, annual_dividend_at_retirement - annual_income_required_at_retirement)]
85
-
86
- # 은퇴 ν›„ 각 λ…„λ„μ˜ 투자 및 λ°°λ‹Ή 수읡 계산
87
- for year in range(1, post_retirement_years + 1):
88
- # 은퇴 ν›„ 수읡λ₯ μ„ μ μš©ν•˜μ—¬ 총 νˆ¬μžμ•‘ κ°±μ‹ 
89
- total_investment *= (1 + post_retirement_roi / 100)
90
- # μ—°κ°„ λ°°λ‹Ή 수읡 계산
91
- annual_dividend_income = total_investment * (post_retirement_dividend_yield / 100)
92
- # μ›”κ°„ λ°°λ‹Ή 수읡 계산
93
- monthly_dividend_income = annual_dividend_income / 12
94
- # 연도별 λ¬Όκ°€μƒμŠΉλ₯  λ°˜μ˜ν•œ μ›” μƒν™œλΉ„ κ°±μ‹ 
95
- inflated_income_required = monthly_income_required_over_time[year][1] if year < len(monthly_income_required_over_time) else monthly_income_required_over_time[-1][1]
96
- # 각 연도별 νˆ¬μžμ™€ λ°°λ‹Ή 수읡 및 μ›” μƒν™œλΉ„λ₯Ό λ¦¬μŠ€νŠΈμ— μΆ”κ°€
97
- difference = annual_dividend_income - inflated_income_required * 12
98
- post_retirement_investments.append((retirement_age + year, total_investment, inflated_income_required * 12, annual_dividend_income, monthly_dividend_income, inflated_income_required, difference))
99
-
100
- # λ§ˆμ΄λ„ˆμŠ€ κ°’μ˜ difference 합계 계산
101
- negative_differences_sum = sum(diff for _, _, _, _, _, _, diff in post_retirement_investments if diff < 0)
102
-
103
- # CSV 파일 생성
104
- csv_output = StringIO()
105
- csv_writer = csv.writer(csv_output)
106
- csv_writer.writerow(['Age', 'SAVINGS', 'Annual Income Required', 'Annual Dividend Income', 'Monthly Income Required', 'Monthly Dividend Income', 'Additional Cash Required'])
107
- for age, investment, annual_income_required, annual_dividend_income, monthly_dividend_income, income_required, difference in post_retirement_investments:
108
- additional_cash_required = f'{abs(difference):,.0f}' if difference < 0 else ''
109
- csv_writer.writerow([age, f'{investment:,.0f}', f'{annual_income_required:,.0f}', f'{income_required:,.0f}', f'{annual_dividend_income:,.0f}', f'{monthly_dividend_income:,.0f}', additional_cash_required])
110
-
111
- csv_string = csv_output.getvalue().encode('utf-8')
112
- csv_base64 = base64.b64encode(csv_string).decode('utf-8')
113
-
114
- # style.cssμ—μ„œ CSS 읽기
115
- css = load_css()
116
-
117
- # SVG κ·Έλž˜ν”„ 생성
118
- fig, ax = plt.subplots()
119
- ages = [investment[0] for investment in post_retirement_investments]
120
- income_required = [investment[2] for investment in post_retirement_investments]
121
- dividend_income = [investment[3] for investment in post_retirement_investments]
122
-
123
- ax.plot(ages, income_required, label='Income Required', color='red')
124
- ax.plot(ages, dividend_income, label='Dividend Income', color='green')
125
- ax.set_xlabel('Age')
126
- ax.set_ylabel('Amount')
127
- # ax.set_title('Retirement Plan Overview')
128
- ax.legend()
129
- ax.grid(True)
130
-
131
- # κ·Έλž˜ν”„λ₯Ό SVG ν˜•μ‹μœΌλ‘œ μ €μž₯
132
- svg_output = io.StringIO()
133
- plt.savefig(svg_output, format='svg')
134
- plt.close(fig)
135
- svg_data = svg_output.getvalue()
136
- svg_base64 = base64.b64encode(svg_data.encode('utf-8')).decode('utf-8')
137
-
138
- graph_html = f'<h3 style="margin-bottom: 0.5rem;">Retirement Planning: Income vs. Dividends</h3>'
139
-
140
- # 은퇴 κ³„νšμ— λŒ€ν•œ HTML κ²°κ³Ό 생성
141
- result_html = css + f"""
142
- <div class="wrap-text">
143
- <div>
144
-
145
- <div style="margin-bottom: 1.5rem;">
146
- <div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Total Additional Cash Required After Retirement</div>
147
- <span style='font-size: 1.5rem; font-weight: bold; color: #1678fb'>{abs(negative_differences_sum):,.0f}</span>
148
- <hr style="margin: 1.5rem 0;">
149
- </div>
150
- <div style="margin-bottom: 1.5rem;">
151
- <div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Income Required Immediately After Retirement</div>
152
- <span style='font-size: 1.5rem; font-weight: bold; color: #1678fb'>{annual_income_required_at_retirement:,.0f}</span>
153
- Annual
154
- <div></div>
155
- <span style='font-size: 1.5rem; font-weight: bold; color: #1678fb'>{monthly_income_required_at_retirement:,.0f}</span>
156
- MONTHLY
157
- <hr style="margin: 1.5rem 0;">
158
- </div>
159
- <div style="margin-bottom: 1.5rem;">
160
- <div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Dividend Income Immediately After Retirement</div>
161
- <span style='font-size: 1.5rem; font-weight: bold; color: #1678fb'>{annual_dividend_at_retirement:,.0f}</span>
162
- Annual
163
- <div></div>
164
- <span style='font-size: 1.5rem; font-weight: bold; color: #1678fb'>{monthly_dividend_at_retirement:,.0f}</span>
165
- MONTHLY
166
- <hr style="margin: 1.5rem 0;">
167
- </div>
168
- <div style="margin-bottom: 1.5rem;">
169
- <div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;"></div>
170
- {graph_html}
171
- <img src="data:image/svg+xml;base64,{svg_base64}" alt="Retirement Plan Graph" style="width: 100%; height: auto;"/>
172
- </div>
173
- </div>
174
- </div>
175
- <div style="margin-bottom: 2rem;"></div>
176
- <div style="display: flex; align-items: center; justify-content: space-between;">
177
- <h3>Retirement Plan Overview</h3>
178
- <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>
179
- </div>
180
- <div class='table-container'>
181
- <table>
182
- <thead>
183
- <tr>
184
- <th>Age</th>
185
- <th>SAVINGS</th>
186
- <th>Annual Income Required</th>
187
- <th>Monthly Income Required</th>
188
- <th>Annual Dividend Income</th>
189
- <th>Monthly Dividend Income</th>
190
- <th>Additional Cash Required</th>
191
- </tr>
192
- </thead>
193
- <tbody>
194
- """
195
-
196
- # 각 연도별 νˆ¬μžμ™€ λ°°λ‹Ή 수읡 및 μ›” μƒν™œλΉ„λ₯Ό ν…Œμ΄λΈ”μ— μΆ”κ°€
197
- for age, investment, annual_income_required, annual_dividend_income, monthly_dividend_income, income_required, difference in post_retirement_investments:
198
- additional_cash_required = f'{abs(difference):,.0f}' if difference < 0 else ''
199
- result_html += f"""
200
- <tr>
201
- <td>{age}</td>
202
- <td>{investment:,.0f}</td>
203
- <td>{annual_income_required:,.0f}</td>
204
- <td>{income_required:,.0f}</td>
205
- <td>{annual_dividend_income:,.0f}</td>
206
- <td>{monthly_dividend_income:,.0f}</td>
207
- <td>{additional_cash_required}</td>
208
- </tr>
209
- """
210
-
211
- result_html += """
212
- </tbody>
213
- </table>
214
- </div>
215
- <p style="padding: 10px; border: 1px solid; border-radius: 5px; text-align: center; max-width: 400px; margin: 20px auto;">
216
- <strong>Note:</strong> No additional investments or reinvestment of dividends after retirement.
217
- </p>
218
- """
219
-
220
- return result_html
221
-
222
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
modules/share_price_trend.py DELETED
@@ -1,128 +0,0 @@
1
- import io
2
- import matplotlib.pyplot as plt
3
- import FinanceDataReader as fdr
4
- import pandas as pd
5
- from concurrent.futures import ThreadPoolExecutor, as_completed
6
-
7
- def get_stock_prices(stock_code, days):
8
- try:
9
- df = fdr.DataReader(stock_code)
10
- end_date = pd.to_datetime('today')
11
- start_date = pd.date_range(end=end_date, periods=days, freq='B')[0]
12
- df = df[(df.index >= start_date) & (df.index <= end_date)]
13
- if df.empty:
14
- print(f"<p style='color: red;'>No data available for {stock_code}</p>")
15
- return None
16
- return df['Close']
17
- except Exception as e:
18
- print(f"<p style='color: red;'>Failed to fetch data for {stock_code}: {e}</p>")
19
- return None
20
-
21
- def share_price_trend(stock_codes, days):
22
- stock_prices = {}
23
- with ThreadPoolExecutor(max_workers=10) as executor:
24
- futures = {executor.submit(get_stock_prices, stock_code.strip(), int(days)): stock_code.strip() for stock_code in stock_codes.split(',')}
25
- for future in as_completed(futures):
26
- stock_code = futures[future]
27
- try:
28
- prices = future.result()
29
- if prices is not None:
30
- stock_prices[stock_code] = prices
31
- except Exception as e:
32
- print(f"<p style='color: red;'>Failed to fetch data for {stock_code}: {e}</p>")
33
-
34
- if not stock_prices:
35
- return "<p style='color: red;'>No data available for the provided stock codes.</p>"
36
-
37
- plt.switch_backend('agg')
38
- plt.style.use('tableau-colorblind10')
39
-
40
- fig, ax = plt.subplots(figsize=(8, 4.5))
41
- for stock_code, prices in stock_prices.items():
42
- relative_prices = prices / prices.iloc[0]
43
- ax.plot(prices.index, relative_prices, label=stock_code.upper())
44
-
45
- # Remove the axes and ticks
46
- ax.spines['top'].set_visible(False)
47
- ax.spines['right'].set_visible(False)
48
- ax.spines['left'].set_visible(False)
49
- ax.spines['bottom'].set_visible(False)
50
-
51
- ax.xaxis.set_visible(False)
52
- ax.yaxis.set_visible(False)
53
-
54
- # Add grid for better readability
55
- ax.grid(True, which='both', linestyle='--', linewidth=0.5)
56
-
57
- ax.legend()
58
- plt.tight_layout()
59
-
60
- svg_graph = io.StringIO()
61
- plt.savefig(svg_graph, format='svg')
62
- svg_graph.seek(0)
63
- svg_data = svg_graph.getvalue()
64
- plt.close()
65
-
66
- svg_data = svg_data.replace('<svg ', '<svg width="100%" height="100%" ')
67
- svg_data = svg_data.replace('</svg>', '''
68
- <defs>
69
- <linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
70
- <stop offset="0%" style="stop-color:rgb(173,216,230);stop-opacity:1" />
71
- <stop offset="100%" style="stop-color:rgb(0,191,255);stop-opacity:1" />
72
- </linearGradient>
73
- <filter id="dropshadow" height="130%">
74
- <feGaussianBlur in="SourceAlpha" stdDeviation="3"/>
75
- <feOffset dx="2" dy="2" result="offsetblur"/>
76
- <feMerge>
77
- <feMergeNode/>
78
- <feMergeNode in="SourceGraphic"/>
79
- </feMerge>
80
- </filter>
81
- </defs>
82
- <style>
83
- @keyframes lineAnimation {
84
- from {
85
- stroke-dasharray: 0, 1000;
86
- }
87
- to {
88
- stroke-dasharray: 1000, 0;
89
- }
90
- }
91
- path {
92
- animation: lineAnimation 1s linear forwards;
93
- }
94
- </style>
95
- </svg>''')
96
-
97
- svg_data = svg_data.replace('stroke="#1f77b4"', 'stroke="url(#grad1)" filter="url(#dropshadow)"')
98
-
99
- html_table = "<h3>Stock Prices Data</h3><div class='table-container'><table>"
100
- html_table += "<thead><tr><th>Date</th>"
101
- for stock_code in stock_prices.keys():
102
- html_table += f"<th>{stock_code.upper()}</th>"
103
- html_table += "</tr></thead><tbody>"
104
-
105
- # Create a date range with only business days
106
- all_dates = pd.date_range(start=min(df.index.min() for df in stock_prices.values()), end=pd.to_datetime('today'), freq='B')
107
- all_dates = all_dates[::-1] # Reverse the order of dates
108
-
109
- for date in all_dates:
110
- html_table += f"<tr><td>{date.strftime('%Y-%m-%d')}</td>"
111
- for stock_code in stock_prices.keys():
112
- price = stock_prices[stock_code].get(date, None)
113
- if price is not None:
114
- html_table += f"<td>{price:,.2f}</td>"
115
- else:
116
- html_table += "<td>N/A</td>"
117
- html_table += "</tr>"
118
-
119
- html_table += "</tbody></table></div>"
120
-
121
- graph_html = f'<h3>Relative Stock Prices Over the Last {days} Days</h3>{svg_data}'
122
- return graph_html + html_table
123
-
124
- # Example usage
125
- # stock_codes = "AAPL,MSFT,GOOGL"
126
- # days = 30
127
- # result = share_price_trend(stock_codes, days)
128
- # print(result)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
modules/utils.py DELETED
@@ -1,164 +0,0 @@
1
- import ssl
2
- import logging
3
- import gradio as gr
4
- import matplotlib.pyplot as plt
5
- from io import BytesIO
6
- import base64
7
- from datetime import datetime
8
-
9
- # 둜그 μ„€μ •
10
- #logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
11
-
12
- # SSL μΈμ¦μ„œ 검증 λΉ„ν™œμ„±ν™”
13
- ssl._create_default_https_context = ssl._create_unverified_context
14
-
15
- current_time = datetime.now().strftime("%b-%d-%Y")
16
-
17
- def load_css():
18
- with open('style.css', 'r', encoding='utf-8') as file:
19
- css = file.read()
20
- return f"<style>{css}</style>"
21
-
22
-
23
- def format_quantity(quantity):
24
- if quantity < 0:
25
- return f"({-quantity:,})"
26
- else:
27
- return f"{quantity:,}"
28
-
29
- currency_symbols = {
30
- "KRW": "β‚©",
31
- "USD": "$",
32
- "CAD": "$",
33
- "EUR": "€",
34
- "JPY": "Β₯",
35
- "GBP": "Β£"
36
- }
37
-
38
- def get_currency_symbol(currency_code):
39
- return currency_symbols.get(currency_code.upper(), "")
40
-
41
- def get_currency_codes():
42
- return list(currency_symbols.keys())
43
-
44
- currency_codes = get_currency_codes()
45
-
46
- # Helper function to add buttons
47
- def clear_buttons(inputs):
48
- clear_button = gr.ClearButton(value="Clear")
49
- clear_button.click(
50
- fn=lambda: [None] * len(inputs),
51
- inputs=[],
52
- outputs=inputs
53
- )
54
- return clear_button
55
-
56
- def submit_buttons(inputs, update_fn, output):
57
- submit_button = gr.Button(value="Run", variant="primary")
58
- submit_button.click(
59
- fn=update_fn,
60
- inputs=inputs,
61
- outputs=output
62
- )
63
- return submit_button
64
-
65
- def on_change(inputs, update_ouutput, outputs):
66
- for input_component in inputs:
67
- input_component.change(
68
- fn=update_ouutput,
69
- inputs=inputs,
70
- outputs=outputs
71
- )
72
-
73
- def render_components(component_rows):
74
- for row in component_rows:
75
- if isinstance(row, list):
76
- with gr.Row():
77
- for component in row:
78
- component.render()
79
- else:
80
- row.render()
81
-
82
- def create_tab(tab_name, inputs, outputs, update_fn, examples, component_rows):
83
- with gr.TabItem(tab_name):
84
- with gr.Row():
85
- with gr.Column(elem_classes="input"):
86
- render_components(component_rows)
87
- clear_buttons(inputs)
88
- submit_buttons(inputs, update_fn, outputs)
89
- # gr.Examples(examples=examples, cache_examples=False, inputs=inputs)
90
- with gr.Column():
91
- outputs.render()
92
- on_change(inputs, update_fn, outputs)
93
-
94
- import matplotlib.pyplot as plt
95
- import squarify
96
- from io import BytesIO
97
- import base64
98
- from matplotlib import font_manager
99
-
100
- # Global dictionary to store color mapping
101
- color_map_storage = {}
102
-
103
- def get_color_for_label(label, color_map, num_labels):
104
- """Retrieve or generate color for the given label."""
105
- if label not in color_map_storage:
106
- cmap = plt.get_cmap(color_map)
107
- # Generate a color based on label index
108
- index = len(color_map_storage) % num_labels
109
- color_map_storage[label] = cmap(index / (num_labels - 1))
110
- return color_map_storage[label]
111
-
112
- def plot_treemap(data, color_map='Set3', font_path='Quicksand-Regular.ttf', legend_fontsize=30):
113
- # 데이터 필터링: 비쀑이 0이 μ•„λ‹Œ ν•­λͺ©λ§Œ μΆ”μΆœ
114
- filtered_data = {k: v for k, v in data.items() if v > 0}
115
-
116
- if not filtered_data:
117
- return '<p>No data to display.</p>' # 데이터가 μ—†λŠ” 경우 처리
118
-
119
- labels = [label.upper() for label in filtered_data.keys()] # 라벨을 λŒ€λ¬Έμžλ‘œ λ³€ν™˜
120
- sizes = list(filtered_data.values())
121
-
122
- # 폰트 μ„€μ •
123
- font_properties = font_manager.FontProperties(fname=font_path, size=legend_fontsize)
124
-
125
- # 색상 맡을 μ„€μ •
126
- num_labels = len(labels)
127
-
128
- if len(labels) == 1:
129
- # ν•­λͺ©μ΄ ν•˜λ‚˜μΌ λ•Œ, 전체 μ‚¬κ°ν˜•μ„ κ·Έλ¦½λ‹ˆλ‹€.
130
- rectangles = [{'x': 0, 'y': 0, 'dx': 100, 'dy': 100}]
131
- colors = [get_color_for_label(labels[0], color_map, num_labels)] # 색상 ν•˜λ‚˜λ‘œ μ±„μš°κΈ°
132
- else:
133
- norm_sizes = squarify.normalize_sizes(sizes, 100, 100)
134
- rectangles = squarify.squarify(norm_sizes, x=0, y=0, dx=100, dy=100)
135
- colors = [get_color_for_label(label, color_map, num_labels) for label in labels]
136
-
137
- # 트리맡 μ‹œκ°ν™”
138
- fig, ax = plt.subplots(figsize=(12, 8), dpi=300) # figsize와 dpiλ₯Ό μ„€μ •ν•˜μ—¬ 해상도 높이기
139
- for rect, color in zip(rectangles, colors):
140
- x, y, dx, dy = rect['x'], rect['y'], rect['dx'], rect['dy']
141
- ax.add_patch(plt.Rectangle((x, y), dx, dy, color=color, alpha=0.7))
142
-
143
- # λ²”λ‘€ 생성
144
- handles = [plt.Line2D([0], [0], marker='s', color='w', label=f'{label} {size:.1f}%',
145
- markersize=45, markerfacecolor=get_color_for_label(label, color_map, num_labels))
146
- for label, size in zip(labels, sizes)]
147
-
148
- # λ²”λ‘€ μΆ”κ°€, 제λͺ© 제거, κΈ€μž 크기λ₯Ό ν‚€μš°κ³  λ²”λ‘€ λ°•μŠ€λ₯Ό μ‘°μ •
149
- ax.legend(handles=handles, loc='upper left', bbox_to_anchor=(1, 1),
150
- prop=font_properties, frameon=False)
151
-
152
- ax.set_xlim(0, 100)
153
- ax.set_ylim(0, 100)
154
- ax.axis('off') # 좕을 μˆ¨κΉλ‹ˆλ‹€.
155
-
156
- # SVG둜 μ €μž₯
157
- buf = BytesIO()
158
- plt.savefig(buf, format='svg', bbox_inches='tight') # bbox_inches='tight'λ₯Ό μΆ”κ°€ν•˜μ—¬ λ²”λ‘€κ°€ μž˜λ¦¬λŠ” 문제λ₯Ό λ°©μ§€
159
- plt.close(fig)
160
- buf.seek(0)
161
-
162
- # SVG 데이터λ₯Ό base64둜 인코딩
163
- svg_str = buf.getvalue().decode('utf-8')
164
- return f'<img src="data:image/svg+xml;base64,{base64.b64encode(svg_str.encode("utf-8")).decode("utf-8")}" />'