cryman38 commited on
Commit
069fbff
ยท
verified ยท
1 Parent(s): 34087b9

Upload 16 files

Browse files
interface/.DS_Store ADDED
Binary file (6.15 kB). View file
 
interface/__init__.py ADDED
File without changes
interface/about_interface.py ADDED
@@ -0,0 +1,514 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+
3
+ def render_about_tab():
4
+ with gr.TabItem("๐Ÿค"):
5
+
6
+ # def create_tool_section(tool_name, korean_description, english_description):
7
+ # with gr.Accordion(tool_name, open=False):
8
+ # with gr.Accordion("ํ•œ๊ตญ์–ด", open=False):
9
+ # gr.Markdown(korean_description)
10
+ # with gr.Accordion("English", open=False):
11
+ # gr.Markdown(english_description)
12
+
13
+ # # Portfolio Rebalancing Tool Section
14
+ # create_tool_section(
15
+ # "RE-BALANCING CALCULATOR",
16
+ # """
17
+ # ## ๐Ÿ“Š ๋ฆฌ๋ฐธ๋Ÿฐ์‹ฑ ๊ณ„์‚ฐ๊ธฐ
18
+
19
+ # ์ด ๋„๊ตฌ๋Š” ์ฃผ์–ด์ง„ ์ฃผ์‹ ๋ณด์œ ๋Ÿ‰, ํ˜„๊ธˆ ๊ธˆ์•ก ๋ฐ ํ˜„๊ธˆ ๋น„์œจ์„ ๋ฐ”ํƒ•์œผ๋กœ ํฌํŠธํด๋ฆฌ์˜ค๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์žฌ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
20
+
21
+ # ### ์ž…๋ ฅ๊ฐ’
22
+
23
+ # - **์ฃผ์‹ ๋ณด์œ ๋Ÿ‰ (holdings)**: ๊ฐ ์ฃผ์‹์˜ ์ฝ”๋“œ, ํ†ตํ™” ์ฝ”๋“œ, ์ˆ˜๋Ÿ‰, ๋ชฉํ‘œ ๋น„์œจ์„ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ์ฃผ์‹ ์ข…๋ชฉ์€ ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„ํ•˜์—ฌ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: `AAPL USD 10 0.30,TSLA USD 5 0.20`
24
+ # - **ํ˜„๊ธˆ ๊ธˆ์•ก (cash_amount)**: ํฌํŠธํด๋ฆฌ์˜ค์— ํฌํ•จ๋œ ํ˜„๊ธˆ์˜ ๊ธˆ์•ก์ž…๋‹ˆ๋‹ค. ์˜ˆ: `1000`
25
+ # - **ํ˜„๊ธˆ ๋น„์œจ (cash_ratio)**: ํฌํŠธํด๋ฆฌ์˜ค์—์„œ ํ˜„๊ธˆ์ด ์ฐจ์ง€ํ•˜๋Š” ๋น„์œจ์ž…๋‹ˆ๋‹ค. ์˜ˆ: `10` (10%)
26
+
27
+ # ### ๊ธฐ๋Šฅ
28
+
29
+ # 1. **์ž…๋ ฅ ํŒŒ์‹ฑ**: ์ฃผ์‹ ๋ณด์œ ๋Ÿ‰, ํ˜„๊ธˆ ๊ธˆ์•ก ๋ฐ ๋น„์œจ์„ ๋ถ„์„ํ•˜์—ฌ ๊ฐ ์ฃผ์‹์˜ ๋ชฉํ‘œ ๋น„์œจ์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
30
+ # 2. **ํ™˜์œจ ์กฐํšŒ**: ๊ฐ ์ฃผ์‹์˜ ํ†ตํ™” ์ฝ”๋“œ์™€ ๋ฉ”์ธ ํ†ตํ™” ๊ฐ„์˜ ํ™˜์œจ์„ ์กฐํšŒํ•˜์—ฌ ์ฃผ์‹ ๊ฐ€๊ฒฉ์„ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
31
+ # 3. **ํฌํŠธํด๋ฆฌ์˜ค ๊ตฌ์ถ•**: ์ฃผ์‹๊ณผ ํ˜„๊ธˆ ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ํฌํŠธํด๋ฆฌ์˜ค๋ฅผ ๊ตฌ์ถ•ํ•ฉ๋‹ˆ๋‹ค.
32
+ # 4. **ํฌํŠธํด๋ฆฌ์˜ค ์žฌ์กฐ์ • ์ •๋ณด ์ œ๊ณต**: ํ˜„์žฌ ํฌํŠธํด๋ฆฌ์˜ค์˜ ๊ฐ€์น˜์™€ ๋ชฉํ‘œ ๋น„์œจ์„ ๋น„๊ตํ•˜์—ฌ ์žฌ์กฐ์ • ํ•„์š”์„ฑ์„ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.
33
+ # - **ํ˜„์žฌ ๋ณด์œ ๋Ÿ‰ ๋ฐ ๊ฐ€์น˜**: ๊ฐ ์ฃผ์‹ ๋ฐ ํ˜„๊ธˆ์˜ ํ˜„์žฌ ๋ณด์œ ๋Ÿ‰๊ณผ ๊ฐ€์น˜๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
34
+ # - **์žฌ์กฐ์ • ํ•ญ๋ชฉ**: ๋ชฉํ‘œ ๋น„์œจ์— ๋งž์ถ”๊ธฐ ์œ„ํ•ด ๋งค์ˆ˜ ๋˜๋Š” ๋งค๋„ํ•ด์•ผ ํ•  ์ฃผ์‹ ์ˆ˜๋Ÿ‰๊ณผ ๊ธˆ์•ก์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
35
+ # - **ํ†ตํ™”๋ณ„ ์š”์•ฝ**: ํฌํŠธํด๋ฆฌ์˜ค์˜ ํ†ตํ™”๋ณ„ ๋ถ„ํฌ๋ฅผ ์š”์•ฝํ•ฉ๋‹ˆ๋‹ค.
36
+
37
+ # ### ๊ฒฐ๊ณผ
38
+
39
+ # - **ํฌํŠธํด๋ฆฌ์˜ค ์ •๋ณด**: ํ˜„์žฌ ํฌํŠธํด๋ฆฌ์˜ค์˜ ์ด ๊ฐ€์น˜ ๋ฐ ํ˜„๊ธˆ ์ •๋ณด๋ฅผ ํฌํ•จํ•œ HTML ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.
40
+ # - **ํ˜„์žฌ ๋ณด์œ ๋Ÿ‰ ๋ฐ ๊ฐ€์น˜**: ๊ฐ ์ฃผ์‹์˜ ํ˜„์žฌ ๋น„์œจ ๋ฐ ๊ฐ€์น˜์— ๋Œ€ํ•œ ์ •๋ณด์ž…๋‹ˆ๋‹ค.
41
+ # - **์žฌ์กฐ์ • ๋ถ„์„**: ์ฃผ์‹์˜ ๋งค์ˆ˜/๋งค๋„ ์กฐ์ •์ด ํ•„์š”ํ•œ ํ•ญ๋ชฉ ๋ฐ ๊ธˆ์•ก์— ๋Œ€ํ•œ ์ƒ์„ธ ๋ถ„์„์ž…๋‹ˆ๋‹ค.
42
+ # - **ํ†ตํ™”๋ณ„ ์š”์•ฝ**: ํฌํŠธํด๋ฆฌ์˜ค์˜ ํ†ตํ™”๋ณ„ ๋น„์œจ ๋ฐ ์ด ๊ฐ€์น˜๋ฅผ ์š”์•ฝํ•œ ์ •๋ณด์ž…๋‹ˆ๋‹ค.
43
+
44
+ # ์ด ๋„๊ตฌ๋ฅผ ํ†ตํ•ด ํฌํŠธํด๋ฆฌ์˜ค๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์žฌ์กฐ์ •ํ•˜๊ณ , ์ฃผ์‹ ๋ฐ ํ˜„๊ธˆ์˜ ๋น„์œจ์„ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค! ๐Ÿ“ˆ๐Ÿ’ผ
45
+
46
+ # ### ์ค‘์š” ์‚ฌํ•ญ
47
+ # - ์ฃผ์‹ ์ฝ”๋“œ์™€ ํ†ตํ™” ์ฝ”๋“œ๊ฐ€ ์œ ํšจํ•œ์ง€ ํ™•์ธํ•˜์„ธ์š”.
48
+ # - ์ด ๋„๊ตฌ๋Š” ์ฃผ๊ฐ€์™€ ํ™˜์œจ์˜ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์ธํ„ฐ๋„ท ์—ฐ๊ฒฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
49
+ # - Yahoo Finance ๋ฐ FinanceDataReader์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
50
+
51
+ # ### ๋ฉด์ฑ… ์กฐํ•ญ
52
+ # ์ด ๋„๊ตฌ์—์„œ ์ œ๊ณตํ•˜๋Š” ์ •๋ณด๋Š” ์ผ๋ฐ˜์ ์ธ ์ •๋ณด ์ œ๊ณต ๋ชฉ์ ๋งŒ์„ ์œ„ํ•ด ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. ์‚ฌ์ดํŠธ์˜ ๋ชจ๋“  ์ •๋ณด๋Š” ์„ ์˜๋กœ ์ œ๊ณต๋˜์ง€๋งŒ, ์‚ฌ์ดํŠธ์˜ ์ •๋ณด์˜ ์ •ํ™•์„ฑ, ์ ์ ˆ์„ฑ, ์œ ํšจ์„ฑ, ์‹ ๋ขฐ์„ฑ, ๊ฐ€์šฉ์„ฑ ๋˜๋Š” ์™„์ „์„ฑ์— ๋Œ€ํ•ด ๋ช…์‹œ์ ์ด๋“  ๋ฌต์‹œ์ ์ด๋“  ์–ด๋– ํ•œ ์ข…๋ฅ˜์˜ ์ง„์ˆ ์ด๋‚˜ ๋ณด์ฆ์„ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์‚ฌ์ดํŠธ์˜ ์‚ฌ์šฉ๊ณผ ์‚ฌ์ดํŠธ์˜ ์ •๋ณด๋ฅผ ์‹ ๋ขฐํ•˜๋Š” ๊ฒƒ์€ ์ „์ ์œผ๋กœ ์‚ฌ์šฉ์ž์˜ ์ฑ…์ž„์ž…๋‹ˆ๋‹ค.
53
+ # """,
54
+ # """
55
+ # ## ๐Ÿ“Š RE-BALANCING CALCULATOR
56
+
57
+ # This tool analyzes and rebalances a portfolio based on given stock holdings, cash amount, and cash ratio.
58
+
59
+ # ### Input Values
60
+
61
+ # - **Stock Holdings (holdings)**: Enter the stock code, currency code, quantity, and target weight for each stock. Separate multiple stocks with commas. Example: `AAPL USD 10 0.30,TSLA USD 5 0.20`
62
+ # - **Cash Amount (cash_amount)**: The amount of cash in the portfolio. Example: `1000`
63
+ # - **Cash Ratio (cash_ratio)**: The proportion of cash in the portfolio. Example: `10` (10%)
64
+
65
+ # ### Functionality
66
+
67
+ # 1. **Parse Input**: Analyzes stock holdings, cash amount, and ratio to calculate the target weights for each stock.
68
+ # 2. **Exchange Rate Lookup**: Retrieves exchange rates between each stock's currency code and the main currency to convert stock prices.
69
+ # 3. **Build Portfolio**: Constructs the portfolio based on stock and cash information.
70
+ # 4. **Get Portfolio Rebalancing Information**: Compares the current portfolio value and target weights to analyze rebalancing needs.
71
+ # - **Current Holdings and Value**: Calculates the current holdings and value of each stock and cash.
72
+ # - **Rebalancing Items**: Calculates the quantities and amounts of stocks to buy or sell to meet target weights.
73
+ # - **Currency Summary**: Summarizes the portfolio's distribution by currency.
74
+
75
+ # ### Results
76
+
77
+ # - **Portfolio Information**: HTML code containing the total value of the current portfolio and cash information.
78
+ # - **Current Holdings and Value**: Information on the current weights and values of each stock.
79
+ # - **Rebalancing Analysis**: Detailed analysis of the adjustments needed to buy/sell stocks to meet target weights.
80
+ # - **Currency Summary**: Summary of portfolio distribution by currency, including total value.
81
+
82
+ # Use this tool to efficiently rebalance your portfolio and optimize the ratio of stocks and cash! ๐Ÿ“ˆ๐Ÿ’ผ
83
+
84
+ # ### Important Notes
85
+ # - Ensure stock codes and currency codes are valid.
86
+ # - This tool requires an internet connection as it uses real-time stock prices and exchange rates.
87
+ # - Data is sourced from Yahoo Finance and FinanceDataReader.
88
+
89
+ # ### Disclaimer
90
+ # The information provided by this tool is for general informational purposes only. All information on the site is provided in good faith, but we make no representation or warranty of any kind, express or implied, regarding the accuracy, adequacy, validity, reliability, availability, or completeness of any information on the site. Your use of the site and reliance on any information on the site is solely at your own risk.
91
+ # """
92
+ # )
93
+
94
+ # # Stock Comparison Tool Section
95
+ # create_tool_section(
96
+ # "STOCK PRICE TREND VISUALIZATION",
97
+ # """
98
+ # ## ๐Ÿ“ˆ ์ฃผ์‹ ๊ฐ€๊ฒฉ ์ถ”์„ธ ์‹œ๊ฐํ™”
99
+
100
+ # ์ด ์ฝ”๋“œ๋Š” ์ฃผ์–ด์ง„ ์ฃผ์‹ ์ฝ”๋“œ๋“ค์˜ ๊ฐ€๊ฒฉ ์ถ”์„ธ๋ฅผ ์‹œ๊ฐํ™”ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ์ฃผ์‹์˜ ๊ฐ€๊ฒฉ์„ ์ถ”์ ํ•˜๊ณ  ๋น„๊ตํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๐Ÿ“Š
101
+
102
+ # ### ์ž…๋ ฅ๊ฐ’
103
+
104
+ # - **์ฃผ์‹ ์ฝ”๋“œ (stock_codes)**: ๊ฐ€๊ฒฉ์„ ์ถ”์ ํ•  ์ฃผ์‹์˜ ์ฝ”๋“œ๋“ค์ž…๋‹ˆ๋‹ค. ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„๋œ ๋ฌธ์ž์—ด ํ˜•์‹์œผ๋กœ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: `AAPL,GOOGL,MSFT`
105
+ # - **์ผ์ˆ˜ (days)**: ๊ฐ€๊ฒฉ์„ ์กฐํšŒํ•  ๊ธฐ๊ฐ„์ž…๋‹ˆ๋‹ค. ์˜ˆ: `30` (์ตœ๊ทผ 30์ผ)
106
+
107
+ # ### ๊ธฐ๋Šฅ
108
+
109
+ # 1. **์ฃผ์‹ ๊ฐ€๊ฒฉ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ**: ์ฃผ์–ด์ง„ ์ฃผ์‹ ์ฝ”๋“œ์™€ ๊ธฐ๊ฐ„์— ๋Œ€ํ•ด ์ฃผ์‹ ๊ฐ€๊ฒฉ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
110
+ # 2. **๊ฐ€๊ฒฉ ๋ณ€๋™ ์‹œ๊ฐํ™”**: ๊ฐ ์ฃผ์‹์˜ ๊ฐ€๊ฒฉ ๋ณ€๋™์„ ์‹œ๊ฐํ™”ํ•˜์—ฌ ๋น„๊ตํ•  ์ˆ˜ ์žˆ๋Š” ๊ทธ๋ž˜ํ”„๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
111
+ # - **์ƒ๋Œ€ ๊ฐ€๊ฒฉ**: ๊ฐ ์ฃผ์‹์˜ ๊ฐ€๊ฒฉ์„ ์ฒ˜์Œ ๊ฐ€๊ฒฉ์— ๋Œ€ํ•ด ์ƒ๋Œ€์ ์œผ๋กœ ๋ณ€๋™์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.
112
+ # - **์Šคํƒ€์ผ**: ๊ทธ๋ž˜ํ”„์—๋Š” ์ƒ‰์ƒ ๊ทธ๋ผ๋””์–ธํŠธ์™€ ๊ทธ๋ฆผ์ž ํšจ๊ณผ๊ฐ€ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.
113
+ # - **์• ๋‹ˆ๋ฉ”์ด์…˜**: ์„ ์˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ๊ฐ€ ์ถ”๊ฐ€๋˜์–ด ์‹œ๊ฐ์ ์œผ๋กœ ๋”์šฑ ๋งค๋ ฅ์ ์ž…๋‹ˆ๋‹ค.
114
+ # 3. **๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ” ์ƒ์„ฑ**: ๊ฐ ๋‚ ์งœ์™€ ์ฃผ์‹์˜ ๊ฐ€๊ฒฉ์„ ํฌํ•จํ•œ HTML ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
115
+
116
+ # ### ๊ฒฐ๊ณผ
117
+
118
+ # - **๊ทธ๋ž˜ํ”„**: ์ตœ๊ทผ `days` ์ผ ๋™์•ˆ์˜ ์ƒ๋Œ€ ์ฃผ์‹ ๊ฐ€๊ฒฉ ์ถ”์„ธ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๊ทธ๋ž˜ํ”„์ž…๋‹ˆ๋‹ค.
119
+ # - **๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ”**: ๋‚ ์งœ๋ณ„๋กœ ๊ฐ ์ฃผ์‹์˜ ๊ฐ€๊ฒฉ์„ ํฌํ•จํ•˜๋Š” HTML ํ…Œ์ด๋ธ”์ž…๋‹ˆ๋‹ค.
120
+
121
+ # ### ์‚ฌ์šฉ ์˜ˆ์ œ
122
+
123
+ # ์ฃผ์‹ ์ฝ”๋“œ์™€ ๊ธฐ๊ฐ„์„ ์ž…๋ ฅํ•˜๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
124
+
125
+ # - **์ฃผ์‹ ์ฝ”๋“œ**: `AAPL,GOOGL,MSFT`
126
+ # - **์ผ์ˆ˜**: `30`
127
+
128
+ # ๊ฒฐ๊ณผ๋Š” ์ตœ๊ทผ 30์ผ ๋™์•ˆ์˜ ์ฃผ์‹ ๊ฐ€๊ฒฉ ๋ณ€๋™์„ ๋ณด์—ฌ์ฃผ๋Š” ๊ทธ๋ž˜ํ”„์™€ ๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ”์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.
129
+
130
+ # ์ด ๋„๊ตฌ๋ฅผ ํ†ตํ•ด ์ฃผ์‹ ๊ฐ€๊ฒฉ์˜ ๋ณ€๋™ ์ถ”์„ธ๋ฅผ ํ•œ๋ˆˆ์— ํ™•์ธํ•˜๊ณ  ๋ถ„์„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค! ๐Ÿ“ˆ๐Ÿ’ก
131
+
132
+ # ### ์ค‘์š” ์‚ฌํ•ญ
133
+ # - ์ฃผ์‹ ์ฝ”๋“œ๊ฐ€ ์œ ํšจํ•œ์ง€ ํ™•์ธํ•˜์„ธ์š”.
134
+ # - ์ด ๋„๊ตฌ๋Š” ์ฃผ๊ฐ€์˜ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์ธํ„ฐ๋„ท ์—ฐ๊ฒฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
135
+ # - Yahoo Finance ๋ฐ FinanceDataReader์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
136
+
137
+ # ### ๋ฉด์ฑ… ์กฐํ•ญ
138
+ # ์ด ๋„๊ตฌ์—์„œ ์ œ๊ณตํ•˜๋Š” ์ •๋ณด๋Š” ์ผ๋ฐ˜์ ์ธ ์ •๋ณด ์ œ๊ณต ๋ชฉ์ ๋งŒ์„ ์œ„ํ•ด ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. ์‚ฌ์ดํŠธ์˜ ๋ชจ๋“  ์ •๋ณด๋Š” ์„ ์˜๋กœ ์ œ๊ณต๋˜์ง€๋งŒ, ์‚ฌ์ดํŠธ์˜ ์ •๋ณด์˜ ์ •ํ™•์„ฑ, ์ ์ ˆ์„ฑ, ์œ ํšจ์„ฑ, ์‹ ๋ขฐ์„ฑ, ๊ฐ€์šฉ์„ฑ ๋˜๋Š” ์™„์ „์„ฑ์— ๋Œ€ํ•ด ๋ช…์‹œ์ ์ด๋“  ๋ฌต์‹œ์ ์ด๋“  ์–ด๋– ํ•œ ์ข…๋ฅ˜์˜ ์ง„์ˆ ์ด๋‚˜ ๋ณด์ฆ์„ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์‚ฌ์ดํŠธ์˜ ์‚ฌ์šฉ๊ณผ ์‚ฌ์ดํŠธ์˜ ์ •๋ณด๋ฅผ ์‹ ๋ขฐํ•˜๋Š” ๊ฒƒ์€ ์ „์ ์œผ๋กœ ์‚ฌ์šฉ์ž์˜ ์ฑ…์ž„์ž…๋‹ˆ๋‹ค.
139
+ # """,
140
+ # """
141
+ # ## ๐Ÿ“ˆ Stock Price Trend Visualization
142
+
143
+ # This script visualizes the price trends of given stock codes over a specified period. It allows you to track and compare the price movements of multiple stocks. ๐Ÿ“Š
144
+
145
+ # ### Input Values
146
+
147
+ # - **Stock Codes (stock_codes)**: The codes of the stocks you want to track. Enter them as a comma-separated string. Example: `AAPL,GOOGL,MSFT`
148
+ # - **Days (days)**: The period for which you want to fetch the price data. Example: `30` (last 30 days)
149
+
150
+ # ### Functionality
151
+
152
+ # 1. **Fetch Stock Price Data**: Retrieves stock price data for the given stock codes and period.
153
+ # 2. **Visualize Price Trends**: Creates a graph to visualize and compare the price trends of each stock.
154
+ # - **Relative Price**: Shows each stock's price relative to its initial price.
155
+ # - **Style**: The graph includes color gradients and shadow effects.
156
+ # - **Animation**: Line animation effects are added for visual appeal.
157
+ # 3. **Generate Data Table**: Creates an HTML table with the price data for each stock and date.
158
+
159
+ # ### Results
160
+
161
+ # - **Graph**: A graph showing the relative stock price trends over the last `days` days.
162
+ # - **Data Table**: An HTML table displaying the prices of each stock by date.
163
+
164
+ # ### Example Usage
165
+
166
+ # Given stock codes and days, you can obtain results such as:
167
+
168
+ # - **Stock Codes**: `AAPL,GOOGL,MSFT`
169
+ # - **Days**: `30`
170
+
171
+ # The result will include a graph showing the price trends over the last 30 days and an HTML table with the price data.
172
+
173
+ # Use this tool to easily analyze and visualize stock price movements! ๐Ÿ“ˆ๐Ÿ’ก
174
+
175
+ # ### Important Notes
176
+ # - Ensure stock codes are valid.
177
+ # - This tool requires an internet connection as it uses real-time stock prices.
178
+ # - Data is sourced from Yahoo Finance and FinanceDataReader.
179
+
180
+ # ### Disclaimer
181
+ # The information provided by this tool is for general informational purposes only. All information on the site is provided in good faith, but we make no representation or warranty of any kind, express or implied, regarding the accuracy, adequacy, validity, reliability, availability, or completeness of any information on the site. Your use of the site and reliance on any information on the site is solely at your own risk.
182
+ # """
183
+ # )
184
+
185
+ # # Cost Averaging Tool Section
186
+ # create_tool_section(
187
+ # "DOLLAR-COST AVERAGING CALCULATOR",
188
+ # """
189
+ # ## ๐Ÿ’ธ ๋ฌผํƒ€๊ธฐ ๊ณ„์‚ฐ๊ธฐ
190
+
191
+ # ๋ฌผํƒ€๊ธฐ(Dollar-Cost Averaging, DCA) ๊ณ„์‚ฐ๊ธฐ๋ฅผ ์ด์šฉํ•ด ๋ณด์„ธ์š”! ์ด ๊ณ„์‚ฐ๊ธฐ๋Š” ํŠน์ • ์ฃผ์‹์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ํˆฌ์ž๋ฅผ ํ•  ๋•Œ ํ‰๊ท  ๊ตฌ๋งค ๊ฐ€๊ฒฉ์„ ๊ณ„์‚ฐํ•ด์ค๋‹ˆ๋‹ค. ๐Ÿ“ˆ
192
+
193
+ # ### ์ž…๋ ฅ๊ฐ’ ์„ค์ • ๐Ÿ“Š
194
+
195
+ # ๋‹ค์Œ์˜ ๊ฐ’์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”:
196
+
197
+ # - **์ด์ „ ํ‰๊ท  ๊ฐ€๊ฒฉ (old_avg_price)**: ๊ธฐ์กด ์ฃผ์‹์˜ ํ‰๊ท  ๊ฐ€๊ฒฉ ๐Ÿ’ต
198
+ # - **์ด์ „ ์ˆ˜๋Ÿ‰ (old_quantity)**: ๊ธฐ์กด ์ฃผ์‹์˜ ์ˆ˜๋Ÿ‰ ๐Ÿ“ฆ
199
+ # - **์ƒˆ ๊ฐ€๊ฒฉ (new_price)**: ์ƒˆ๋กœ ๊ตฌ๋งคํ•œ ์ฃผ์‹์˜ ๊ฐ€๊ฒฉ ๐Ÿ’ฒ
200
+ # - **์ƒˆ ์ˆ˜๋Ÿ‰ (new_quantity)**: ์ƒˆ๋กœ ๊ตฌ๋งคํ•œ ์ฃผ์‹์˜ ์ˆ˜๋Ÿ‰ ๐Ÿ“ˆ
201
+
202
+ # ### ๊ณ„์‚ฐ ๋ฐฉ๋ฒ• ๐Ÿ”ข
203
+
204
+ # 1. **ํ˜„์žฌ ํˆฌ์ž ๊ธˆ์•ก ๊ณ„์‚ฐ**: ์ด์ „ ํ‰๊ท  ๊ฐ€๊ฒฉ๊ณผ ์ด์ „ ์ˆ˜๋Ÿ‰์„ ๊ณฑํ•˜์—ฌ ํ˜„์žฌ ํˆฌ์ž ๊ธˆ์•ก์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
205
+ # 2. **์ถ”๊ฐ€ ํˆฌ์ž ๊ธˆ์•ก ๊ณ„์‚ฐ**: ์ƒˆ ๊ฐ€๊ฒฉ๊ณผ ์ƒˆ ์ˆ˜๋Ÿ‰์„ ๊ณฑํ•˜์—ฌ ์ถ”๊ฐ€ ํˆฌ์ž ๊ธˆ์•ก์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
206
+ # 3. **์ด ํˆฌ์ž ๊ธˆ์•ก ๋ฐ ์ด ์ฃผ์‹ ์ˆ˜ ๊ณ„์‚ฐ**: ํ˜„์žฌ ํˆฌ์ž ๊ธˆ์•ก๊ณผ ์ถ”๊ฐ€ ํˆฌ์ž ๊ธˆ์•ก์„ ํ•ฉ์ณ์„œ ์ด ํˆฌ์ž ๊ธˆ์•ก์„ ๊ณ„์‚ฐํ•˜๊ณ , ์ด์ „ ์ˆ˜๋Ÿ‰๊ณผ ์ƒˆ ์ˆ˜๋Ÿ‰์„ ๋”ํ•˜์—ฌ ์ด ์ฃผ์‹ ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
207
+ # 4. **์ƒˆ ํ‰๊ท  ๊ฐ€๊ฒฉ ๊ณ„์‚ฐ**: ์ด ํˆฌ์ž ๊ธˆ์•ก์„ ์ด ์ฃผ์‹ ์ˆ˜๋กœ ๋‚˜๋ˆ„์–ด ์ƒˆ๋กœ์šด ํ‰๊ท  ๊ฐ€๊ฒฉ์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
208
+ # 5. **์ˆ˜์ต๋ฅ  ๊ณ„์‚ฐ**: ์ด์ „๊ณผ ์ƒˆ๋กœ์šด ์ˆ˜์ต๋ฅ ์„ ๊ณ„์‚ฐํ•˜์—ฌ ํˆฌ์ž ๊ฒฐ๊ณผ๋ฅผ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.
209
+
210
+ # ### ๊ฒฐ๊ณผ ๐ŸŽฏ
211
+
212
+ # **1) ์ด์ „ ์ˆ˜์ต๋ฅ  (Old Return)**
213
+
214
+ # ์ด์ „ ํ‰๊ท  ๊ฐ€๊ฒฉ์„ ๊ธฐ์ค€์œผ๋กœ ํ•œ ์ˆ˜์ต๋ฅ ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
215
+
216
+ # **2) ์ƒˆ๋กœ์šด ์ˆ˜์ต๋ฅ  (New Return)**
217
+
218
+ # ์ƒˆ๋กœ์šด ํ‰๊ท  ๊ฐ€๊ฒฉ์„ ๊ธฐ์ค€์œผ๋กœ ํ•œ ์ˆ˜์ต๋ฅ ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
219
+
220
+ # **3) ์ถ”๊ฐ€ ํˆฌ์ž ๊ธˆ์•ก (Additional Investment)**
221
+
222
+ # ์ถ”๊ฐ€๋กœ ํˆฌ์žํ•œ ๊ธˆ์•ก์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
223
+
224
+ # **4) ์ƒˆ๋กœ์šด ํ‰๊ท  ๊ฐ€๊ฒฉ (Average Price)**
225
+
226
+ # ์ƒˆ๋กญ๊ฒŒ ๊ณ„์‚ฐ๋œ ํ‰๊ท  ๊ฐ€๊ฒฉ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
227
+
228
+ # **5) ์ด ์ˆ˜๋Ÿ‰ (Total Quantity)**
229
+
230
+ # ์ด ์ฃผ์‹ ์ˆ˜๋Ÿ‰์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
231
+
232
+ # **6) ์ด ํˆฌ์ž ๊ธˆ์•ก (Total Investment)**
233
+
234
+ # ์ด ํˆฌ์ž ๊ธˆ์•ก์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
235
+
236
+ # ### ์˜ˆ์ œ ๐Ÿ“‰
237
+
238
+ # - **์ด์ „ ํ‰๊ท  ๊ฐ€๊ฒฉ**: 100
239
+ # - **์ด์ „ ์ˆ˜๋Ÿ‰**: 10
240
+ # - **์ƒˆ ๊ฐ€๊ฒฉ**: 120
241
+ # - **์ƒˆ ์ˆ˜๋Ÿ‰**: 5
242
+
243
+ # ๊ฒฐ๊ณผ:
244
+ # - ์ƒˆ๋กœ์šด ํ‰๊ท  ๊ฐ€๊ฒฉ: 105
245
+ # - ์ด ์ˆ˜๋Ÿ‰: 15
246
+ # - ์ด ํˆฌ์ž ๊ธˆ์•ก: 1,575
247
+ # - ์ด์ „ ์ˆ˜์ต๋ฅ : 20.00%
248
+ # - ์ƒˆ๋กœ์šด ์ˆ˜์ต๋ฅ : 14.29%
249
+ # - ์ถ”๊ฐ€ ํˆฌ์ž ๊ธˆ์•ก: 600
250
+
251
+ # ๋ฌผํƒ€๊ธฐ ๊ณ„์‚ฐ๊ธฐ๋ฅผ ํ†ตํ•ด ํˆฌ์ž์˜ ํ‰๊ท  ๊ตฌ๋งค ๊ฐ€๊ฒฉ์„ ์‰ฝ๊ฒŒ ๊ณ„์‚ฐํ•ด ๋ณด์„ธ์š”! ๐Ÿ“Š๐Ÿ’ฐ
252
+
253
+ # ### ๋ฉด์ฑ… ์กฐํ•ญ
254
+ # ์ด ๋„๊ตฌ์—์„œ ์ œ๊ณตํ•˜๋Š” ์ •๋ณด๋Š” ์ผ๋ฐ˜์ ์ธ ์ •๋ณด ์ œ๊ณต ๋ชฉ์ ๋งŒ์„ ์œ„ํ•ด ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. ์‚ฌ์ดํŠธ์˜ ๋ชจ๋“  ์ •๋ณด๋Š” ์„ ์˜๋กœ ์ œ๊ณต๋˜์ง€๋งŒ, ์‚ฌ์ดํŠธ์˜ ์ •๋ณด์˜ ์ •ํ™•์„ฑ, ์ ์ ˆ์„ฑ, ์œ ํšจ์„ฑ, ์‹ ๋ขฐ์„ฑ, ๊ฐ€์šฉ์„ฑ ๋˜๋Š” ์™„์ „์„ฑ์— ๋Œ€ํ•ด ๋ช…์‹œ์ ์ด๋“  ๋ฌต์‹œ์ ์ด๋“  ์–ด๋– ํ•œ ์ข…๋ฅ˜์˜ ์ง„์ˆ ์ด๋‚˜ ๋ณด์ฆ์„ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์‚ฌ์ดํŠธ์˜ ์‚ฌ์šฉ๊ณผ ์‚ฌ์ดํŠธ์˜ ์ •๋ณด๋ฅผ ์‹ ๋ขฐํ•˜๋Š” ๊ฒƒ์€ ์ „์ ์œผ๋กœ ์‚ฌ์šฉ์ž์˜ ์ฑ…์ž„์ž…๋‹ˆ๋‹ค.
255
+ # """,
256
+ # """
257
+ # ## ๐Ÿ’ธ Dollar-Cost Averaging Calculator
258
+
259
+ # Try out the Dollar-Cost Averaging (DCA) calculator! This tool helps you calculate the average purchase price of stocks when making additional investments. ๐Ÿ“ˆ
260
+
261
+ # ### Input Values ๐Ÿ“Š
262
+
263
+ # Please enter the following details:
264
+
265
+ # - **Old Average Price (old_avg_price)**: The average price of the existing stocks ๐Ÿ’ต
266
+ # - **Old Quantity (old_quantity)**: The quantity of the existing stocks ๐Ÿ“ฆ
267
+ # - **New Price (new_price)**: The price of the newly purchased stocks ๐Ÿ’ฒ
268
+ # - **New Quantity (new_quantity)**: The quantity of the newly purchased stocks ๐Ÿ“ˆ
269
+
270
+ # ### Calculation Process ๐Ÿ”ข
271
+
272
+ # 1. **Current Investment**: Calculate the current investment amount by multiplying the old average price by the old quantity.
273
+ # 2. **Additional Investment**: Calculate the additional investment amount by multiplying the new price by the new quantity.
274
+ # 3. **Total Investment and Total Quantity**: Add the current investment amount and additional investment amount to get the total investment. Sum the old quantity and new quantity to get the total quantity of stocks.
275
+ # 4. **New Average Price**: Calculate the new average price by dividing the total investment by the total quantity of stocks.
276
+ # 5. **Return Calculation**: Calculate the old and new returns to analyze the investment results.
277
+
278
+ # ### Results ๐ŸŽฏ
279
+
280
+ # **1) Old Return**
281
+
282
+ # Displays the return based on the old average price.
283
+
284
+ # **2) New Return**
285
+
286
+ # Displays the return based on the new average price.
287
+
288
+ # **3) Additional Investment**
289
+
290
+ # Shows the amount of additional investment made.
291
+
292
+ # **4) Average Price**
293
+
294
+ # Displays the newly calculated average price.
295
+
296
+ # **5) Total Quantity**
297
+
298
+ # Shows the total quantity of stocks.
299
+
300
+ # **6) Total Investment**
301
+
302
+ # Displays the total investment amount.
303
+
304
+ # ### Example ๐Ÿ“‰
305
+
306
+ # - **Old Average Price**: 100
307
+ # - **Old Quantity**: 10
308
+ # - **New Price**: 120
309
+ # - **New Quantity**: 5
310
+
311
+ # Results:
312
+ # - New Average Price: 105
313
+ # - Total Quantity: 15
314
+ # - Total Investment: 1,575
315
+ # - Old Return: 20.00%
316
+ # - New Return: 14.29%
317
+ # - Additional Investment: 600
318
+
319
+ # Use the Dollar-Cost Averaging Calculator to easily compute the average purchase price of your investments! ๐Ÿ“Š๐Ÿ’ฐ
320
+
321
+ # ### Disclaimer
322
+ # The information provided by this tool is for general informational purposes only. All information on the site is provided in good faith, but we make no representation or warranty of any kind, express or implied, regarding the accuracy, adequacy, validity, reliability, availability, or completeness of any information on the site. Your use of the site and reliance on any information on the site is solely at your own risk.
323
+ # """
324
+ # )
325
+
326
+ # # Retirement Planning Tool Section
327
+ # create_tool_section(
328
+ # "RETIREMENT PLANNING CALCULATOR",
329
+ # """
330
+ # ## ๐Ÿ–๏ธ ์€ํ‡ด ๊ณ„ํš ๊ณ„์‚ฐ๊ธฐ ์‚ฌ์šฉ๋ฒ•
331
+
332
+ # ์•ˆ๋…•ํ•˜์„ธ์š”! ๐Ÿ˜Š ์€ํ‡ด ๊ณ„ํš ๊ณ„์‚ฐ๊ธฐ์— ์˜ค์‹  ๊ฒƒ์„ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋„๊ตฌ๋Š” ์—ฌ๋Ÿฌ๋ถ„์˜ ์€ํ‡ด ํ›„ ์žฌ์ • ์ƒํ™ฉ์„ ์˜ˆ์ธกํ•˜๊ณ  ์ค€๋น„ํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ค„ ๊ฑฐ์˜ˆ์š”. ์•„๋ž˜๋Š” ์ด ๊ณ„์‚ฐ๊ธฐ์˜ ์ž‘๋™ ๋ฐฉ์‹๊ณผ ํ•„์š”ํ•œ ์ •๋ณด์ž…๋‹ˆ๋‹ค.
333
+
334
+ # ### 1. ์ž…๋ ฅ ๊ฐ’ ๐Ÿ“Š
335
+
336
+ # ๊ณ„์‚ฐ๊ธฐ์—์„œ ๋‹ค์Œ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”:
337
+
338
+ # - **ํ˜„์žฌ ๋‚˜์ด (current_age)**: ํ˜„์žฌ ๋‚˜์ด ๐ŸŽ‚
339
+ # - **์€ํ‡ด ๋‚˜์ด (retirement_age)**: ์€ํ‡ดํ•˜๊ณ  ์‹ถ์€ ๋‚˜์ด ๐ŸŽ‰
340
+ # - **ํ‰์ƒ ๊ธฐ๋Œ€ ์ˆ˜๋ช… (life_expectancy)**: ์˜ˆ์ƒ๋˜๋Š” ํ‰์ƒ ์ˆ˜๋ช… ๐ŸŒŸ
341
+ # - **์›”๋ณ„ ํ•„์š”ํ•œ ์ƒํ™œ๋น„ (monthly_income_required)**: ๋งค์›” ํ•„์š”ํ•œ ์ƒํ™œ๋น„ ๐Ÿ’ต
342
+ # - **์ธํ”Œ๋ ˆ์ด์…˜์œจ (inflation_rate)**: ๋ฌผ๊ฐ€ ์ƒ์Šน๋ฅ  ๐Ÿ“ˆ
343
+ # - **ํ˜„์žฌ ํˆฌ์ž์•ก (current_investment)**: ํ˜„์žฌ ๋ณด์œ ํ•œ ํˆฌ์ž์•ก ๐Ÿ’ฐ
344
+ # - **์›”๋ณ„ ์ถ”๊ฐ€ ํˆฌ์ž์•ก (monthly_investment)**: ๋งค์›” ์ถ”๊ฐ€๋กœ ํˆฌ์žํ•  ๊ธˆ์•ก ๐Ÿ’ธ
345
+ # - **์›” ํˆฌ์ž์•ก์˜ ์—ฐ๊ฐ„ ์ฆ๊ฐ€์•ก (annual_increase_in_monthly_investment)**: ๋งค์›” ์ฆ๊ฐ€ํ•  ํˆฌ์ž์•ก(๋งค๋…„ ์›” ์ ๋ฆฝ์‹ ํˆฌ์ž๊ธˆ์—์„œ ๋Š˜๋ ค๋‚˜๊ฐˆ ๊ธˆ์•ก. (ex: ๋งค๋…„ 10๋งŒ์›์”ฉ ๋Š˜๋ฆด ์˜ˆ์ •์ด๋ผ๋ฉด, 100,000 ์ž…๋ ฅ)) ๐Ÿ“ˆ
346
+ # - **๋ฐฐ๋‹น๊ธˆ ์žฌํˆฌ์ž ์—ฌ๋ถ€ (reinvest_dividends)**: ๋ฐฐ๋‹น๊ธˆ์„ ์žฌํˆฌ์žํ• ์ง€ ์—ฌ๋ถ€ ๐Ÿ”„
347
+ # - **์€ํ‡ด ์ „ ์ˆ˜์ต๋ฅ  (pre_retirement_roi)**: ์€ํ‡ด ์ „ ์˜ˆ์ƒ ์—ฐ ์ˆ˜์ต๋ฅ  ๐Ÿ“Š
348
+ # - **์€ํ‡ด ์ „ ๋ฐฐ๋‹น ์ˆ˜์ต๋ฅ  (pre_retirement_dividend_yield)**: ์€ํ‡ด ์ „ ๋ฐฐ๋‹น๊ธˆ ์ˆ˜์ต๋ฅ  ๐Ÿ“ˆ
349
+ # - **์€ํ‡ด ํ›„ ์ˆ˜์ต๋ฅ  (post_retirement_roi)**: ์€ํ‡ด ํ›„ ์˜ˆ์ƒ ์—ฐ ์ˆ˜์ต๋ฅ  ๐ŸŒฟ
350
+ # - **์€ํ‡ด ํ›„ ๋ฐฐ๋‹น ์ˆ˜์ต๋ฅ  (post_retirement_dividend_yield)**: ์€ํ‡ด ํ›„ ๋ฐฐ๋‹น๊ธˆ ์ˆ˜์ต๋ฅ  ๐Ÿ’ต
351
+
352
+ # ### 2. ๊ณ„์‚ฐ ๊ณผ์ • ๐Ÿ”ข
353
+
354
+ # **1) ์€ํ‡ด ์ „ ์ž์‚ฐ ๊ณ„์‚ฐ**
355
+
356
+ # ํ˜„์žฌ ์ž์‚ฐ๊ณผ ๋งค์›” ์ถ”๊ฐ€ ํˆฌ์ž์•ก์„ ๊ณ ๋ คํ•ด ์€ํ‡ด ์ „ ์ž์‚ฐ์„ ๊ณ„์‚ฐํ•ด์š”. ์›”๋ณ„ ์ˆ˜์ต๋ฅ ์„ ์ ์šฉํ•˜์—ฌ ์ž์‚ฐ์ด ์–ด๋–ป๊ฒŒ ์„ฑ์žฅํ• ์ง€ ์˜ˆ์ธกํ•ฉ๋‹ˆ๋‹ค.
357
+
358
+ # **๊ณต์‹:**
359
+ # \[ \text{์›”๊ฐ„ ์ˆ˜์ต๋ฅ } = \left(1 + \frac{\text{์—ฐ ์ˆ˜์ต๋ฅ }}{100}\right)^{\frac{1}{12}} - 1 \]
360
+
361
+ # **2) ์€ํ‡ด ํ›„ ์ž์‚ฐ ๋ฐ ๋ฐฐ๋‹น ์ˆ˜์ต ๊ณ„์‚ฐ**
362
+
363
+ # ์€ํ‡ด ์‹œ์ ์˜ ์ž์‚ฐ๊ณผ ๋ฐฐ๋‹น ์ˆ˜์ต์„ ๊ธฐ๋กํ•˜๊ณ , ์€ํ‡ด ํ›„ ๋งค๋…„ ์ž์‚ฐ๊ณผ ๋ฐฐ๋‹น ์ˆ˜์ต์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. ๋งค๋…„ ํ•„์š”ํ•œ ์ƒํ™œ๋น„๋„ ๋ฌผ๊ฐ€ ์ƒ์Šน๋ฅ ์— ๋งž์ถฐ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
364
+
365
+ # **3) ์ถ”๊ฐ€ ํ˜„๊ธˆ ํ•„์š”๋Ÿ‰ ๊ณ„์‚ฐ**
366
+
367
+ # ๋ฐฐ๋‹น ์ˆ˜์ต๊ณผ ํ•„์š”ํ•œ ์ƒํ™œ๋น„๋ฅผ ๋น„๊ตํ•˜์—ฌ ์ถ”๊ฐ€๋กœ ํ•„์š”ํ•œ ํ˜„๊ธˆ์„ ๊ณ„์‚ฐํ•ด์š”.
368
+
369
+ # **๊ณต์‹:**
370
+ # \[ \text{์—ฐ๊ฐ„ ํ•„์š”ํ•œ ์ƒํ™œ๋น„} = \text{์›”๋ณ„ ํ•„์š”ํ•œ ์ƒํ™œ๋น„} \times 12 \]
371
+ # \[ \text{์ถ”๊ฐ€ ํ˜„๊ธˆ ํ•„์š”๋Ÿ‰} = \text{์—ฐ๊ฐ„ ํ•„์š”ํ•œ ์ƒํ™œ๋น„} - \text{์—ฐ๊ฐ„ ๋ฐฐ๋‹น ์ˆ˜์ต} \]
372
+
373
+ # ### 3. ๊ฒฐ๊ณผ ์ถœ๋ ฅ ๐Ÿ“ˆ
374
+
375
+ # ๊ณ„์‚ฐ๋œ ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•์‹์œผ๋กœ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค:
376
+
377
+ # - **์€ํ‡ด ์งํ›„ ํ•„์š”ํ•œ ์ƒํ™œ๋น„**์™€ **๋ฐฐ๋‹น ์ˆ˜์ต**์„ ํ•œ๋ˆˆ์— ๋ณผ ์ˆ˜ ์žˆ์–ด์š”.
378
+ # - ์€ํ‡ด ํ›„ ๋งค๋…„ ์ž์‚ฐ, ํ•„์š”ํ•œ ์ƒํ™œ๋น„, ๋ฐฐ๋‹น ์ˆ˜์ต ๋“ฑ์„ ํ‘œ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
379
+ # - ์ถ”๊ฐ€๋กœ ํ•„์š”ํ•œ ํ˜„๊ธˆ์„ ๊ณ„์‚ฐํ•˜์—ฌ ์ด ํ•„์š” ํ˜„๊ธˆ์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.
380
+
381
+ # ### ์˜ˆ์ œ ๐ŸŽ
382
+
383
+ # **์ž…๋ ฅ ๊ฐ’ ์˜ˆ์‹œ:**
384
+
385
+ # - ํ˜„์žฌ ๋‚˜์ด: 40์„ธ
386
+ # - ์€ํ‡ด ๋‚˜์ด: 65์„ธ
387
+ # - ํ‰์ƒ ๊ธฐ๋Œ€ ์ˆ˜๋ช…: 85์„ธ
388
+ # - ์›”๋ณ„ ํ•„์š”ํ•œ ์ƒํ™œ๋น„: 2,000,000 ์›
389
+ # - ์ธํ”Œ๋ ˆ์ด์…˜์œจ: 2%
390
+ # - ํ˜„์žฌ ํˆฌ์ž์•ก: 50,000,000 ์›
391
+ # - ์›”๋ณ„ ์ถ”๊ฐ€ ํˆฌ์ž์•ก: 500,000 ์›
392
+ # - ์›”๋ณ„ ํˆฌ์ž ์ฆ๊ฐ€์•ก: 50,000 ์›
393
+ # - ๋ฐฐ๋‹น๊ธˆ ์žฌํˆฌ์ž ์—ฌ๋ถ€: ์˜ˆ
394
+ # - ์€ํ‡ด ์ „ ์ˆ˜์ต๋ฅ : 5%
395
+ # - ์€ํ‡ด ์ „ ๋ฐฐ๋‹น ์ˆ˜์ต๋ฅ : 2%
396
+ # - ์€ํ‡ด ํ›„ ์ˆ˜์ต๋ฅ : 4%
397
+ # - ์€ํ‡ด ํ›„ ๋ฐฐ๋‹น ์ˆ˜์ต๋ฅ : 2%
398
+
399
+ # **์ถœ๋ ฅ ๊ฒฐ๊ณผ:**
400
+
401
+ # - ์€ํ‡ด ์งํ›„ ์›”๋ณ„ ํ•„์š”ํ•œ ์ƒํ™œ๋น„: 2,000,000 ์›
402
+ # - ์€ํ‡ด ์งํ›„ ์›”๋ณ„ ๋ฐฐ๋‹น ์ˆ˜์ต: 500,000 ์›
403
+ # - ์ด ์ถ”๊ฐ€ ํ˜„๊ธˆ ํ•„์š”๋Ÿ‰: ๋งค๋…„ ์ถ”๊ฐ€๋กœ ํ•„์š”ํ•œ ๊ธˆ์•ก
404
+
405
+ # **๊ทธ๋ž˜ํ”„์™€ CSV ํŒŒ์ผ ๐Ÿ“‰:**
406
+
407
+ # - ์€ํ‡ด ๊ณ„ํš์„ ์‹œ๊ฐ์ ์œผ๋กœ ๋ณด์—ฌ์ฃผ๋Š” ๊ทธ๋ž˜ํ”„์™€ ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ํฌํ•จํ•œ CSV ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ๋งํฌ๋„ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.
408
+
409
+ # ์€ํ‡ด ๊ณ„ํš ๊ณ„์‚ฐ๊ธฐ๋ฅผ ํ†ตํ•ด ์—ฌ๋Ÿฌ๋ถ„์˜ ์žฌ์ • ๊ณ„ํš์„ ๋ฏธ๋ฆฌ ์ค€๋น„ํ•˜๊ณ , ์•ˆ์ •์ ์ธ ๋ฏธ๋ž˜๋ฅผ ๊ณ„ํšํ•ด๋ณด์„ธ์š”! ๐ŸŒŸ๐Ÿ’ผ
410
+
411
+
412
+ # ### ๋ฉด์ฑ… ์กฐํ•ญ
413
+ # ์ด ๋„๊ตฌ์—์„œ ์ œ๊ณตํ•˜๋Š” ์ •๋ณด๋Š” ์ผ๋ฐ˜์ ์ธ ์ •๋ณด ์ œ๊ณต ๋ชฉ์ ๋งŒ์„ ์œ„ํ•ด ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. ์‚ฌ์ดํŠธ์˜ ๋ชจ๋“  ์ •๋ณด๋Š” ์„ ์˜๋กœ ์ œ๊ณต๋˜์ง€๋งŒ, ์‚ฌ์ดํŠธ์˜ ์ •๋ณด์˜ ์ •ํ™•์„ฑ, ์ ์ ˆ์„ฑ, ์œ ํšจ์„ฑ, ์‹ ๋ขฐ์„ฑ, ๊ฐ€์šฉ์„ฑ ๋˜๋Š” ์™„์ „์„ฑ์— ๋Œ€ํ•ด ๋ช…์‹œ์ ์ด๋“  ๋ฌต์‹œ์ ์ด๋“  ์–ด๋– ํ•œ ์ข…๋ฅ˜์˜ ์ง„์ˆ ์ด๋‚˜ ๋ณด์ฆ์„ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์‚ฌ์ดํŠธ์˜ ์‚ฌ์šฉ๊ณผ ์‚ฌ์ดํŠธ์˜ ์ •๋ณด๋ฅผ ์‹ ๋ขฐํ•˜๋Š” ๊ฒƒ์€ ์ „์ ์œผ๋กœ ์‚ฌ์šฉ์ž์˜ ์ฑ…์ž„์ž…๋‹ˆ๋‹ค.
414
+ # """,
415
+ # """
416
+ # ## ๐Ÿ–๏ธ Retirement Planning Calculator Guide
417
+
418
+ # Welcome to the Retirement Planning Calculator! ๐Ÿ˜Š This tool helps you forecast and prepare for your financial situation after retirement. Hereโ€™s how it works and what information you need to provide:
419
+
420
+ # ### 1. Input Values ๐Ÿ“Š
421
+
422
+ # Please provide the following details:
423
+
424
+ # - **Current Age (current_age)**: Your current age ๐ŸŽ‚
425
+ # - **Retirement Age (retirement_age)**: Age at which you plan to retire ๐ŸŽ‰
426
+ # - **Life Expectancy (life_expectancy)**: Expected lifespan ๐ŸŒŸ
427
+ # - **Monthly Income Required (monthly_income_required)**: Monthly expenses needed ๐Ÿ’ต
428
+ # - **Inflation Rate (inflation_rate)**: Rate of inflation ๐Ÿ“ˆ
429
+ # - **Current Investment (current_investment)**: Amount currently invested ๐Ÿ’ฐ
430
+ # - **Monthly Investment (monthly_investment)**: Additional monthly investment ๐Ÿ’ธ
431
+ # - **Annual Increase in Monthly Investment (annual_increase_in_monthly_investment)**: Increase in monthly investment (For example, if you plan to increase your monthly investment by $100,000 each year, enter 100,000.) ๐Ÿ“ˆ
432
+ # - **Reinvest Dividends (reinvest_dividends)**: Whether to reinvest dividends ๐Ÿ”„
433
+ # - **Pre-Retirement ROI (pre_retirement_roi)**: Expected annual return before retirement ๐Ÿ“Š
434
+ # - **Pre-Retirement Dividend Yield (pre_retirement_dividend_yield)**: Dividend yield before retirement ๐Ÿ“ˆ
435
+ # - **Post-Retirement ROI (post_retirement_roi)**: Expected annual return after retirement ๐ŸŒฟ
436
+ # - **Post-Retirement Dividend Yield (post_retirement_dividend_yield)**: Dividend yield after retirement ๐Ÿ’ต
437
+
438
+ # ### 2. Calculation Process ๐Ÿ”ข
439
+
440
+ # **1) Pre-Retirement Asset Calculation**
441
+
442
+ # The calculator uses your current assets and monthly contributions to forecast your asset growth before retirement. Monthly returns are applied to estimate how your assets will grow.
443
+
444
+ # **Formula:**
445
+ # \[ \text{Monthly Return} = \left(1 + \frac{\text{Annual ROI}}{100}\right)^{\frac{1}{12}} - 1 \]
446
+
447
+ # **2) Post-Retirement Asset and Dividend Income Calculation**
448
+
449
+ # The calculator tracks your assets and dividend income at retirement, then updates these figures annually. It also adjusts monthly income needs for inflation.
450
+
451
+ # **3) Additional Cash Needed Calculation**
452
+
453
+ # It compares dividend income with required income to determine if additional cash is needed.
454
+
455
+ # **Formula:**
456
+ # \[ \text{Annual Income Required} = \text{Monthly Income Required} \times 12 \]
457
+ # \[ \text{Additional Cash Required} = \text{Annual Income Required} - \text{Annual Dividend Income} \]
458
+
459
+ # ### 3. Results ๐Ÿ“ˆ
460
+
461
+ # The results are displayed as follows:
462
+
463
+ # - **Immediate Post-Retirement Income Needs** and **Dividend Income** for easy comparison.
464
+ # - Annual asset, required income, and dividend income details in a table.
465
+ # - Total additional cash required to cover any shortfall.
466
+
467
+ # ### Example ๐ŸŽ
468
+
469
+ # **Example Input Values:**
470
+
471
+ # - Current Age: 40 years
472
+ # - Retirement Age: 65 years
473
+ # - Life Expectancy: 85 years
474
+ # - Monthly Income Required: $2,000
475
+ # - Inflation Rate: 2%
476
+ # - Current Investment: $50,000
477
+ # - Monthly Investment: $500
478
+ # - Monthly Increase in Investment: $50
479
+ # - Reinvest Dividends: Yes
480
+ # - Pre-Retirement ROI: 5%
481
+ # - Pre-Retirement Dividend Yield: 2%
482
+ # - Post-Retirement ROI: 4%
483
+ # - Post-Retirement Dividend Yield: 2%
484
+
485
+ # **Output Results:**
486
+
487
+ # - Monthly Income Required Immediately After Retirement: $2,000
488
+ # - Monthly Dividend Income Immediately After Retirement: $500
489
+ # - Total Additional Cash Required: Amount needed annually
490
+
491
+ # **Graph and CSV File ๐Ÿ“‰:**
492
+
493
+ # - Visual graphs of the retirement plan and a CSV file with detailed calculations are available for download.
494
+
495
+ # Use the Retirement Planning Calculator to prepare for your future and ensure a secure retirement! ๐ŸŒŸ๐Ÿ’ผ
496
+
497
+ # ### Disclaimer
498
+ # The information provided by this tool is for general informational purposes only. All information on the site is provided in good faith, but we make no representation or warranty of any kind, express or implied, regarding the accuracy, adequacy, validity, reliability, availability, or completeness of any information on the site. Your use of the site and reliance on any information on the site is solely at your own risk.
499
+ # """
500
+ # )
501
+ gr.Markdown("""
502
+ <h2 style='margin: 5px'>Support us</h2>
503
+
504
+ If you find this tool useful and would like to support the development of such projects, please consider making a donation. Your support is greatly appreciated.
505
+
506
+ [![Donate with PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=M8SBRC396DPBW)
507
+
508
+ Or, if you prefer, you can also support us through Toss at:
509
+
510
+ <a href="https://toss.me/eichijei" target="_blank">
511
+ <img src="https://static.toss.im/logos/png/1x/logo-toss.png" alt="Donate with Toss" style="width: 150px;">
512
+ </a>
513
+ """)
514
+
interface/dollar_cost_averaging_interface.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from modules.dollar_cost_averaging import dollar_cost_averaging
3
+
4
+ examples = [
5
+ [1000, 9000, 560, 20000]
6
+ ]
7
+ # Define the inputs
8
+ title = gr.Markdown("<h2 style='margin: 5px'>Dollar Cost Averaging Calculator</h2>")
9
+ first_purchase = gr.Markdown("<h3 class='h3_title'>FIRST PURCHASE</h3>")
10
+ old_price = gr.Textbox(label="Old Price", value=7850)
11
+ old_quantity = gr.Textbox(label="Quantity", value=70)
12
+ second_purchase = gr.Markdown("<h3 class='h3_title'>SECOND PURCHASE</h3>")
13
+ new_price = gr.Textbox(label="New Price", value=6700)
14
+ new_quantity = gr.Textbox(label="Quantity", value=70)
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):
22
+ return dollar_cost_averaging(*args)
interface/rebalancing_interface.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from modules.rebalancing import rebalancing_tool
3
+ from modules.utils import currency_codes, current_time
4
+
5
+ examples = [
6
+ ["KRW", "458730 KRW 580 [8],\n368590 KRW 80 [2],", 507977],
7
+ ["KRW", "SCHD USD 500 [8],\nQQQ USD 200 [2],\n458730 KRW 580 [8],\n368590 KRW 80 [2],", 10000000]
8
+ ]
9
+
10
+ # Define Gradio components
11
+ main_currency = gr.Dropdown(
12
+ label="Main Currency",
13
+ choices=currency_codes,
14
+ value="KRW",
15
+ # info="Assets converted to main currency"
16
+ )
17
+ title_setup = gr.Markdown(f"""<h3 class='h3_title'>Portfolio Holdings</h3>""")
18
+ holdings = gr.Textbox(
19
+ label="Portfolio [Ratio]",
20
+ lines=1,
21
+ # info="Format: Ticker Currency Quantity [Target_Weight], ...",
22
+ placeholder="Format: Ticker Currency Quantity [Target_Weight], ...",
23
+ value="SCHD USD 7 [3], 458730 KRW 580 [4], 368590 KRW 80 [1],"
24
+ )
25
+ title_cash = gr.Markdown("<h3 class='h3_title'>Contribution or withdrawal</h3>")
26
+ cash_amount = gr.Number(
27
+ label="amount",
28
+ value=507977
29
+ )
30
+ allow_sales = gr.Checkbox(
31
+ value=True,
32
+ label="If checked, the table below will display both buy and sell transactions. If unchecked, only buy transactions will be shown."
33
+ )
34
+
35
+ input = [main_currency, holdings, cash_amount, allow_sales]
36
+ output = gr.HTML()
37
+ component_rows = [title_setup, main_currency, holdings, title_cash, cash_amount, allow_sales]
38
+
39
+ # Define the update function
40
+ def update_output(*args):
41
+ return rebalancing_tool(*args)
interface/retirement_planning_interface.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from modules.retirement_planning import retirement_planning
3
+
4
+ # Define examples for retirement planning
5
+ examples = [
6
+ [38, 55, 85, 2000000, 3.0, 10000000, 500000, 0, True, 8, 8, 3.3, 3.3]
7
+ ]
8
+
9
+ # Define the input components
10
+ title = gr.Markdown("<h2 style='margin: 5px'>Retirement Planning</h2>")
11
+ title_profile = gr.Markdown("<h3 class='h3_title'>PROFILE</h3>")
12
+ current_age = gr.Slider(label="Current Age (15-60 Years)", value=28, minimum=15, maximum=60, step=1)
13
+ retirement_age = gr.Slider(label="Retirement Age (Upto 70 Years)", value=55, minimum=15, maximum=70, step=1)
14
+ life_expectancy = gr.Slider(label="Life Expectancy (Upto 100 Years)", value=80, minimum=30, maximum=100, step=1)
15
+ title_savings = gr.Markdown("<h3 class='h3_title'>SAVINGS</h3>")
16
+ monthly_income_required = gr.Number(label="Monthly Income Required (CPP)", value=2000000)
17
+ inflation_rate = gr.Number(label="Inflation Rate (%)", value=3)
18
+ current_investment = gr.Number(label="Current Investment", value=10000000)
19
+ monthly_investment = gr.Number(label="Monthly Investment", value=500000)
20
+ annual_increase_in_monthly_investment = gr.Number(label="Annual Increase in Monthly Investment", value=0) # ์ถ”๊ฐ€๋œ ์ž…๋ ฅ
21
+ reinvest_dividends = gr.Checkbox(label="Reinvest Dividends", value=True)
22
+ title_growth = gr.Markdown("<h3 class='h3_title'>GROWTH</h3>")
23
+ pre_retirement_roi = gr.Number(label="Return On Investment (Pre-retirement) (%)", value=8)
24
+ post_retirement_roi = gr.Number(label="Return On Investment (Post-retirement) (%)", value=8)
25
+ pre_retirement_dividend_yield = gr.Number(label="Dividend Yield (Pre-retirement) (%)", value=3.3)
26
+ post_retirement_dividend_yield = gr.Number(label="Dividend Yield (Post-retirement) (%)", value=3.3)
27
+
28
+ input = [current_age, retirement_age, life_expectancy, monthly_income_required, inflation_rate, current_investment, monthly_investment, annual_increase_in_monthly_investment, reinvest_dividends, pre_retirement_roi, post_retirement_roi, pre_retirement_dividend_yield, post_retirement_dividend_yield]
29
+ output = gr.HTML()
30
+ component_rows = [
31
+ current_age, retirement_age, life_expectancy,[monthly_income_required, inflation_rate],
32
+ current_investment, monthly_investment, annual_increase_in_monthly_investment, [reinvest_dividends],
33
+ [pre_retirement_roi, post_retirement_roi], [pre_retirement_dividend_yield, post_retirement_dividend_yield]
34
+ ]
35
+
36
+ # Define the update function
37
+ def update_output(*args):
38
+ return retirement_planning(*args)
39
+
interface/share_price_trend_interface.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from modules.share_price_trend import share_price_trend
3
+
4
+ examples = [
5
+ ["AAPL,GOOGL,MSFT", 90],
6
+ ["SCHD,QQQ", 365]
7
+ ]
8
+
9
+ # Define Gradio components for inputs
10
+ title = gr.Markdown("<h2 style='margin: 5px'>share price trend</h2>")
11
+ stock_codes = gr.Textbox(
12
+ label="Stock Codes",
13
+ # info="Enter stock codes separated by comma.",
14
+ placeholder="e.g., AAPL,GOOGL,MSFT",
15
+ value="SCHD,QQQ"
16
+ )
17
+ period = gr.Slider(
18
+ label="Number of Days",
19
+ value=90,
20
+ maximum=3650
21
+ )
22
+
23
+ # Define output component
24
+ input = [stock_codes, period]
25
+ output = gr.HTML()
26
+ component_rows = [[stock_codes], [period]]
27
+
28
+ # Define the update function
29
+ def update_output(*args):
30
+ return share_price_trend(*args)
modules/.DS_Store ADDED
Binary file (6.15 kB). View file
 
modules/__init__.py ADDED
File without changes
modules/dollar_cost_averaging.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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")}" />'