diff --git a/pages/ETF_Portal_Builder.py b/pages/ETF_Portal_Builder.py new file mode 100644 index 0000000..5c1b594 --- /dev/null +++ b/pages/ETF_Portal_Builder.py @@ -0,0 +1,60 @@ +# Main title +st.title("📈 ETF Portfolio Builder") + +# Function to remove ticker +def remove_ticker(ticker_to_remove: str) -> None: + """Remove a ticker from the portfolio.""" + try: + logger.info(f"Removing ticker: {ticker_to_remove}") + current_allocations = list(st.session_state.etf_allocations) + st.session_state.etf_allocations = [etf for etf in current_allocations if etf["ticker"] != ticker_to_remove] + logger.info(f"Updated allocations after removal: {st.session_state.etf_allocations}") + st.rerun() + except Exception as e: + logger.error(f"Error removing ticker: {str(e)}") + st.error(f"Error removing ticker: {str(e)}") + +# Display current tickers in the main space +if st.session_state.etf_allocations: + st.markdown(""" + + """, unsafe_allow_html=True) + + # Create columns for the tickers + cols = st.columns([1] * len(st.session_state.etf_allocations)) + + # Display each ticker with a close button + for i, etf in enumerate(st.session_state.etf_allocations): + with cols[i]: + if st.button(f"× {etf['ticker']}", key=f"remove_{etf['ticker']}", + help=f"Remove {etf['ticker']} from portfolio"): + remove_ticker(etf['ticker']) + +# Debug information +logger.info("=== Session State Debug ===") \ No newline at end of file diff --git a/pages/ETF_Portfolio_Builder.py b/pages/ETF_Portfolio_Builder.py index c51f614..a8753d0 100644 --- a/pages/ETF_Portfolio_Builder.py +++ b/pages/ETF_Portfolio_Builder.py @@ -478,20 +478,9 @@ def optimize_portfolio_allocation(etf_metrics: List[Dict[str, Any]], risk_tolera etf['risk_score'] = calculate_etf_risk_score(etf) logger.info(f"Risk score for {etf['Ticker']}: {etf['risk_score']:.2f}") - # Sort ETFs by risk score based on risk tolerance - if risk_tolerance == "Conservative": - # For conservative, prefer lower risk - sorted_etfs = sorted(etf_metrics, key=lambda x: x['risk_score']) - elif risk_tolerance == "Aggressive": - # For aggressive, prefer higher risk - sorted_etfs = sorted(etf_metrics, key=lambda x: x['risk_score'], reverse=True) - else: # Moderate - # For moderate, sort by Sharpe ratio first, then risk score - sorted_etfs = sorted(etf_metrics, - key=lambda x: (x.get('sharpe_ratio', 0), -x['risk_score']), - reverse=True) - - logger.info(f"Sorted ETFs: {[etf['Ticker'] for etf in sorted_etfs]}") + # Sort ETFs by yield (higher yield = higher risk) + sorted_etfs = sorted(etf_metrics, key=lambda x: x.get('Yield (%)', 0), reverse=True) + logger.info(f"Sorted ETFs by yield: {[etf['Ticker'] for etf in sorted_etfs]}") # Calculate base allocations based on risk tolerance num_etfs = len(sorted_etfs) @@ -499,14 +488,16 @@ def optimize_portfolio_allocation(etf_metrics: List[Dict[str, Any]], risk_tolera return [] if risk_tolerance == "Conservative": - # 50% to low risk, 30% to medium risk, 20% to high risk - base_allocations = [0.5] + [0.3] + [0.2] + [0.0] * (num_etfs - 3) + # For conservative, allocate more to lower yielding ETFs + # This will require more capital to achieve the same income + base_allocations = [0.4] + [0.3] + [0.2] + [0.1] + [0.0] * (num_etfs - 4) elif risk_tolerance == "Moderate": - # 40% to medium risk, 30% to low risk, 30% to high risk - base_allocations = [0.4] + [0.3] + [0.3] + [0.0] * (num_etfs - 3) + # For moderate, balance between yield and risk + base_allocations = [0.3] + [0.3] + [0.2] + [0.2] + [0.0] * (num_etfs - 4) else: # Aggressive - # 40% to high risk, 40% to medium risk, 20% to low risk - base_allocations = [0.4] + [0.4] + [0.2] + [0.0] * (num_etfs - 3) + # For aggressive, allocate more to higher yielding ETFs + # This will require less capital to achieve the same income + base_allocations = [0.4] + [0.3] + [0.2] + [0.1] + [0.0] * (num_etfs - 4) # Adjust allocations based on number of ETFs if num_etfs < len(base_allocations): @@ -1711,10 +1702,53 @@ with st.sidebar: risk_tolerance = st.select_slider( "Risk Tolerance", options=["Conservative", "Moderate", "Aggressive"], - value="Moderate" + value=st.session_state.get("risk_tolerance", "Moderate"), + key="risk_tolerance_slider" ) - st.session_state.risk_tolerance = risk_tolerance - + + # Check if risk tolerance changed + if risk_tolerance != st.session_state.get("risk_tolerance"): + logger.info("=== Risk Tolerance Change Detection ===") + logger.info(f"Current risk tolerance in session state: {st.session_state.get('risk_tolerance')}") + logger.info(f"New risk tolerance from slider: {risk_tolerance}") + + # Update session state + st.session_state.risk_tolerance = risk_tolerance + + # Recalculate allocations if we have ETFs + if st.session_state.etf_allocations: + logger.info("Recalculating allocations due to risk tolerance change") + tickers = [etf["ticker"] for etf in st.session_state.etf_allocations] + df_data = fetch_etf_data(tickers) + + if df_data is not None and not df_data.empty: + etf_metrics = df_data.to_dict('records') + new_allocations = optimize_portfolio_allocation( + etf_metrics, + risk_tolerance, + pd.DataFrame() + ) + st.session_state.etf_allocations = new_allocations + logger.info(f"New allocations after risk tolerance change: {new_allocations}") + + # If simulation has been run, update final allocation + if st.session_state.simulation_run and st.session_state.df_data is not None: + if st.session_state.mode == "Income Target": + final_alloc = allocate_for_income( + st.session_state.df_data, + st.session_state.target, + new_allocations + ) + else: + final_alloc = allocate_for_capital( + st.session_state.df_data, + st.session_state.initial_capital, + new_allocations + ) + if final_alloc is not None: + st.session_state.final_alloc = final_alloc + st.rerun() + # Additional options st.subheader("Additional Options")