fix: DRIP Forecast tab improvements - Fix duplicate sections in DRIP Forecast tab - Fix monthly income calculation in detailed allocation table - Add back Detailed Allocation table with improved calculations - Update DRIP vs No-DRIP comparison to use correct variable names
This commit is contained in:
parent
edf2ce5e9c
commit
163bf0e93b
@ -2080,6 +2080,20 @@ def display_drip_forecast(portfolio_result, tickers):
|
|||||||
total_income = portfolio_result.total_income
|
total_income = portfolio_result.total_income
|
||||||
accumulated_cash = portfolio_result.accumulated_cash
|
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
|
# Create columns for key metrics
|
||||||
col1, col2, col3 = st.columns(3)
|
col1, col2, col3 = st.columns(3)
|
||||||
|
|
||||||
@ -2087,12 +2101,15 @@ def display_drip_forecast(portfolio_result, tickers):
|
|||||||
st.metric(
|
st.metric(
|
||||||
"Portfolio Value",
|
"Portfolio Value",
|
||||||
f"${total_value:,.2f}",
|
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:
|
with col2:
|
||||||
st.metric(
|
st.metric(
|
||||||
"Monthly Income",
|
"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:
|
with col3:
|
||||||
st.metric(
|
st.metric(
|
||||||
@ -2108,7 +2125,7 @@ def display_drip_forecast(portfolio_result, tickers):
|
|||||||
drip_service = DRIPService()
|
drip_service = DRIPService()
|
||||||
|
|
||||||
# Calculate DRIP scenario
|
# Calculate DRIP scenario
|
||||||
portfolio_result = drip_service.forecast_portfolio(
|
drip_result = drip_service.forecast_portfolio(
|
||||||
portfolio_df=final_alloc,
|
portfolio_df=final_alloc,
|
||||||
config=DripConfig(
|
config=DripConfig(
|
||||||
months=12,
|
months=12,
|
||||||
@ -2139,7 +2156,7 @@ def display_drip_forecast(portfolio_result, tickers):
|
|||||||
comparison_data = {
|
comparison_data = {
|
||||||
"Strategy": ["DRIP", "No-DRIP"],
|
"Strategy": ["DRIP", "No-DRIP"],
|
||||||
"Portfolio Value": [
|
"Portfolio Value": [
|
||||||
portfolio_result.total_value,
|
drip_result.total_value,
|
||||||
nodrip_result.total_value
|
nodrip_result.total_value
|
||||||
],
|
],
|
||||||
"Accumulated Cash": [
|
"Accumulated Cash": [
|
||||||
@ -2147,7 +2164,7 @@ def display_drip_forecast(portfolio_result, tickers):
|
|||||||
nodrip_result.accumulated_cash
|
nodrip_result.accumulated_cash
|
||||||
],
|
],
|
||||||
"Total Value": [
|
"Total Value": [
|
||||||
portfolio_result.total_value,
|
drip_result.total_value,
|
||||||
nodrip_result.total_value + nodrip_result.accumulated_cash
|
nodrip_result.total_value + nodrip_result.accumulated_cash
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -2159,7 +2176,7 @@ def display_drip_forecast(portfolio_result, tickers):
|
|||||||
fig.add_trace(go.Bar(
|
fig.add_trace(go.Bar(
|
||||||
name="DRIP",
|
name="DRIP",
|
||||||
x=["Portfolio Value"],
|
x=["Portfolio Value"],
|
||||||
y=[portfolio_result.total_value],
|
y=[drip_result.total_value],
|
||||||
marker_color="#1f77b4"
|
marker_color="#1f77b4"
|
||||||
))
|
))
|
||||||
|
|
||||||
@ -2201,12 +2218,12 @@ def display_drip_forecast(portfolio_result, tickers):
|
|||||||
"Share Growth"
|
"Share Growth"
|
||||||
],
|
],
|
||||||
"DRIP": [
|
"DRIP": [
|
||||||
f"${portfolio_result.total_value:,.2f}",
|
f"${drip_result.total_value:,.2f}",
|
||||||
"$0.00",
|
"$0.00",
|
||||||
f"${portfolio_result.total_value:,.2f}",
|
f"${drip_result.total_value:,.2f}",
|
||||||
f"${portfolio_result.monthly_income:,.2f}",
|
f"${drip_result.monthly_income:,.2f}",
|
||||||
f"${portfolio_result.total_income:,.2f}",
|
f"${drip_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.etf_results[tickers[0]].final_shares / drip_result.etf_results[tickers[0]].initial_shares - 1) * 100):.1f}%"
|
||||||
],
|
],
|
||||||
"No-DRIP": [
|
"No-DRIP": [
|
||||||
f"${nodrip_result.total_value:,.2f}",
|
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)
|
- 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:
|
except Exception as e:
|
||||||
st.error(f"Error calculating DRIP forecast: {str(e)}")
|
st.error(f"Error calculating DRIP forecast: {str(e)}")
|
||||||
logger.error(f"DRIP forecast error: {str(e)}")
|
logger.error(f"DRIP forecast error: {str(e)}")
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user