Spaces:
Running
Running
Upload 14 files
Browse files- app.py +30 -30
- interface/about_interface.py +20 -22
- interface/dollar_cost_averaging_interface.py +22 -0
- interface/rebalancing_interface.py +37 -0
- interface/share_price_trend_interface.py +27 -0
- modules/dollar_cost_averaging.py +122 -0
- modules/rebalancing.py +241 -0
- modules/retirement_planning.py +4 -2
- modules/share_price_trend.py +109 -0
app.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
-
from interface.
|
| 3 |
-
from interface.
|
| 4 |
-
from interface.
|
| 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
|
| 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(
|
| 21 |
inputs=[],
|
| 22 |
-
outputs=
|
| 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=
|
| 29 |
-
inputs=
|
| 30 |
-
outputs=
|
| 31 |
)
|
| 32 |
with gr.Column(): # Column for output
|
| 33 |
-
|
| 34 |
|
| 35 |
# Add examples
|
| 36 |
gr.Examples(
|
| 37 |
examples=examples,
|
| 38 |
cache_examples=False,
|
| 39 |
-
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
|
| 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(
|
| 50 |
inputs=[],
|
| 51 |
-
outputs=
|
| 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=
|
| 58 |
-
inputs=
|
| 59 |
-
outputs=
|
| 60 |
)
|
| 61 |
with gr.Column(): # Column for output
|
| 62 |
-
|
| 63 |
|
| 64 |
-
with gr.TabItem("
|
| 65 |
with gr.Row(): # Use Row to place inputs and output side by side
|
| 66 |
with gr.Column(): # Column for inputs
|
| 67 |
-
|
| 68 |
old_price.render()
|
| 69 |
old_quantity.render()
|
| 70 |
-
|
| 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(
|
| 77 |
inputs=[],
|
| 78 |
-
outputs=
|
| 79 |
)
|
| 80 |
with gr.Column(): # Column for output
|
| 81 |
-
|
| 82 |
|
| 83 |
# Add change event handlers for cost averaging inputs
|
| 84 |
-
for input_component in
|
| 85 |
input_component.change(
|
| 86 |
-
fn=
|
| 87 |
-
inputs=
|
| 88 |
-
outputs=
|
| 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("💡
|
| 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 |
-
"
|
| 17 |
"""
|
| 18 |
# 포트폴리오 리밸런싱 도구
|
| 19 |
|
|
@@ -46,7 +45,7 @@ def render():
|
|
| 46 |
이 도구에서 제공하는 정보는 일반적인 정보 제공 목적만을 위해 제공됩니다. 사이트의 모든 정보는 선의로 제공되지만, 사이트의 정보의 정확성, 적절성, 유효성, 신뢰성, 가용성 또는 완전성에 대해 명시적이든 묵시적이든 어떠한 종류의 진술이나 보증을 하지 않습니다. 사이트의 사용과 사이트의 정보를 신뢰하는 것은 전적으로 사용자의 책임입니다.
|
| 47 |
""",
|
| 48 |
"""
|
| 49 |
-
#
|
| 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 |
-
"
|
| 84 |
"""
|
| 85 |
-
#
|
| 86 |
|
| 87 |
이 도구는 사용자가 지정한 기간 동안 여러 회사의 상대 주가를 비교할 수 있도록 합니다. 역사적인 주가를 가져와 상대 성능을 보여주도록 정상화하고 비교 그래프를 생성합니다.
|
| 88 |
|
|
@@ -113,7 +112,7 @@ def render():
|
|
| 113 |
이 도구에서 제공하는 정보는 일반적인 정보 제공 목적만을 위해 제공됩니다. 사이트의 모든 정보는 선의로 제공되지만, 사이트의 정보의 정확성, 적절성, 유효성, 신뢰성, 가용성 또는 완전성에 대해 명시적이든 묵시적이든 어떠한 종류의 진술이나 보증을 하지 않습니다. 사이트의 사용과 사이트의 정보를 신뢰하는 것은 전적으로 사용자의 책임입니다.
|
| 114 |
""",
|
| 115 |
"""
|
| 116 |
-
#
|
| 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 |
-
"
|
| 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 |
-
"
|
| 220 |
"""
|
| 221 |
# 은퇴 계획 도구
|
| 222 |
|
|
@@ -261,7 +260,7 @@ def render():
|
|
| 261 |
이 도구에서 제공하는 정보는 일반적인 정보 제공 목적만을 위해 제공됩니다. 사이트의 모든 정보는 선의로 제공되지만, 사이트의 정보의 정확성, 적절성, 유효성, 신뢰성, 가용성 또는 완전성에 대해 명시적이든 묵시적이든 어떠한 종류의 진술이나 보증을 하지 않습니다. 사이트의 사용과 사이트의 정보를 신뢰하는 것은 전적으로 사용자의 책임입니다.
|
| 262 |
""",
|
| 263 |
"""
|
| 264 |
-
#
|
| 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 |
-
|
| 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 |
-
|
| 314 |
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 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 |
+
[](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 |
-
|
| 27 |
-
|
|
|
|
|
|
|
| 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)
|