cryman38 commited on
Commit
7acc724
·
verified ·
1 Parent(s): 3925489

Upload 14 files

Browse files
app.py CHANGED
@@ -1,7 +1,7 @@
1
  import gradio as gr
2
- from interface.portfolio_rebalancing_interface import examples, portfolio_inputs, output as portfolio_output, update_output as portfolio_update_output
3
- from interface.compare_stock_prices_interface import compare_inputs, output as compare_output, update_output as compare_update_output
4
- from interface.cost_averaging_interface import cost_averaging_inputs, output as cost_averaging_output, update_output as cost_averaging_update_output, markdown1 as cost_averaging_markdown1, old_price, old_quantity, markdown2 as cost_averaging_markdown2, new_price, new_quantity
5
  from interface.retirement_planning_interface import current_age, retirement_age, life_expectancy, monthly_income_required, inflation_rate, current_investment, monthly_investment, pre_retirement_roi, pre_retirement_dividend_yield, reinvest_dividends, markdown as retirement_markdown, post_retirement_roi, post_retirement_dividend_yield, output as retirement_output, update_output as retirement_update_output
6
 
7
  from interface.about_interface import render as render_about_tab
@@ -12,83 +12,83 @@ with gr.Blocks(css='style.css') as demo:
12
  with gr.TabItem("RE-BALANCING"):
13
  with gr.Row(): # Use Row to place inputs and output side by side
14
  with gr.Column(): # Column for inputs
15
- for input_component in portfolio_inputs:
16
  input_component.render()
17
  # Add the ClearButton below the inputs
18
  clear_button = gr.ClearButton(value="Clear")
19
  clear_button.click(
20
- fn=lambda: [None] * len(portfolio_inputs),
21
  inputs=[],
22
- outputs=portfolio_inputs
23
  )
24
 
25
  # Add the Submit Button below the ClearButton
26
  submit_button = gr.Button(value="Run", variant="primary")
27
  submit_button.click(
28
- fn=portfolio_update_output,
29
- inputs=portfolio_inputs,
30
- outputs=portfolio_output
31
  )
32
  with gr.Column(): # Column for output
33
- portfolio_output.render()
34
 
35
  # Add examples
36
  gr.Examples(
37
  examples=examples,
38
  cache_examples=False,
39
- inputs=portfolio_inputs
40
  )
41
- with gr.TabItem("TREND"):
42
  with gr.Row(): # Use Row to place inputs and output side by side
43
  with gr.Column(): # Column for inputs
44
- for input_component in compare_inputs:
45
  input_component.render()
46
  # Add the ClearButton below the inputs
47
  clear_button = gr.ClearButton(value="Clear")
48
  clear_button.click(
49
- fn=lambda: [None] * len(compare_inputs),
50
  inputs=[],
51
- outputs=compare_inputs
52
  )
53
 
54
  # Add the Submit Button below the ClearButton
55
  submit_button = gr.Button(value="Run", variant="primary")
56
  submit_button.click(
57
- fn=compare_update_output,
58
- inputs=compare_inputs,
59
- outputs=compare_output
60
  )
61
  with gr.Column(): # Column for output
62
- compare_output.render()
63
 
64
- with gr.TabItem("DCA"):
65
  with gr.Row(): # Use Row to place inputs and output side by side
66
  with gr.Column(): # Column for inputs
67
- cost_averaging_markdown1.render()
68
  old_price.render()
69
  old_quantity.render()
70
- cost_averaging_markdown2.render()
71
  new_price.render()
72
  new_quantity.render()
73
  # Add the ClearButton below the inputs
74
  clear_button = gr.ClearButton(value="Clear")
75
  clear_button.click(
76
- fn=lambda: [None] * len(cost_averaging_inputs),
77
  inputs=[],
78
- outputs=cost_averaging_inputs
79
  )
80
  with gr.Column(): # Column for output
81
- cost_averaging_output.render()
82
 
83
  # Add change event handlers for cost averaging inputs
84
- for input_component in cost_averaging_inputs:
85
  input_component.change(
86
- fn=cost_averaging_update_output,
87
- inputs=cost_averaging_inputs,
88
- outputs=cost_averaging_output
89
  )
90
 
91
- with gr.TabItem("RETIREMENT"):
92
  with gr.Row(): # Use Row to place inputs and output side by side
93
  with gr.Column(): # Column for inputs
94
  current_age.render()
 
1
  import gradio as gr
2
+ from interface.rebalancing_interface import examples, rebalancing_inputs, output as rebalancing_output, update_output as rebalancing_update_output
3
+ from interface.share_price_trend_interface import share_price_trend_inputs, output as share_price_trend_output, update_output as share_price_trend_update_output
4
+ from interface.dollar_cost_averaging_interface import dollar_cost_averaging_inputs, output as dollar_cost_averaging_output, update_output as dollar_cost_averaging_update_output, markdown1 as dollar_cost_averaging_markdown1, old_price, old_quantity, markdown2 as dollar_cost_averaging_markdown2, new_price, new_quantity
5
  from interface.retirement_planning_interface import current_age, retirement_age, life_expectancy, monthly_income_required, inflation_rate, current_investment, monthly_investment, pre_retirement_roi, pre_retirement_dividend_yield, reinvest_dividends, markdown as retirement_markdown, post_retirement_roi, post_retirement_dividend_yield, output as retirement_output, update_output as retirement_update_output
6
 
7
  from interface.about_interface import render as render_about_tab
 
12
  with gr.TabItem("RE-BALANCING"):
13
  with gr.Row(): # Use Row to place inputs and output side by side
14
  with gr.Column(): # Column for inputs
15
+ for input_component in rebalancing_inputs:
16
  input_component.render()
17
  # Add the ClearButton below the inputs
18
  clear_button = gr.ClearButton(value="Clear")
19
  clear_button.click(
20
+ fn=lambda: [None] * len(rebalancing_inputs),
21
  inputs=[],
22
+ outputs=rebalancing_inputs
23
  )
24
 
25
  # Add the Submit Button below the ClearButton
26
  submit_button = gr.Button(value="Run", variant="primary")
27
  submit_button.click(
28
+ fn=rebalancing_update_output,
29
+ inputs=rebalancing_inputs,
30
+ outputs=rebalancing_output
31
  )
32
  with gr.Column(): # Column for output
33
+ rebalancing_output.render()
34
 
35
  # Add examples
36
  gr.Examples(
37
  examples=examples,
38
  cache_examples=False,
39
+ inputs=rebalancing_inputs
40
  )
41
+ with gr.TabItem("SHARE PRICE TREND"):
42
  with gr.Row(): # Use Row to place inputs and output side by side
43
  with gr.Column(): # Column for inputs
44
+ for input_component in share_price_trend_inputs:
45
  input_component.render()
46
  # Add the ClearButton below the inputs
47
  clear_button = gr.ClearButton(value="Clear")
48
  clear_button.click(
49
+ fn=lambda: [None] * len(share_price_trend_inputs),
50
  inputs=[],
51
+ outputs=share_price_trend_inputs
52
  )
53
 
54
  # Add the Submit Button below the ClearButton
55
  submit_button = gr.Button(value="Run", variant="primary")
56
  submit_button.click(
57
+ fn=share_price_trend_update_output,
58
+ inputs=share_price_trend_inputs,
59
+ outputs=share_price_trend_output
60
  )
61
  with gr.Column(): # Column for output
62
+ share_price_trend_output.render()
63
 
64
+ with gr.TabItem("DOLLAR-COST AVERAGING"):
65
  with gr.Row(): # Use Row to place inputs and output side by side
66
  with gr.Column(): # Column for inputs
67
+ dollar_cost_averaging_markdown1.render()
68
  old_price.render()
69
  old_quantity.render()
70
+ dollar_cost_averaging_markdown2.render()
71
  new_price.render()
72
  new_quantity.render()
73
  # Add the ClearButton below the inputs
74
  clear_button = gr.ClearButton(value="Clear")
75
  clear_button.click(
76
+ fn=lambda: [None] * len(dollar_cost_averaging_inputs),
77
  inputs=[],
78
+ outputs=dollar_cost_averaging_inputs
79
  )
80
  with gr.Column(): # Column for output
81
+ dollar_cost_averaging_output.render()
82
 
83
  # Add change event handlers for cost averaging inputs
84
+ for input_component in dollar_cost_averaging_inputs:
85
  input_component.change(
86
+ fn=dollar_cost_averaging_update_output,
87
+ inputs=dollar_cost_averaging_inputs,
88
+ outputs=dollar_cost_averaging_output
89
  )
90
 
91
+ with gr.TabItem("RETIREMENT PLANNING"):
92
  with gr.Row(): # Use Row to place inputs and output side by side
93
  with gr.Column(): # Column for inputs
94
  current_age.render()
interface/about_interface.py CHANGED
@@ -1,8 +1,7 @@
1
  import gradio as gr
2
 
3
  def render():
4
- with gr.TabItem("💡 HOW TO USE"):
5
- gr.Markdown("## HOW TO USE:")
6
 
7
  def create_tool_section(tool_name, korean_description, english_description):
8
  with gr.Accordion(tool_name, open=False):
@@ -13,7 +12,7 @@ def render():
13
 
14
  # Portfolio Rebalancing Tool Section
15
  create_tool_section(
16
- "Portfolio",
17
  """
18
  # 포트폴리오 리밸런싱 도구
19
 
@@ -46,7 +45,7 @@ def render():
46
  이 도구에서 제공하는 정보는 일반적인 정보 제공 목적만을 위해 제공됩니다. 사이트의 모든 정보는 선의로 제공되지만, 사이트의 정보의 정확성, 적절성, 유효성, 신뢰성, 가용성 또는 완전성에 대해 명시적이든 묵시적이든 어떠한 종류의 진술이나 보증을 하지 않습니다. 사이트의 사용과 사이트의 정보를 신뢰하는 것은 전적으로 사용자의 책임입니다.
47
  """,
48
  """
49
- # Portfolio Rebalancing Tool
50
 
51
  This tool helps investors rebalance their portfolios by analyzing their current holdings and suggesting buy/sell actions to achieve target allocations. It provides detailed information on how to adjust the portfolio, considering various stock codes, currencies, and desired target ratios.
52
 
@@ -80,9 +79,9 @@ def render():
80
 
81
  # Stock Comparison Tool Section
82
  create_tool_section(
83
- "Compare",
84
  """
85
- # 주식 그래프 비교 도구
86
 
87
  이 도구는 사용자가 지정한 기간 동안 여러 회사의 상대 주가를 비교할 수 있도록 합니다. 역사적인 주가를 가져와 상대 성능을 보여주도록 정상화하고 비교 그래프를 생성합니다.
88
 
@@ -113,7 +112,7 @@ def render():
113
  이 도구에서 제공하는 정보는 일반적인 정보 제공 목적만을 위해 제공됩니다. 사이트의 모든 정보는 선의로 제공되지만, 사이트의 정보의 정확성, 적절성, 유효성, 신뢰성, 가용성 또는 완전성에 대해 명시적이든 묵시적이든 어떠한 종류의 진술이나 보증을 하지 않습니다. 사이트의 사용과 사이트의 정보를 신뢰하는 것은 전적으로 사용자의 책임입니다.
114
  """,
115
  """
116
- # Stock Comparison Tool
117
 
118
  This tool allows users to compare the relative prices of multiple companies over a specified period. It fetches historical stock prices, normalizes them to show relative performance, and generates a comparison graph.
119
 
@@ -147,7 +146,7 @@ def render():
147
 
148
  # Cost Averaging Tool Section
149
  create_tool_section(
150
- "Cost Averaging",
151
  """
152
  # 물타기 계산 도구
153
 
@@ -181,7 +180,7 @@ def render():
181
  이 도구에서 제공하는 정보는 일반적인 정보 제공 목적만을 위해 제공됩니다. 사이트의 모든 정보는 선의로 제공되지만, 사이트의 정보의 정확성, 적절성, 유효성, 신뢰성, 가용성 또는 완전성에 대해 명시적이든 묵시적이든 어떠한 종류의 진술이나 보증을 하지 않습니다. 사이트의 사용과 사이트의 정보를 신뢰하는 것은 전적으로 사용자의 책임입니다.
182
  """,
183
  """
184
- # Cost Averaging Calculator
185
 
186
  This tool helps investors calculate the new average purchase price and yield after purchasing additional shares at different prices. It is useful for understanding the impact of new investments on the overall cost basis and potential returns.
187
 
@@ -216,7 +215,7 @@ def render():
216
 
217
  # Retirement Planning Tool Section
218
  create_tool_section(
219
- "Retirement Planning",
220
  """
221
  # 은퇴 계획 도구
222
 
@@ -261,7 +260,7 @@ def render():
261
  이 도구에서 제공하는 정보는 일반적인 정보 제공 목적만을 위해 제공됩니다. 사이트의 모든 정보는 선의로 제공되지만, 사이트의 정보의 정확성, 적절성, 유효성, 신뢰성, 가용성 또는 완전성에 대해 명시적이든 묵시적이든 어떠한 종류의 진술이나 보증을 하지 않습니다. 사이트의 사용과 사이트의 정보를 신뢰하는 것은 전적으로 사용자의 책임입니다.
262
  """,
263
  """
264
- # Retirement Planning Tool
265
 
266
  This tool helps individuals plan for retirement by predicting investment growth and dividend income up to the retirement date and beyond. It considers various factors such as initial investment, monthly contributions, ROI before and after retirement, and dividend yield.
267
 
@@ -304,18 +303,17 @@ def render():
304
  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.
305
  """
306
  )
 
 
 
 
307
 
308
- # Support Us Section
309
- gr.Markdown("""
310
- ## Support Us
311
- 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.
312
 
313
- [![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)
314
 
315
- Or, if you prefer, you can also support us through Toss at:
316
-
317
- <a href="https://toss.me/eichijei" target="_blank">
318
- <img src="https://static.toss.im/logos/png/1x/logo-toss.png" alt="Donate with Toss" style="width: 150px;">
319
- </a>
320
- """)
321
 
 
1
  import gradio as gr
2
 
3
  def render():
4
+ with gr.TabItem("💡 HINT"):
 
5
 
6
  def create_tool_section(tool_name, korean_description, english_description):
7
  with gr.Accordion(tool_name, open=False):
 
12
 
13
  # Portfolio Rebalancing Tool Section
14
  create_tool_section(
15
+ "1️⃣ RE-BALANCING CALCULATOR",
16
  """
17
  # 포트폴리오 리밸런싱 도구
18
 
 
45
  이 도구에서 제공하는 정보는 일반적인 정보 제공 목적만을 위해 제공됩니다. 사이트의 모든 정보는 선의로 제공되지만, 사이트의 정보의 정확성, 적절성, 유효성, 신뢰성, 가용성 또는 완전성에 대해 명시적이든 묵시적이든 어떠한 종류의 진술이나 보증을 하지 않습니다. 사이트의 사용과 사이트의 정보를 신뢰하는 것은 전적으로 사용자의 책임입니다.
46
  """,
47
  """
48
+ # RE-BALANCING CALCULATOR
49
 
50
  This tool helps investors rebalance their portfolios by analyzing their current holdings and suggesting buy/sell actions to achieve target allocations. It provides detailed information on how to adjust the portfolio, considering various stock codes, currencies, and desired target ratios.
51
 
 
79
 
80
  # Stock Comparison Tool Section
81
  create_tool_section(
82
+ "2️⃣ SHARE PRICE TREND CALCULATOR",
83
  """
84
+ # 주가 동향 비교
85
 
86
  이 도구는 사용자가 지정한 기간 동안 여러 회사의 상대 주가를 비교할 수 있도록 합니다. 역사적인 주가를 가져와 상대 성능을 보여주도록 정상화하고 비교 그래프를 생성합니다.
87
 
 
112
  이 도구에서 제공하는 정보는 일반적인 정보 제공 목적만을 위해 제공됩니다. 사이트의 모든 정보는 선의로 제공되지만, 사이트의 정보의 정확성, 적절성, 유효성, 신뢰성, 가용성 또는 완전성에 대해 명시적이든 묵시적이든 어떠한 종류의 진술이나 보증을 하지 않습니다. 사이트의 사용과 사이트의 정보를 신뢰하는 것은 전적으로 사용자의 책임입니다.
113
  """,
114
  """
115
+ # SHARE PRICE TREND CALCULATOR
116
 
117
  This tool allows users to compare the relative prices of multiple companies over a specified period. It fetches historical stock prices, normalizes them to show relative performance, and generates a comparison graph.
118
 
 
146
 
147
  # Cost Averaging Tool Section
148
  create_tool_section(
149
+ "3️⃣ DOLLAR-COST AVERAGING CALCULATOR",
150
  """
151
  # 물타기 계산 도구
152
 
 
180
  이 도구에서 제공하는 정보는 일반적인 정보 제공 목적만을 위해 제공됩니다. 사이트의 모든 정보는 선의로 제공되지만, 사이트의 정보의 정확성, 적절성, 유효성, 신뢰성, 가용성 또는 완전성에 대해 명시적이든 묵시적이든 어떠한 종류의 진술이나 보증을 하지 않습니다. 사이트의 사용과 사이트의 정보를 신뢰하는 것은 전적으로 사용자의 책임입니다.
181
  """,
182
  """
183
+ # Dollar-Cost Averaging Calculator
184
 
185
  This tool helps investors calculate the new average purchase price and yield after purchasing additional shares at different prices. It is useful for understanding the impact of new investments on the overall cost basis and potential returns.
186
 
 
215
 
216
  # Retirement Planning Tool Section
217
  create_tool_section(
218
+ "4️⃣ RETIREMENT PLANNING CALCULATOR",
219
  """
220
  # 은퇴 계획 도구
221
 
 
260
  이 도구에서 제공하는 정보는 일반적인 정보 제공 목적만을 위해 제공됩니다. 사이트의 모든 정보는 선의로 제공되지만, 사이트의 정보의 정확성, 적절성, 유효성, 신뢰성, 가용성 또는 완전성에 대해 명시적이든 묵시적이든 어떠한 종류의 진술이나 보증을 하지 않습니다. 사이트의 사용과 사이트의 정보를 신뢰하는 것은 전적으로 사용자의 책임입니다.
261
  """,
262
  """
263
+ # RETIREMENT PLANNING CALCULATOR
264
 
265
  This tool helps individuals plan for retirement by predicting investment growth and dividend income up to the retirement date and beyond. It considers various factors such as initial investment, monthly contributions, ROI before and after retirement, and dividend yield.
266
 
 
303
  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.
304
  """
305
  )
306
+ with gr.Accordion("🤝 Support Us", open=False):
307
+ # Support Us Section
308
+ gr.Markdown("""
309
+ 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.
310
 
311
+ [![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)
 
 
 
312
 
313
+ Or, if you prefer, you can also support us through Toss at:
314
 
315
+ <a href="https://toss.me/eichijei" target="_blank">
316
+ <img src="https://static.toss.im/logos/png/1x/logo-toss.png" alt="Donate with Toss" style="width: 150px;">
317
+ </a>
318
+ """)
 
 
319
 
interface/dollar_cost_averaging_interface.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from modules.dollar_cost_averaging import gradio_dollar_cost_averaging, load_css
3
+
4
+ def dollar_cost_averaging_interface_fn(old_avg_price, old_quantity, new_price, new_quantity):
5
+ result = gradio_dollar_cost_averaging(old_avg_price, old_quantity, new_price, new_quantity)
6
+ css = load_css()
7
+ return css + result
8
+
9
+ # Define the inputs
10
+ markdown1 = gr.Markdown("### First Purchase")
11
+ old_price = gr.Textbox(label="Old Price", value="")
12
+ old_quantity = gr.Textbox(label="Quantity", value="")
13
+ markdown2 = gr.Markdown("### Second Purchase")
14
+ new_price = gr.Textbox(label="New Price", value="")
15
+ new_quantity = gr.Textbox(label="Quantity", value="")
16
+
17
+ dollar_cost_averaging_inputs = [old_price, old_quantity, new_price, new_quantity]
18
+ output = gr.HTML()
19
+
20
+ # Define the update function
21
+ def update_output(old_avg_price, old_quantity, new_price, new_quantity):
22
+ return dollar_cost_averaging_interface_fn(old_avg_price, old_quantity, new_price, new_quantity)
interface/rebalancing_interface.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from modules.rebalancing import rebalancing_tool, load_css
3
+ from modules.utils import get_currency_codes
4
+
5
+ def rebalancing_interface_fn(main_currency, holdings, cash_amount, cash_ratio):
6
+ result = rebalancing_tool(main_currency, holdings, cash_amount, cash_ratio)
7
+ css = load_css()
8
+ return css + result
9
+
10
+ currency_codes = get_currency_codes()
11
+ examples = [
12
+ ["KRW", "458730 KRW 580 8,\n368590 KRW 80 2", 507977, 0],
13
+ ["KRW", "SCHD USD 500 8,\nQQQ USD 200 2", 0, 15]
14
+ ]
15
+
16
+ rebalancing_inputs = [
17
+ gr.Dropdown(label="Main Currency", choices=currency_codes, value="KRW", info="Assets converted to main currency."),
18
+ gr.Textbox(label="Holdings", lines=2, info="Format: [ ticker currency quantity weight, ... ]", placeholder="e.g., SCHD USD 500 8, QQQ USD 200 2"),
19
+ gr.Number(label="Cash", value="", info="MAIN CURRENCY CASH"),
20
+ gr.Slider(label="Cash Ratio (%)", minimum=0, maximum=100, step=1)
21
+ ]
22
+
23
+ output = gr.HTML()
24
+
25
+ # Define the update function
26
+ def update_output(main_currency, holdings, cash_amount, cash_ratio):
27
+ return rebalancing_interface_fn(main_currency, holdings, cash_amount, cash_ratio)
28
+
29
+ # portfolio_rebalancing_interface = gr.Interface(
30
+ # fn=portfolio_rebalancing_interface_fn,
31
+ # inputs=portfolio_inputs,
32
+ # outputs=gr.HTML(),
33
+ # examples=examples,
34
+ # cache_examples=False,
35
+ # live=False
36
+ # )
37
+
interface/share_price_trend_interface.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from modules.utils import load_css
3
+ from modules.share_price_trend import share_price_trend
4
+
5
+ # Define the interface for the Compare tab
6
+ def share_price_trend_interface_fn(stock_codes, period):
7
+ result = share_price_trend(stock_codes, period)
8
+ css = load_css()
9
+ return css + result
10
+
11
+ share_price_trend_inputs = [
12
+ gr.Textbox(label="Stock Codes", info="Enter stock codes separated by comma.", placeholder="e.g., AAPL,GOOGL,MSFT", value="AAPL,GOOGL,MSFT"),
13
+ gr.Number(label="input # of days below", value="90")
14
+ ]
15
+
16
+ output=gr.HTML()
17
+
18
+ # Define the update function
19
+ def update_output(stock_codes, period):
20
+ return share_price_trend_interface_fn(stock_codes, period)
21
+
22
+ # compare_stock_prices_interface = gr.Interface(
23
+ # fn=compare_stock_prices_interface_fn,
24
+ # inputs=compare_inputs,
25
+ # outputs=gr.HTML(),
26
+ # live=False
27
+ # )
modules/dollar_cost_averaging.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from modules.utils import load_css
2
+
3
+ def 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 gradio_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 = dollar_cost_averaging(old_avg_price, old_quantity, new_price, new_quantity)
39
+
40
+ # 수익률에 따른 클래스 설정
41
+ new_return_class = ""
42
+ old_return_class = ""
43
+ if new_return > 0:
44
+ new_return_class = f"<span style='color: #4caf50; font-weight: bold;'>{new_return:+,.2f}%</span>"
45
+ elif new_return < 0:
46
+ new_return_class = f"<span style='color: #f44336; font-weight: bold;'>{new_return:,.2f}%</span>"
47
+ else:
48
+ new_return_class = f"<span><strong>0</strong></span>"
49
+
50
+ if old_return > 0:
51
+ old_return_class = f"<span style='color: #4caf50; font-weight: bold;'>{old_return:+,.2f}%</span>"
52
+ elif old_return < 0:
53
+ old_return_class = f"<span style='color: #f44336; font-weight: bold;'>{old_return:,.2f}%</span>"
54
+ else:
55
+ old_return_class = f"<span><strong>0</strong></span>"
56
+
57
+ # HTML 결과 생성
58
+ result_html = css + f"""
59
+ <div class="wrap-text" style="box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.1); border-radius: 0.5rem; padding: 3rem; position: relative; width: 100%; padding: 1.5rem;">
60
+ <div>
61
+ <div style="margin-bottom: 1.5rem;">
62
+ <!-- 이전 수익률 표시 -->
63
+ <div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Old Return</div>
64
+ <div style="font-size: 1.5rem;">
65
+ {old_return_class}
66
+ </div>
67
+ <hr style="margin: 1.5rem 0;">
68
+ </div>
69
+ </div>
70
+ <div>
71
+ <div style="margin-bottom: 1.5rem;">
72
+ <!-- 새로운 수익률 표시 -->
73
+ <div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">New Return</div>
74
+ <div style="font-size: 1.5rem;">
75
+ {new_return_class}
76
+ </div>
77
+ <hr style="margin: 1.5rem 0;">
78
+ </div>
79
+ </div>
80
+ <div>
81
+ <div style="margin-bottom: 1.5rem;">
82
+ <!-- 추가 투자 금액 표시 -->
83
+ <div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Additional Investment</div>
84
+ <div style="font-size: 1.5rem; font-weight: bold; color: #1c75bc;">
85
+ <span style='color: #1678fb'>{additional_investment:,.0f}</span>
86
+ </div>
87
+ <hr style="margin: 1.5rem 0;">
88
+ </div>
89
+ </div>
90
+ <div>
91
+ <div style="margin-bottom: 1.5rem;">
92
+ <!-- 평균 가격 표시 -->
93
+ <div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Average Price</div>
94
+ <div style="font-size: 1.5rem; font-weight: bold; color: #1c75bc;">
95
+ <span style='color: #1678fb'>{new_avg_price:,.0f}</span>
96
+ </div>
97
+ <hr style="margin: 1.5rem 0;">
98
+ </div>
99
+ </div>
100
+ <div>
101
+ <div style="margin-bottom: 1.5rem;">
102
+ <!-- 총 수량 표시 -->
103
+ <div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Total Quantity</div>
104
+ <div style="font-size: 1.5rem; font-weight: bold; color: #1c75bc;">
105
+ <span style='color: #1678fb'>{total_quantity:,.0f}</span>
106
+ </div>
107
+ <hr style="margin: 1.5rem 0;">
108
+ </div>
109
+ </div>
110
+ <div>
111
+ <div style="margin-bottom: 1.5rem;">
112
+ <!-- 총 투자 금액 표시 -->
113
+ <div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Total Investment</div>
114
+ <div style="font-size: 1.5rem; font-weight: bold; color: #1c75bc;">
115
+ <span style='color: #1678fb'>{total_investment:,.0f}</span>
116
+ </div>
117
+ <hr style="margin: 1.5rem 0;">
118
+ </div>
119
+ </div>
120
+ </div>
121
+ """
122
+ return result_html
modules/rebalancing.py ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import pytz
3
+ import math
4
+ import pandas as pd
5
+ import FinanceDataReader as fdr
6
+ import yfinance as yf
7
+ from datetime import datetime
8
+ from concurrent.futures import ThreadPoolExecutor
9
+ from modules.utils import load_css, get_currency_symbol, format_quantity
10
+
11
+ # 주어진 입력을 구문 분석하여 주식 보유량, 현금 금액 및 현금 비율을 계산하는 함수
12
+ def parse_input(holdings, cash_amount, cash_ratio):
13
+ lines = holdings.strip().split(',')
14
+ stock_inputs = []
15
+ total_target_weight = 0
16
+
17
+ for line in lines:
18
+ parts = line.split()
19
+ if len(parts) == 4:
20
+ stock_code, currency_code, quantity_expr, target_weight_expr = parts
21
+ quantity = math.floor(eval(quantity_expr.replace(' ', '')))
22
+ target_weight = eval(target_weight_expr.replace(' ', ''))
23
+ target_ratio = (1 - cash_ratio / 100) * target_weight
24
+ stock_inputs.append((currency_code, stock_code, quantity, target_weight, target_ratio))
25
+ total_target_weight += target_weight
26
+
27
+ cash_amount = math.floor(cash_amount) if cash_amount else 0
28
+ main_currency_cash_inputs = {'amount': cash_amount, 'target_weight': cash_ratio / 100.0}
29
+
30
+ stock_total_weight = total_target_weight
31
+
32
+ for i in range(len(stock_inputs)):
33
+ stock_inputs[i] = (stock_inputs[i][0], stock_inputs[i][1], stock_inputs[i][2], stock_inputs[i][3], (1 - main_currency_cash_inputs['target_weight']) * stock_inputs[i][3] / stock_total_weight)
34
+
35
+ return stock_inputs, main_currency_cash_inputs
36
+
37
+ # 주어진 통화 코드와 메인 통화 간의 환율을 가져오는 함수
38
+ def get_portfolio_exchange_rate(currency_code, main_currency):
39
+ if currency_code.lower() == main_currency.lower():
40
+ return 1.0
41
+
42
+ ticker = f"{currency_code.upper()}{main_currency.upper()}=X"
43
+ data = yf.download(ticker, period='1d', progress=False) # progress=False 추가
44
+ if not data.empty:
45
+ return data['Close'].iloc[0]
46
+ else:
47
+ raise ValueError("Failed to retrieve exchange rate data.")
48
+
49
+ # 주어진 주식 코드와 통화 코드에 대해 환율을 반영한 주식 가격을 가져오는 함수
50
+ def get_portfolio_exchange_reflected_stock_price(stock_code, currency_code, main_currency):
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
+
55
+ # 주어진 주식 코드의 현재 주식 가격을 가져오는 함수
56
+ def get_portfolio_current_stock_price(stock_code):
57
+ df = fdr.DataReader(stock_code)
58
+ return df['Close'].iloc[-1]
59
+
60
+ # 주어진 주식 입력 및 현금 입력을 바탕으로 포트폴리오를 구축하는 함수
61
+ def build_portfolio(stock_inputs, main_currency_cash_inputs, main_currency):
62
+ portfolio = {}
63
+ target_weights = {}
64
+
65
+ with ThreadPoolExecutor() as executor:
66
+ results = executor.map(lambda x: (x[1], get_portfolio_exchange_reflected_stock_price(x[1], x[0], main_currency), x[2], x[3], x[4], x[0]), stock_inputs)
67
+
68
+ for stock_code, new_price, quantity, target_weight, target_ratio, currency_code in results:
69
+ portfolio[stock_code] = {'quantity': quantity, 'price': new_price, 'target_weight': target_weight, 'currency': currency_code}
70
+ target_weights[stock_code] = target_ratio
71
+
72
+ return portfolio, target_weights, main_currency_cash_inputs
73
+
74
+ # 포트폴리오 재조정 정보를 가져오는 함수
75
+ def get_portfolio_rebalancing_info(portfolio, target_weights, main_currency_cash_inputs, main_currency):
76
+ css = load_css()
77
+
78
+ current_time = datetime.now().strftime("%b-%d-%Y")
79
+
80
+ total_value = sum(stock['price'] * stock['quantity'] for stock in portfolio.values()) + main_currency_cash_inputs['amount']
81
+ total_new_stock_value = 0
82
+ total_trade_value = 0
83
+ adjustments = []
84
+
85
+ currency_symbol = get_currency_symbol(main_currency)
86
+
87
+ # 포트폴리오 정보 생성
88
+ portfolio_info = css + f"""
89
+ <div class="wrap-text" style="box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.1); border-radius: 0.5rem; padding: 3rem; position: relative; width: 100%; padding: 1.5rem;">
90
+ <div>
91
+ <div style="margin-bottom: 1.5rem;">
92
+ <!-- 전체 평가금액 표시 -->
93
+ <div style="font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 1.5rem;">Market Value</div>
94
+ <span style='font-size: 1.5rem; font-weight: bold; color: #1678fb'>{currency_symbol}{total_value:,.0f}</span>
95
+ As of {current_time}
96
+ <hr style="margin: 1.5rem 0;">
97
+ </div>
98
+ </div>
99
+ </div>
100
+ """
101
+
102
+ # 현재 비율 및 가치를 계산
103
+ current_weights = {stock_code: (stock['price'] * stock['quantity'] / total_value) * 100 for stock_code, stock in portfolio.items()}
104
+ current_values = {stock_code: stock['price'] * stock['quantity'] for stock_code, stock in portfolio.items()}
105
+
106
+ # 현금을 현재 비율 및 가치에 포함
107
+ current_weights['CASH'] = (main_currency_cash_inputs['amount'] / total_value) * 100
108
+ current_values['CASH'] = main_currency_cash_inputs['amount']
109
+
110
+ # 현재 비율을 기준으로 주식을 내림차순으로 정렬
111
+ # sorted_stocks = sorted(current_weights.items(), key=lambda x: x[1], reverse=True)
112
+ sorted_stocks = current_weights.items()
113
+
114
+ # 현재 보유량 및 가치 섹션 표시
115
+ current_info_html = "<h3>Your Portfolio Holdings</h3><div class='table-container'><table>"
116
+ current_info_html += "<thead><tr><th>Stock Code</th><th>Current Weight (%)</th><th>Current Value</th></tr></thead><tbody>"
117
+ for stock_code, weight in sorted_stocks:
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_values[stock_code]:,.0f}</td>"
123
+ f"</tr>"
124
+ )
125
+ current_info_html += "</tbody></table></div><br>"
126
+
127
+ # 조정 항목 생성
128
+ for stock_code, stock_data in portfolio.items():
129
+ current_value = stock_data['price'] * stock_data['quantity']
130
+ target_value = total_value * target_weights.get(stock_code, 0)
131
+ difference = target_value - current_value
132
+ trade_quantity = math.floor(difference / stock_data['price']) if difference > 0 else -math.ceil(-difference / stock_data['price'])
133
+ new_quantity = trade_quantity + stock_data['quantity']
134
+ new_value = new_quantity * stock_data['price']
135
+ trade_value = trade_quantity * stock_data['price']
136
+ total_trade_value += abs(trade_value)
137
+ total_new_stock_value += new_value
138
+ current_value_pct = (current_value / total_value) * 100
139
+ new_value_pct = (new_value / total_value) * 100
140
+
141
+ adjustments.append((difference, current_value, target_value, current_value_pct, trade_quantity, stock_code, stock_data['price'], new_value, trade_value, stock_data['quantity'], new_quantity, target_weights[stock_code], new_value_pct, stock_data['target_weight'], stock_data['currency']))
142
+
143
+ # 현금에 대한 조정 항목 생성
144
+ main_currency_new_amount = total_value - total_new_stock_value
145
+ main_currency_target_value = total_value * main_currency_cash_inputs['target_weight']
146
+ main_currency_difference = main_currency_new_amount - main_currency_cash_inputs['amount']
147
+ trade_quantity = main_currency_difference
148
+ new_quantity = main_currency_cash_inputs['amount'] + trade_quantity
149
+ new_value = new_quantity
150
+ trade_value = trade_quantity
151
+ current_value = main_currency_cash_inputs['amount']
152
+ current_value_pct = (current_value / total_value) * 100
153
+ new_value_pct = (new_value / total_value) * 100
154
+
155
+ adjustments.append((main_currency_difference, current_value, main_currency_target_value, current_value_pct, trade_quantity, 'CASH', 1, new_value, trade_value, main_currency_cash_inputs['amount'], new_quantity, main_currency_cash_inputs['target_weight'], new_value_pct, '', 'main_currency'))
156
+
157
+ # 통화별 포트폴리오 요약 생성
158
+ currency_totals = {stock_data['currency']: {'amount': 0, 'weight': 0} for stock_data in portfolio.values()}
159
+
160
+ for stock_code, stock_data in portfolio.items():
161
+ currency = stock_data['currency']
162
+ current_value = stock_data['price'] * stock_data['quantity']
163
+ currency_totals[currency]['amount'] += current_value
164
+ currency_totals[currency]['weight'] += current_value / total_value
165
+
166
+ currency_totals['CASH'] = {'amount': main_currency_cash_inputs['amount'], 'weight': main_currency_cash_inputs['amount'] / total_value}
167
+ # sorted_currencies = sorted(currency_totals.items(), key=lambda x: x[1]['weight'], reverse=True)
168
+ sorted_currencies = currency_totals.items()
169
+
170
+
171
+ # 통화별 요약 테이블 생성
172
+ currency_table = "<h3>Your Portfolio by Currency</h3><div class='table-container wrap-text'><table>"
173
+ currency_table += "<thead><tr><th>Currency</th><th>Total Weight (%)</th><th>Total Value</th></tr></thead><tbody>"
174
+
175
+ for currency, data in sorted_currencies:
176
+ currency_table += (
177
+ f"<tr>"
178
+ f"<td>{currency.upper()}</td>"
179
+ f"<td>{data['weight'] * 100:.1f}%</td>"
180
+ f"<td>{currency_symbol}{data['amount']:,}</td>"
181
+ f"</tr>"
182
+ )
183
+
184
+ currency_table += "</tbody></table></div><br>"
185
+
186
+ # 재조정 분석 테이블 생성
187
+ result_message = portfolio_info + current_info_html + currency_table + "<h3>Re-Balancing Analysis</h3><div class='table-container wrap-text'><table>"
188
+ result_message += "<thead><tr><th>Stock Code</th><th>Current Weight (%)</th><th>Target Weight</th><th>Target Ratio (%)</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>"
189
+
190
+ for adj in adjustments:
191
+ difference, current_value, target_value, current_value_pct, trade_quantity, stock_code, price, new_value, trade_value, old_quantity, new_quantity, target_ratio, new_value_pct, target_weight, currency = adj
192
+ Buy_or_Sell = ""
193
+ if trade_quantity > 0:
194
+ Buy_or_Sell = f"<span class='buy-sell buy'>Buy</span>"
195
+ elif trade_quantity < 0:
196
+ Buy_or_Sell = f"<span class='buy-sell sell'>Sell</span>"
197
+ else:
198
+ Buy_or_Sell = f"<span></span>"
199
+
200
+ current_value_pct_str = f"{current_value_pct:.1f}%"
201
+ target_weight_str = f"<span class='highlight-edit'>{target_weight}</span>" if stock_code != 'CASH' else ''
202
+ target_ratio_str = f"<span class='highlight-edit'>{target_ratio * 100:.1f}%</span>" if stock_code == 'CASH' else f"{target_ratio * 100:.1f}%"
203
+ trade_value_str = f"<span class='highlight-sky'>{format_quantity(trade_value)}</span>" if trade_value != 0 else ''
204
+ price_str = f"{currency_symbol}{price:,.0f}" if stock_code != 'CASH' else ''
205
+ trade_quantity_str = (
206
+ f"<span class='highlight-sky'>{format_quantity(trade_quantity)}</span>"
207
+ if stock_code != 'CASH' and trade_value != 0 else ''
208
+ )
209
+ old_quantity_str = f"{old_quantity:,.0f} → {new_quantity:,.0f}" if stock_code != 'CASH' else ''
210
+ new_value_str = f"{currency_symbol}{new_value:,.0f}"
211
+ new_value_pct_str = f"{new_value_pct:.1f}%"
212
+
213
+ result_message += (
214
+ f"<tr>"
215
+ f"<td>{stock_code.upper()}</td>"
216
+ f"<td>{current_value_pct_str}</td>"
217
+ f"<td>{target_weight_str}</td>"
218
+ f"<td>{target_ratio_str}</td>"
219
+ f"<td>{Buy_or_Sell}</td>"
220
+ f"<td>{trade_value_str}</td>"
221
+ f"<td>{price_str}</td>"
222
+ f"<td>{trade_quantity_str}</td>"
223
+ f"<td>{old_quantity_str}</td>"
224
+ f"<td>{new_value_str}</td>"
225
+ f"<td>{new_value_pct_str}</td>"
226
+ f"</tr>"
227
+ )
228
+
229
+ result_message += "</tbody></table></div>"
230
+
231
+ return result_message
232
+
233
+ # 포트폴리오 재조정 도구의 주요 함수
234
+ def rebalancing_tool(main_currency, holdings, cash_amount, cash_ratio):
235
+ try:
236
+ stock_inputs, main_currency_cash_inputs = parse_input(holdings, cash_amount, cash_ratio)
237
+ portfolio, target_weights, main_currency_cash_inputs = build_portfolio(stock_inputs, main_currency_cash_inputs, main_currency)
238
+ result = get_portfolio_rebalancing_info(portfolio, target_weights, main_currency_cash_inputs, main_currency)
239
+ return result
240
+ except Exception as e:
241
+ return str(e)
modules/retirement_planning.py CHANGED
@@ -23,8 +23,10 @@ def retirement_planning(current_age=None, retirement_age=None, life_expectancy=N
23
  # 은퇴 전후의 년 수 계산
24
  if retirement_age > life_expectancy:
25
  return "<p style='color: red;'>Error: Retirement age cannot be greater than life expectancy.</p>"
26
- if current_age > retirement_age:
27
- return "<p style='color: red;'>Error: Current age cannot be greater than Retirement age.</p>"
 
 
28
  years_to_retirement = retirement_age - current_age
29
  post_retirement_years = life_expectancy - retirement_age
30
 
 
23
  # 은퇴 전후의 년 수 계산
24
  if retirement_age > life_expectancy:
25
  return "<p style='color: red;'>Error: Retirement age cannot be greater than life expectancy.</p>"
26
+
27
+ if retirement_age < current_age:
28
+ return "<p style='color: red;'>Error: Retirement age cannot be less than current age.</p>"
29
+
30
  years_to_retirement = retirement_age - current_age
31
  post_retirement_years = life_expectancy - retirement_age
32
 
modules/share_price_trend.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ return df['Close']
14
+ except Exception as e:
15
+ print(f"Failed to fetch data for {stock_code}: {e}")
16
+ return None
17
+
18
+ def share_price_trend(stock_codes, days):
19
+ stock_prices = {}
20
+ with ThreadPoolExecutor(max_workers=10) as executor:
21
+ futures = {executor.submit(get_stock_prices, stock_code.strip(), int(days)): stock_code.strip() for stock_code in stock_codes.split(',')}
22
+ for future in as_completed(futures):
23
+ stock_code = futures[future]
24
+ try:
25
+ prices = future.result()
26
+ if prices is not None:
27
+ stock_prices[stock_code] = prices
28
+ except Exception as e:
29
+ print(f"Failed to fetch data for {stock_code}: {e}")
30
+
31
+ plt.switch_backend('agg')
32
+ plt.style.use('tableau-colorblind10') # 테마 변경
33
+
34
+ fig, ax = plt.subplots(figsize=(8, 4.5))
35
+ for stock_code, prices in stock_prices.items():
36
+ relative_prices = prices / prices.iloc[0]
37
+ ax.plot(prices.index, relative_prices, label=stock_code.upper())
38
+
39
+ ax.spines['top'].set_visible(False)
40
+ ax.spines['right'].set_visible(False)
41
+
42
+ ax.set_xlabel('Date')
43
+ ax.set_ylabel('Relative Price (Normalized to 1)')
44
+ ax.legend()
45
+ plt.tight_layout()
46
+
47
+ svg_graph = io.StringIO()
48
+ plt.savefig(svg_graph, format='svg')
49
+ svg_graph.seek(0)
50
+ svg_data = svg_graph.getvalue()
51
+ plt.close()
52
+
53
+ svg_data = svg_data.replace('<svg ', '<svg width="100%" height="100%" ')
54
+ svg_data = svg_data.replace('</svg>', '''
55
+ <defs>
56
+ <linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
57
+ <stop offset="0%" style="stop-color:rgb(173,216,230);stop-opacity:1" />
58
+ <stop offset="100%" style="stop-color:rgb(0,191,255);stop-opacity:1" />
59
+ </linearGradient>
60
+ <filter id="dropshadow" height="130%">
61
+ <feGaussianBlur in="SourceAlpha" stdDeviation="3"/>
62
+ <feOffset dx="2" dy="2" result="offsetblur"/>
63
+ <feMerge>
64
+ <feMergeNode/>
65
+ <feMergeNode in="SourceGraphic"/>
66
+ </feMerge>
67
+ </filter>
68
+ </defs>
69
+ <style>
70
+ @keyframes lineAnimation {
71
+ from {
72
+ stroke-dasharray: 0, 1000;
73
+ }
74
+ to {
75
+ stroke-dasharray: 1000, 0;
76
+ }
77
+ }
78
+ path {
79
+ animation: lineAnimation 1s linear forwards;
80
+ }
81
+ </style>
82
+ </svg>''')
83
+
84
+ # Replace line color with gradient, add shadow filter, and apply animation
85
+ svg_data = svg_data.replace('stroke="#1f77b4"', 'stroke="url(#grad1)" filter="url(#dropshadow)"')
86
+
87
+ html_table = "<h3>Stock Prices Data</h3><div class='table-container'><table>"
88
+ html_table += "<thead><tr><th>Date</th>"
89
+ for stock_code in stock_prices.keys():
90
+ html_table += f"<th>{stock_code.upper()}</th>"
91
+ html_table += "</tr></thead><tbody>"
92
+
93
+ dates = stock_prices[list(stock_prices.keys())[0]].index[::-1]
94
+ for date in dates:
95
+ html_table += f"<tr><td>{date.strftime('%Y-%m-%d')}</td>"
96
+ for stock_code in stock_prices.keys():
97
+ html_table += f"<td>{stock_prices[stock_code][date]:,.2f}</td>"
98
+ html_table += "</tr>"
99
+
100
+ html_table += "</tbody></table></div>"
101
+
102
+ graph_html = f'<h3>Relative Stock Prices Over the Last {days} Days</h3>{svg_data}'
103
+ return graph_html + html_table
104
+
105
+ # 예시 사용 방법
106
+ # stock_codes = "AAPL,MSFT,GOOGL"
107
+ # days = 30
108
+ # result = compare_stock_prices(stock_codes, days)
109
+ # print(result)