Allocation (%) can be updated

This commit is contained in:
Pascal BIBEHE 2025-06-05 12:31:05 +02:00
parent b339dff0d7
commit 783b2580d8

View File

@ -1133,62 +1133,22 @@ def portfolio_summary(final_alloc: pd.DataFrame) -> None:
if final_alloc is None or final_alloc.empty:
st.warning("No portfolio data available.")
return
try:
# Calculate key metrics
total_capital = final_alloc["Capital Allocated ($)"].sum()
total_income = final_alloc["Income Contributed ($)"].sum()
# Calculate weighted average yield
weighted_yield = (final_alloc["Allocation (%)"] * final_alloc["Yield (%)"]).sum() / 100
# Display metrics in columns
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Total Capital", format_large_number(total_capital))
with col2:
st.metric("Annual Income", format_large_number(total_income))
st.metric("Monthly Income", format_large_number(total_income/12))
with col3:
st.metric("Average Yield", f"{weighted_yield:.2f}%")
st.metric("Effective Yield", f"{(total_income/total_capital*100):.2f}%")
# Display allocation chart
fig = px.pie(
final_alloc,
values="Allocation (%)",
names="Ticker",
title="Portfolio Allocation by ETF",
hover_data={
"Ticker": True,
"Allocation (%)": ":.2f",
"Yield (%)": ":.2f",
"Capital Allocated ($)": ":,.2f",
"Income Contributed ($)": ":,.2f"
}
)
st.plotly_chart(fig, use_container_width=True)
# Display detailed allocation table
st.subheader("Detailed Allocation")
# Use the allocation from session state for all calculations and display
display_df = final_alloc.copy()
numeric_columns = ["Allocation (%)", "Yield (%)", "Price", "Shares", "Capital Allocated ($)", "Income Contributed ($)"]
for col in numeric_columns:
if col in display_df.columns:
display_df[col] = pd.to_numeric(display_df[col], errors='coerce')
display_df["Monthly Income"] = display_df["Income Contributed ($)"] / 12
# Format large numbers in the display DataFrame
display_df["Capital Allocated ($)"] = display_df["Capital Allocated ($)"].apply(format_large_number)
display_df["Income Contributed ($)"] = display_df["Income Contributed ($)"].apply(format_large_number)
display_df["Monthly Income"] = display_df["Monthly Income"].apply(format_large_number)
# Ensure data_source column exists and rename it for display
display_df["Capital Allocated ($)"] = display_df["Capital Allocated ($)"].apply(lambda x: f"${float(x):,.2f}")
display_df["Income Contributed ($)"] = display_df["Income Contributed ($)"].apply(lambda x: f"${float(x):,.2f}")
display_df["Monthly Income"] = display_df["Monthly Income"].apply(lambda x: f"${float(x):,.2f}")
if "data_source" in display_df.columns:
display_df = display_df.rename(columns={"data_source": "Data Source"})
else:
display_df["Data Source"] = "Unknown"
# Select and order columns for display
display_columns = [
"Ticker",
"Allocation (%)",
@ -1201,15 +1161,43 @@ def portfolio_summary(final_alloc: pd.DataFrame) -> None:
"Risk Level",
"Data Source"
]
# Format the display
st.dataframe(
display_df[display_columns].style.format({
"Allocation (%)": "{:.2f}%",
"Yield (%)": "{:.2f}%",
"Price": "${:,.2f}",
"Shares": "{:,.4f}"
}),
editor_key = "allocation_editor"
def parse_currency(val):
if isinstance(val, str):
return float(val.replace('$', '').replace(',', ''))
return float(val)
# --- Portfolio Summary Metrics (use session state allocation) ---
total_capital = display_df["Capital Allocated ($)"].apply(parse_currency).sum()
total_income = display_df["Income Contributed ($)"].apply(parse_currency).sum()
weighted_yield = (display_df["Allocation (%)"] * display_df["Yield (%)"]).sum() / 100
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Total Capital", f"${total_capital:,.2f}")
with col2:
st.metric("Annual Income", f"${total_income:,.2f}")
st.metric("Monthly Income", f"${total_income/12:,.2f}")
with col3:
st.metric("Average Yield", f"{weighted_yield:.2f}%")
st.metric("Effective Yield", f"{(total_income/total_capital*100):.2f}%")
# --- Pie chart (use session state allocation) ---
fig = px.pie(
display_df,
values="Allocation (%)",
names="Ticker",
title="Portfolio Allocation by ETF",
hover_data={
"Ticker": True,
"Allocation (%)": ":.2f",
"Yield (%)": ":.2f",
"Capital Allocated ($)": ":,.2f",
"Income Contributed ($)": ":,.2f"
}
)
st.plotly_chart(fig, use_container_width=True)
# --- Editable allocation table below the pie chart ---
st.subheader("Detailed Allocation")
edited_df = st.data_editor(
display_df[display_columns],
column_config={
"Ticker": st.column_config.TextColumn("Ticker", disabled=True),
"Allocation (%)": st.column_config.NumberColumn(
@ -1220,9 +1208,21 @@ def portfolio_summary(final_alloc: pd.DataFrame) -> None:
format="%.1f",
required=True
),
"Yield (%)": st.column_config.TextColumn("Yield (%)", disabled=True),
"Price": st.column_config.TextColumn("Price", disabled=True),
"Shares": st.column_config.TextColumn("Shares", disabled=True),
"Yield (%)": st.column_config.NumberColumn(
"Yield (%)",
format="%.2f",
disabled=True
),
"Price": st.column_config.NumberColumn(
"Price",
format="%.2f",
disabled=True
),
"Shares": st.column_config.NumberColumn(
"Shares",
format="%.2f",
disabled=True
),
"Capital Allocated ($)": st.column_config.TextColumn("Capital Allocated ($)", disabled=True),
"Monthly Income": st.column_config.TextColumn("Monthly Income", disabled=True),
"Income Contributed ($)": st.column_config.TextColumn("Income Contributed ($)", disabled=True),
@ -1230,9 +1230,35 @@ def portfolio_summary(final_alloc: pd.DataFrame) -> None:
"Data Source": st.column_config.TextColumn("Data Source", disabled=True)
},
hide_index=True,
use_container_width=True
use_container_width=True,
key=editor_key
)
edited_df = edited_df.reset_index(drop=True)
# --- Update Allocations Button ---
if st.button("Update Allocations", type="primary"):
try:
edited_df["Allocation (%)"] = pd.to_numeric(edited_df["Allocation (%)"], errors='coerce')
total_allocation = edited_df["Allocation (%)"].sum()
if abs(total_allocation - 100.0) > 0.01:
st.error(f"Total allocation must be 100%. Current total: {total_allocation:.2f}%")
else:
edited_df["Capital Allocated ($)"] = edited_df["Capital Allocated ($)"].apply(parse_currency)
edited_df["Income Contributed ($)"] = edited_df["Income Contributed ($)"].apply(parse_currency)
edited_df["Monthly Income"] = edited_df["Monthly Income"].apply(parse_currency)
total_capital = edited_df["Capital Allocated ($)"].sum()
for idx, row in edited_df.iterrows():
edited_df.at[idx, "Capital Allocated ($)"] = total_capital * (row["Allocation (%)"] / 100)
edited_df.at[idx, "Income Contributed ($)"] = edited_df.at[idx, "Capital Allocated ($)"] * (row["Yield (%)"] / 100)
edited_df.at[idx, "Monthly Income"] = edited_df.at[idx, "Income Contributed ($)"] / 12
edited_df.at[idx, "Shares"] = edited_df.at[idx, "Capital Allocated ($)"] / row["Price"]
# Update the session state with new allocations and trigger rerun
st.session_state.final_alloc = edited_df
st.success("Allocations updated successfully! Click 'Run Portfolio Simulation' to recalculate.")
st.rerun()
except Exception as e:
st.error(f"Error updating allocations: {str(e)}")
logger.error(f"Error in allocation update: {str(e)}")
logger.error(traceback.format_exc())
except Exception as e:
st.error(f"Error calculating portfolio summary: {str(e)}")
logger.error(f"Error in portfolio_summary: {str(e)}")