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

@ -1135,60 +1135,20 @@ def portfolio_summary(final_alloc: pd.DataFrame) -> None:
return return
try: try:
# Calculate key metrics # Use the allocation from session state for all calculations and display
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")
display_df = final_alloc.copy() 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 display_df["Monthly Income"] = display_df["Income Contributed ($)"] / 12
display_df["Capital Allocated ($)"] = display_df["Capital Allocated ($)"].apply(lambda x: f"${float(x):,.2f}")
# Format large numbers in the display DataFrame display_df["Income Contributed ($)"] = display_df["Income Contributed ($)"].apply(lambda x: f"${float(x):,.2f}")
display_df["Capital Allocated ($)"] = display_df["Capital Allocated ($)"].apply(format_large_number) display_df["Monthly Income"] = display_df["Monthly Income"].apply(lambda x: f"${float(x):,.2f}")
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
if "data_source" in display_df.columns: if "data_source" in display_df.columns:
display_df = display_df.rename(columns={"data_source": "Data Source"}) display_df = display_df.rename(columns={"data_source": "Data Source"})
else: else:
display_df["Data Source"] = "Unknown" display_df["Data Source"] = "Unknown"
# Select and order columns for display
display_columns = [ display_columns = [
"Ticker", "Ticker",
"Allocation (%)", "Allocation (%)",
@ -1201,15 +1161,43 @@ def portfolio_summary(final_alloc: pd.DataFrame) -> None:
"Risk Level", "Risk Level",
"Data Source" "Data Source"
] ]
editor_key = "allocation_editor"
# Format the display def parse_currency(val):
st.dataframe( if isinstance(val, str):
display_df[display_columns].style.format({ return float(val.replace('$', '').replace(',', ''))
"Allocation (%)": "{:.2f}%", return float(val)
"Yield (%)": "{:.2f}%", # --- Portfolio Summary Metrics (use session state allocation) ---
"Price": "${:,.2f}", total_capital = display_df["Capital Allocated ($)"].apply(parse_currency).sum()
"Shares": "{:,.4f}" 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={ column_config={
"Ticker": st.column_config.TextColumn("Ticker", disabled=True), "Ticker": st.column_config.TextColumn("Ticker", disabled=True),
"Allocation (%)": st.column_config.NumberColumn( "Allocation (%)": st.column_config.NumberColumn(
@ -1220,9 +1208,21 @@ def portfolio_summary(final_alloc: pd.DataFrame) -> None:
format="%.1f", format="%.1f",
required=True required=True
), ),
"Yield (%)": st.column_config.TextColumn("Yield (%)", disabled=True), "Yield (%)": st.column_config.NumberColumn(
"Price": st.column_config.TextColumn("Price", disabled=True), "Yield (%)",
"Shares": st.column_config.TextColumn("Shares", disabled=True), 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), "Capital Allocated ($)": st.column_config.TextColumn("Capital Allocated ($)", disabled=True),
"Monthly Income": st.column_config.TextColumn("Monthly Income", disabled=True), "Monthly Income": st.column_config.TextColumn("Monthly Income", disabled=True),
"Income Contributed ($)": st.column_config.TextColumn("Income Contributed ($)", 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) "Data Source": st.column_config.TextColumn("Data Source", disabled=True)
}, },
hide_index=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: except Exception as e:
st.error(f"Error calculating portfolio summary: {str(e)}") st.error(f"Error calculating portfolio summary: {str(e)}")
logger.error(f"Error in portfolio_summary: {str(e)}") logger.error(f"Error in portfolio_summary: {str(e)}")