diff --git a/pages/ETF_Portfolio_Builder.py b/pages/ETF_Portfolio_Builder.py index e1e1e29..3d59823 100644 --- a/pages/ETF_Portfolio_Builder.py +++ b/pages/ETF_Portfolio_Builder.py @@ -2080,6 +2080,20 @@ def display_drip_forecast(portfolio_result, tickers): total_income = portfolio_result.total_income accumulated_cash = portfolio_result.accumulated_cash + # Calculate initial values + initial_investment = sum( + etf_result.initial_value + for etf_result in portfolio_result.etf_results.values() + ) + initial_monthly_income = sum( + etf_result.initial_value * (etf_result.average_yield / 12) + for etf_result in portfolio_result.etf_results.values() + ) + + # Calculate variations + portfolio_variation = ((total_value - initial_investment) / initial_investment) * 100 + monthly_income_variation = ((portfolio_result.monthly_income - initial_monthly_income) / initial_monthly_income) * 100 + # Create columns for key metrics col1, col2, col3 = st.columns(3) @@ -2087,12 +2101,15 @@ def display_drip_forecast(portfolio_result, tickers): st.metric( "Portfolio Value", f"${total_value:,.2f}", - f"${accumulated_cash:,.2f} cash" if accumulated_cash > 0 else None + f"{portfolio_variation:+.1f}%" if portfolio_variation >= 0 else f"{portfolio_variation:.1f}%", + delta_color="off" if portfolio_variation < 0 else "normal" ) with col2: st.metric( "Monthly Income", - f"${portfolio_result.monthly_income:,.2f}" + f"${portfolio_result.monthly_income:,.2f}", + f"{monthly_income_variation:+.1f}%" if monthly_income_variation >= 0 else f"{monthly_income_variation:.1f}%", + delta_color="off" if monthly_income_variation < 0 else "normal" ) with col3: st.metric( @@ -2108,7 +2125,7 @@ def display_drip_forecast(portfolio_result, tickers): drip_service = DRIPService() # Calculate DRIP scenario - portfolio_result = drip_service.forecast_portfolio( + drip_result = drip_service.forecast_portfolio( portfolio_df=final_alloc, config=DripConfig( months=12, @@ -2139,7 +2156,7 @@ def display_drip_forecast(portfolio_result, tickers): comparison_data = { "Strategy": ["DRIP", "No-DRIP"], "Portfolio Value": [ - portfolio_result.total_value, + drip_result.total_value, nodrip_result.total_value ], "Accumulated Cash": [ @@ -2147,7 +2164,7 @@ def display_drip_forecast(portfolio_result, tickers): nodrip_result.accumulated_cash ], "Total Value": [ - portfolio_result.total_value, + drip_result.total_value, nodrip_result.total_value + nodrip_result.accumulated_cash ] } @@ -2159,7 +2176,7 @@ def display_drip_forecast(portfolio_result, tickers): fig.add_trace(go.Bar( name="DRIP", x=["Portfolio Value"], - y=[portfolio_result.total_value], + y=[drip_result.total_value], marker_color="#1f77b4" )) @@ -2201,12 +2218,12 @@ def display_drip_forecast(portfolio_result, tickers): "Share Growth" ], "DRIP": [ - f"${portfolio_result.total_value:,.2f}", + f"${drip_result.total_value:,.2f}", "$0.00", - f"${portfolio_result.total_value:,.2f}", - f"${portfolio_result.monthly_income:,.2f}", - f"${portfolio_result.total_income:,.2f}", - f"{((portfolio_result.etf_results[tickers[0]].final_shares / portfolio_result.etf_results[tickers[0]].initial_shares - 1) * 100):.1f}%" + f"${drip_result.total_value:,.2f}", + f"${drip_result.monthly_income:,.2f}", + f"${drip_result.total_income:,.2f}", + f"{((drip_result.etf_results[tickers[0]].final_shares / drip_result.etf_results[tickers[0]].initial_shares - 1) * 100):.1f}%" ], "No-DRIP": [ f"${nodrip_result.total_value:,.2f}", @@ -2229,6 +2246,70 @@ def display_drip_forecast(portfolio_result, tickers): - Portfolio value changes due to NAV erosion and share growth (DRIP) or cash accumulation (No-DRIP) """) + # Add detailed allocation table for validation + st.subheader("Detailed Allocation") + + # Create detailed allocation data + allocation_data = [] + for ticker, etf_result in portfolio_result.etf_results.items(): + # Get initial values + initial_value = etf_result.initial_value + initial_shares = etf_result.initial_shares + initial_yield = etf_result.average_yield + initial_monthly_income = initial_value * (initial_yield / 12) + + # Get final values for comparison + final_value = etf_result.final_value + final_shares = etf_result.final_shares + final_monthly_income = final_value * (etf_result.average_yield / 12) + + # Calculate variations + value_variation = ((final_value - initial_value) / initial_value) * 100 + shares_variation = ((final_shares - initial_shares) / initial_shares) * 100 + income_variation = ((final_monthly_income - initial_monthly_income) / initial_monthly_income) * 100 + + allocation_data.append({ + "Ticker": ticker, + "Initial Value": f"${initial_value:,.2f}", + "Initial Shares": f"{initial_shares:,.4f}", + "Initial Monthly Income": f"${initial_monthly_income:,.2f}", + "Final Value": f"${final_value:,.2f}", + "Final Shares": f"{final_shares:,.4f}", + "Final Monthly Income": f"${final_monthly_income:,.2f}", + "Value Change": f"{value_variation:+.1f}%", + "Shares Change": f"{shares_variation:+.1f}%", + "Income Change": f"{income_variation:+.1f}%" + }) + + # Create DataFrame and display + allocation_df = pd.DataFrame(allocation_data) + st.dataframe( + allocation_df, + use_container_width=True, + hide_index=True, + column_config={ + "Ticker": st.column_config.TextColumn("Ticker", disabled=True), + "Initial Value": st.column_config.TextColumn("Initial Value", disabled=True), + "Initial Shares": st.column_config.TextColumn("Initial Shares", disabled=True), + "Initial Monthly Income": st.column_config.TextColumn("Initial Monthly Income", disabled=True), + "Final Value": st.column_config.TextColumn("Final Value", disabled=True), + "Final Shares": st.column_config.TextColumn("Final Shares", disabled=True), + "Final Monthly Income": st.column_config.TextColumn("Final Monthly Income", disabled=True), + "Value Change": st.column_config.TextColumn("Value Change", disabled=True), + "Shares Change": st.column_config.TextColumn("Shares Change", disabled=True), + "Income Change": st.column_config.TextColumn("Income Change", disabled=True) + } + ) + + # Add explanation + st.info(""" + **Table Explanation:** + - Initial Values: Starting values before DRIP and erosion effects + - Final Values: Values after applying DRIP and erosion effects + - Changes: Percentage variations between initial and final values + - Positive changes indicate growth, negative changes indicate erosion + """) + except Exception as e: st.error(f"Error calculating DRIP forecast: {str(e)}") logger.error(f"DRIP forecast error: {str(e)}")