From 1bd98153a8b99fcceb1759fd91f47e064e7f6dde Mon Sep 17 00:00:00 2001 From: Pascal Date: Wed, 4 Jun 2025 16:30:04 +0200 Subject: [PATCH] refining AI Suggestions and Drip Forecast UI --- pages/ETF_Portfolio_Builder.py | 65 ++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/pages/ETF_Portfolio_Builder.py b/pages/ETF_Portfolio_Builder.py index 1e1dc49..f461bad 100644 --- a/pages/ETF_Portfolio_Builder.py +++ b/pages/ETF_Portfolio_Builder.py @@ -2110,15 +2110,24 @@ if st.session_state.simulation_run and st.session_state.df_data is not None: st.subheader("📈 Strategy Performance Summary") col1, col2, col3, col4 = st.columns(4) + drip_variation = (comparison_result['drip_final_value'] - comparison_result['initial_investment']) / comparison_result['initial_investment'] * 100 + no_drip_variation = (comparison_result['no_drip_final_value'] - comparison_result['initial_investment']) / comparison_result['initial_investment'] * 100 + with col1: st.metric( "DRIP Final Value", - f"${comparison_result['drip_final_value']:,.2f}" + f"${comparison_result['drip_final_value']:,.2f}", + delta=f"{drip_variation:+.1f}%", + delta_color="normal" if drip_variation < 0 else "inverse" if drip_variation == 0 else "off" if drip_variation < 0 else "normal" # fallback, but we will override below ) + # Streamlit does not support custom colors, so we use green for >0, grey for <0 + # But delta_color="normal" is green for positive, red for negative. We'll use normal for green, off for grey. with col2: st.metric( "No-DRIP Final Value", - f"${comparison_result['no_drip_final_value']:,.2f}" + f"${comparison_result['no_drip_final_value']:,.2f}", + delta=f"{no_drip_variation:+.1f}%", + delta_color="normal" if no_drip_variation > 0 else "off" ) with col3: winner = comparison_result['winner'] @@ -2882,7 +2891,7 @@ if st.session_state.simulation_run and st.session_state.df_data is not None: # Strategy 2: Income Focus income_goal = InvestmentGoal( capital_target=capital_target, - income_target=income_target * 1.2 if income_target > 0 else capital_target * 0.05, # 20% higher income target + income_target=income_target * 1.2 if income_target > 0 else capital_target * 0.06, # 6% target yield risk_tolerance=RiskTolerance.CONSERVATIVE, investment_horizon=investment_horizon ) @@ -2891,16 +2900,16 @@ if st.session_state.simulation_run and st.session_state.df_data is not None: # Strategy 3: Growth Focus growth_goal = InvestmentGoal( capital_target=capital_target, - income_target=income_target * 0.8 if income_target > 0 else None, # 20% lower income target + income_target=income_target * 0.8 if income_target > 0 else capital_target * 0.03, # 3% target yield risk_tolerance=RiskTolerance.AGGRESSIVE, investment_horizon=investment_horizon ) growth_portfolio = selection_service.select_etfs(growth_goal) - # Strategy 4: Risk-Adjusted + # Strategy 4: Risk-Adjusted (uses user's risk tolerance) risk_adjusted_goal = InvestmentGoal( capital_target=capital_target, - income_target=income_target if income_target > 0 else None, + income_target=income_target if income_target > 0 else None, # No default yield target risk_tolerance=RiskTolerance[risk_tolerance.upper()], investment_horizon=investment_horizon ) @@ -2917,7 +2926,12 @@ if st.session_state.simulation_run and st.session_state.df_data is not None: # Display Balanced Growth Strategy with strategy_tabs[0]: st.write("### Balanced Growth Strategy") - st.write("A balanced approach focusing on both growth and income, suitable for most investors.") + st.write(""" + A balanced approach focusing on both growth and income, suitable for most investors. + - Target Yield: 4% + - Risk Level: Moderate + - Focus: Equal balance between growth and income + """) if balanced_portfolio: portfolio_df = pd.DataFrame(balanced_portfolio) portfolio_df['Allocation (%)'] = portfolio_df['allocation'].round().astype(int) @@ -2937,7 +2951,7 @@ if st.session_state.simulation_run and st.session_state.df_data is not None: 'Volatility (%)': etf['metrics']['volatility'] * 100, 'Max Drawdown (%)': etf['metrics']['max_drawdown'] * 100, 'Sharpe Ratio': etf['metrics']['sharpe_ratio'], - 'Dividend Yield (%)': etf['metrics']['dividend_yield'] * 100 + 'Dividend Yield (%)': etf['metrics']['dividend_yield'] } for etf in balanced_portfolio ]) @@ -2948,7 +2962,12 @@ if st.session_state.simulation_run and st.session_state.df_data is not None: # Display Income Focus Strategy with strategy_tabs[1]: st.write("### Income Focus Strategy") - st.write("Optimized for higher dividend income with lower risk, suitable for income-focused investors.") + st.write(""" + Optimized for higher dividend income with lower risk, suitable for income-focused investors. + - Target Yield: 6% + - Risk Level: Conservative + - Focus: Maximizing dividend income + """) if income_portfolio: portfolio_df = pd.DataFrame(income_portfolio) portfolio_df['Allocation (%)'] = portfolio_df['allocation'].round().astype(int) @@ -2968,7 +2987,7 @@ if st.session_state.simulation_run and st.session_state.df_data is not None: 'Volatility (%)': etf['metrics']['volatility'] * 100, 'Max Drawdown (%)': etf['metrics']['max_drawdown'] * 100, 'Sharpe Ratio': etf['metrics']['sharpe_ratio'], - 'Dividend Yield (%)': etf['metrics']['dividend_yield'] * 100 + 'Dividend Yield (%)': etf['metrics']['dividend_yield'] } for etf in income_portfolio ]) @@ -2979,7 +2998,12 @@ if st.session_state.simulation_run and st.session_state.df_data is not None: # Display Growth Focus Strategy with strategy_tabs[2]: st.write("### Growth Focus Strategy") - st.write("Optimized for capital appreciation with higher risk tolerance, suitable for growth investors.") + st.write(""" + Optimized for capital appreciation with higher risk tolerance, suitable for growth investors. + - Target Yield: 3% + - Risk Level: Aggressive + - Focus: Capital appreciation + """) if growth_portfolio: portfolio_df = pd.DataFrame(growth_portfolio) portfolio_df['Allocation (%)'] = portfolio_df['allocation'].round().astype(int) @@ -2999,7 +3023,7 @@ if st.session_state.simulation_run and st.session_state.df_data is not None: 'Volatility (%)': etf['metrics']['volatility'] * 100, 'Max Drawdown (%)': etf['metrics']['max_drawdown'] * 100, 'Sharpe Ratio': etf['metrics']['sharpe_ratio'], - 'Dividend Yield (%)': etf['metrics']['dividend_yield'] * 100 + 'Dividend Yield (%)': etf['metrics']['dividend_yield'] } for etf in growth_portfolio ]) @@ -3010,7 +3034,12 @@ if st.session_state.simulation_run and st.session_state.df_data is not None: # Display Risk-Adjusted Strategy with strategy_tabs[3]: st.write("### Risk-Adjusted Strategy") - st.write(f"Optimized for your specific risk tolerance ({risk_tolerance}), balancing growth and income.") + st.write(f""" + Optimized for your specific risk tolerance ({risk_tolerance}), with a focus on sustainable income. + - Target Yield: 5% + - Risk Level: {risk_tolerance} + - Focus: Balanced growth with sustainable income + """) if risk_adjusted_portfolio: portfolio_df = pd.DataFrame(risk_adjusted_portfolio) portfolio_df['Allocation (%)'] = portfolio_df['allocation'].round().astype(int) @@ -3030,7 +3059,7 @@ if st.session_state.simulation_run and st.session_state.df_data is not None: 'Volatility (%)': etf['metrics']['volatility'] * 100, 'Max Drawdown (%)': etf['metrics']['max_drawdown'] * 100, 'Sharpe Ratio': etf['metrics']['sharpe_ratio'], - 'Dividend Yield (%)': etf['metrics']['dividend_yield'] * 100 + 'Dividend Yield (%)': etf['metrics']['dividend_yield'] } for etf in risk_adjusted_portfolio ]) @@ -3046,7 +3075,7 @@ if st.session_state.simulation_run and st.session_state.df_data is not None: if st.button("Apply Balanced Growth", key="apply_balanced"): if balanced_portfolio: st.session_state.etf_allocations = [ - {"ticker": etf['ticker'], "allocation": etf['allocation'] * 100} + {"ticker": etf['ticker'], "allocation": int(round(etf['allocation']))} for etf in balanced_portfolio ] st.success("Applied Balanced Growth strategy!") @@ -3056,7 +3085,7 @@ if st.session_state.simulation_run and st.session_state.df_data is not None: if st.button("Apply Income Focus", key="apply_income"): if income_portfolio: st.session_state.etf_allocations = [ - {"ticker": etf['ticker'], "allocation": etf['allocation'] * 100} + {"ticker": etf['ticker'], "allocation": int(round(etf['allocation']))} for etf in income_portfolio ] st.success("Applied Income Focus strategy!") @@ -3066,7 +3095,7 @@ if st.session_state.simulation_run and st.session_state.df_data is not None: if st.button("Apply Growth Focus", key="apply_growth"): if growth_portfolio: st.session_state.etf_allocations = [ - {"ticker": etf['ticker'], "allocation": etf['allocation'] * 100} + {"ticker": etf['ticker'], "allocation": int(round(etf['allocation']))} for etf in growth_portfolio ] st.success("Applied Growth Focus strategy!") @@ -3076,7 +3105,7 @@ if st.session_state.simulation_run and st.session_state.df_data is not None: if st.button("Apply Risk-Adjusted", key="apply_risk_adjusted"): if risk_adjusted_portfolio: st.session_state.etf_allocations = [ - {"ticker": etf['ticker'], "allocation": etf['allocation'] * 100} + {"ticker": etf['ticker'], "allocation": int(round(etf['allocation']))} for etf in risk_adjusted_portfolio ] st.success("Applied Risk-Adjusted strategy!")