###portfolio_optimizer/
###├── app.py
###├── utils.py
###├── plots.py
###├── metrics.py
###└── requirements.txt
# app.py
import streamlit as st
import pandas as pd
import numpy as np
from datetime import datetime, timedelta

# Import utility, metrics, and plotting functions
from utils import (
    get_tickers,
    download_data,
    get_company_name,
    format_asset_option,
    frequency_mapping,
    fetch_fama_french_factors
)
from metrics import (
    calculate_sharpe_ratio,
    calculate_sortino_ratio,
    calculate_treynor_ratio,
    calculate_beta,
    calculate_alpha,
    calculate_calmar_ratio,
    calculate_r_squared,
    calculate_information_ratio,
    drawdown,
    calculate_gain_loss_ratio,
    calculate_capture_ratio,
    calculate_tracking_error,
    calculate_active_return,
    calculate_modigliani_miller
)
from plots import (
    plot_growth_comparison,
    plot_drawdown_comparison,
    plot_cagr_over_time,
    plot_allocation_pie,
    plot_correlation_heatmap,
    plot_risk_return_attribution
    # Add additional plotting functions as needed
)

# ----------------------------
# Initialize Session State
# ----------------------------
if 'portfolios' not in st.session_state:
    st.session_state.portfolios = []
if 'backtest_results' not in st.session_state:
    st.session_state.backtest_results = {}
if 'step' not in st.session_state:
    st.session_state.step = "Configure Portfolio"
if 'edit_portfolio' not in st.session_state:
    st.session_state.edit_portfolio = None
if 'default_config' not in st.session_state:
    st.session_state.default_config = {
        'rf_rate': 0.02,
        'broker_fee': 0.0,
        'benchmark_symbol': '^GSPC'
    }
if 'first_time_user' not in st.session_state:
    st.session_state.first_time_user = True

# ----------------------------
# Streamlit Layout
# ----------------------------
st.set_page_config(page_title="🎯 Portfolio Optimizer", layout="wide")
st.title("🎯 Portfolio Optimizer")

# Use a placeholder that hides after loading
loading_placeholder = st.empty()
loading_placeholder.info("🎯 Portfolio Optimizer is loading, please be patient...")

# After loading is complete, clear the placeholder
loading_placeholder.empty()

st.sidebar.header("📂 Navigation")
step = st.sidebar.radio("Navigate to", [
    "Configure Portfolio",
    "Run Backtest",
    "Optimize Portfolio",
    "Monte Carlo Simulations",
    "Risk Analysis"
], index=["Configure Portfolio", "Run Backtest", "Optimize Portfolio", "Monte Carlo Simulations", "Risk Analysis"].index(st.session_state.get('step', "Configure Portfolio")), key="sidebar_radio")

tickers = get_tickers()

# ----------------------------
# Configure Portfolio Section
# ----------------------------
if step == "Configure Portfolio":
    col1, col2 = st.columns([1,1])
    with col1:
        st.subheader("📁 Existing Portfolios")
        if st.session_state.portfolios:
            portfolio_names = [p['name'] for p in st.session_state.portfolios]
            selected_portfolio = st.selectbox("Select Portfolio to Edit/Delete/Duplicate", portfolio_names)
            if selected_portfolio:
                portfolio = next(p for p in st.session_state.portfolios if p['name'] == selected_portfolio)
                col_del, col_edit, col_dup = st.columns(3)
                with col_del:
                    if st.button("🗑️ Delete Portfolio"):
                        st.session_state.portfolios = [p for p in st.session_state.portfolios if p['name'] != selected_portfolio]
                        st.success("Portfolio deleted.")
                        # Reset edit_portfolio if it was the deleted portfolio
                        if st.session_state.edit_portfolio == selected_portfolio:
                            st.session_state.edit_portfolio = None
                with col_edit:
                    if st.button("✏️ Edit Portfolio"):
                        st.session_state.edit_portfolio = selected_portfolio
                with col_dup:
                    if st.button("📄 Duplicate Portfolio"):
                        new_name = st.text_input("New Portfolio Name", f"{selected_portfolio}_Copy")
                        if st.button("Confirm Duplicate"):
                            if not new_name:
                                st.error("Please provide a new portfolio name.")
                            elif new_name in [p['name'] for p in st.session_state.portfolios]:
                                st.error("Portfolio name already exists.")
                            else:
                                duplicated = portfolio.copy()
                                duplicated['name'] = new_name
                                st.session_state.portfolios.append(duplicated)
                                st.success("Portfolio duplicated successfully.")
        else:
            st.write("No portfolios saved.")
        # Edit Portfolio Section
        if st.session_state.edit_portfolio:
            st.markdown("---")
            st.subheader("✏️ Edit Portfolio")
            try:
                portfolio_to_edit = next(p for p in st.session_state.portfolios if p['name'] == st.session_state.edit_portfolio)
            except StopIteration:
                st.error("The portfolio you are trying to edit no longer exists.")
                st.session_state.edit_portfolio = None
            else:
                with st.form("edit_portfolio_form"):
                    name_edit = st.text_input("Portfolio Name", portfolio_to_edit['name'], help="Enter a unique name for your portfolio.")
                    start_date_edit = st.date_input(
                        "Start Date:",
                        portfolio_to_edit['start_date'].date(),
                        min_value=datetime(1900, 1, 1),
                        help="Choose the start date for backtesting."
                    )
                    end_date_edit = st.date_input(
                        "End Date:",
                        portfolio_to_edit['end_date'].date(),
                        min_value=datetime(1900, 1, 1),
                        help="Choose the end date for backtesting."
                    )
                    rf_rate_edit = st.number_input(
                        "Risk-Free Rate (%)",
                        0.0,
                        10.0,
                        portfolio_to_edit['rf_rate'] * 100,
                        help="Enter the risk-free rate as a percentage."
                    ) / 100
                    broker_fee_edit = st.number_input(
                        "Broker Fee (%)",
                        0.0,
                        10.0,
                        portfolio_to_edit['broker_fee'] * 100,
                        step=0.000001,
                        format="%.6f",
                        help="Enter the broker fee as a percentage per transaction."
                    ) / 100
                    benchmark_symbol_edit = st.text_input(
                        "Benchmark Symbol (e.g., ^GSPC)",
                        portfolio_to_edit['benchmark_symbol'],
                        help="Enter the ticker symbol for your benchmark index."
                    )
                    rebalance_freq_edit = st.selectbox(
                        "Rebalance Frequency",
                        ["Daily", "Weekly", "Monthly", "Quarterly", "Yearly"],
                        index=["Daily", "Weekly", "Monthly", "Quarterly", "Yearly"].index(
                            [k for k, v in frequency_mapping.items() if v == portfolio_to_edit['rebalance_freq']][0]
                        ),
                        help="Choose how often to rebalance the portfolio."
                    )
                    st.markdown("### 🛠️ Set Allocations")
                    # Set default selections as formatted strings based on existing tickers
                    selected_default = [format_asset_option(ticker, get_company_name(tickers, ticker)) for ticker in portfolio_to_edit['selected']]
                    ticker_df = get_tickers()
                    asset_options = ticker_df.apply(lambda row: format_asset_option(row['Ticker'], row['Company Name']), axis=1).tolist()
                    selected_edit = st.multiselect(
                        "Select Assets:",
                        options=asset_options,
                        default=selected_default,  # Use formatted strings for default
                        help="Choose the assets you want to include in your portfolio."
                    )
                    selected_tickers_edit = [option.split(' - ')[0] for option in selected_edit if ' - ' in option]
                    allocations_edit = []
                    if selected_tickers_edit:
                        for ticker in selected_tickers_edit:
                            # Retrieve the existing allocation for the ticker
                            try:
                                alloc = portfolio_to_edit['allocations'][portfolio_to_edit['selected'].index(ticker)]
                            except ValueError:
                                alloc = 0.0  # Default to 0.0% if ticker not found

                            # Set the number input value to the existing allocation
                            alloc_input = st.number_input(
                                f"{ticker} Allocation (%)",
                                min_value=0.0,
                                max_value=100.0,
                                value=alloc,  # Use existing allocation here
                                step=0.000001,
                                format="%.6f",
                                key=f"alloc_edit_{ticker}",
                                help=f"Set the allocation percentage for {ticker}. Must sum to 100% across all selected assets."
                            )
                            allocations_edit.append(alloc_input)
                        current_total_edit = sum(allocations_edit)
                        st.markdown(f"**Total Allocation:** {current_total_edit:.6f}%")
                        if not np.isclose(current_total_edit, 100.0, atol=1e-4):
                            st.warning(f"Allocations must sum to 100%. Currently sum to {current_total_edit:.6f}%. Please adjust the allocations.")
                            if current_total_edit > 0:
                                allocations_edit = [alloc / current_total_edit * 100 for alloc in allocations_edit]
                            else:
                                st.error("Total allocation is zero. Please set allocations for your assets.")
                    submitted_edit = st.form_submit_button("Save Changes")
                    if submitted_edit:
                        if not name_edit:
                            st.error("Please provide a portfolio name.")
                        elif name_edit != portfolio_to_edit['name'] and name_edit in [p['name'] for p in st.session_state.portfolios]:
                            st.error("Portfolio name already exists. Please choose a unique name.")
                        elif not selected_edit:
                            st.error("Please select at least one asset.")
                        elif not np.isclose(sum(allocations_edit), 100.0, atol=1e-4):
                            st.warning(f"Allocations must sum to 100%. Currently sum to {sum(allocations_edit):.6f}%. Adjusting allocations proportionally.")
                            allocations_edit = [alloc / current_total_edit * 100 for alloc in allocations_edit]
                        portfolio_to_edit.update({
                            'name': name_edit,
                            'start_date': pd.to_datetime(start_date_edit),
                            'end_date': pd.to_datetime(end_date_edit),
                            'rf_rate': rf_rate_edit,
                            'broker_fee': broker_fee_edit,
                            'benchmark_symbol': benchmark_symbol_edit,
                            'rebalance_freq': frequency_mapping.get(rebalance_freq_edit, 'M'),
                            'selected': selected_tickers_edit,
                            'allocations': allocations_edit
                        })
                        st.success(f"Portfolio '{name_edit}' updated successfully!")
                        st.markdown("**Updated Allocations:**")
                        allocations_summary = {ticker: f"{alloc:.6f}%" for ticker, alloc in zip(selected_tickers_edit, allocations_edit)}
                        st.json(allocations_summary)
                        st.session_state.edit_portfolio = None
        st.markdown("---")
        st.subheader("💡 Example Portfolios")
        example_portfolios = [
            {
                'name': "Retirement Portfolio",
                'start_date': pd.to_datetime(datetime.today() - timedelta(days=365*10)),
                'end_date': pd.to_datetime(datetime.today()),
                'rf_rate': 0.02,
                'broker_fee': 0.0,
                'benchmark_symbol': '^GSPC',
                'rebalance_freq': 'M',
                'selected': ['AAPL', 'MSFT', 'GOOGL', 'JPM', 'XOM'],
                'allocations': [20.0, 20.0, 20.0, 20.0, 20.0]
            },
            {
                'name': "Tech-Heavy Portfolio",
                'start_date': pd.to_datetime(datetime.today() - timedelta(days=365*5)),
                'end_date': pd.to_datetime(datetime.today()),
                'rf_rate': 0.02,
                'broker_fee': 0.0,
                'benchmark_symbol': '^IXIC',
                'rebalance_freq': 'M',
                'selected': ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'META'],
                'allocations': [25.0, 25.0, 20.0, 15.0, 15.0]
            },
            {
                'name': "Balanced Risk-Return Portfolio",
                'start_date': pd.to_datetime(datetime.today() - timedelta(days=365*7)),
                'end_date': pd.to_datetime(datetime.today()),
                'rf_rate': 0.02,
                'broker_fee': 0.0,
                'benchmark_symbol': '^GSPC',
                'rebalance_freq': 'M',
                'selected': ['AAPL', 'JNJ', 'V', 'PG', 'XOM'],
                'allocations': [20.0, 20.0, 20.0, 20.0, 20.0]
            },
            {
                'name': "Income-Focused Portfolio",
                'start_date': pd.to_datetime(datetime.today() - timedelta(days=365*7)),
                'end_date': pd.to_datetime(datetime.today()),
                'rf_rate': 0.02,
                'broker_fee': 0.0,
                'benchmark_symbol': '^DJI',
                'rebalance_freq': 'Q',
                'selected': ['PG', 'KO', 'JNJ', 'T', 'PFE'],
                'allocations': [20.0, 20.0, 20.0, 20.0, 20.0]
            },
            {
                'name': "Growth-Oriented Portfolio",
                'start_date': pd.to_datetime(datetime.today() - timedelta(days=365*3)),
                'end_date': pd.to_datetime(datetime.today()),
                'rf_rate': 0.02,
                'broker_fee': 0.0,
                'benchmark_symbol': '^IXIC',
                'rebalance_freq': 'M',
                'selected': ['TSLA', 'NVDA', 'AMD', 'META', 'NFLX'],
                'allocations': [30.0, 25.0, 20.0, 15.0, 10.0]
            },
            {
                'name': "Value Portfolio",
                'start_date': pd.to_datetime(datetime.today() - timedelta(days=365*10)),
                'end_date': pd.to_datetime(datetime.today()),
                'rf_rate': 0.02,
                'broker_fee': 0.0,
                'benchmark_symbol': '^DJI',
                'rebalance_freq': 'Q',
                'selected': ['KO', 'PFE', 'XOM', 'WMT', 'CVX'],
                'allocations': [20.0, 20.0, 20.0, 20.0, 20.0]
            },
            {
                'name': "Conservative Portfolio",
                'start_date': pd.to_datetime(datetime.today() - timedelta(days=365*5)),
                'end_date': pd.to_datetime(datetime.today()),
                'rf_rate': 0.02,
                'broker_fee': 0.0,
                'benchmark_symbol': '^GSPC',
                'rebalance_freq': 'Q',
                'selected': ['JNJ', 'PG', 'KO', 'PEP', 'WMT'],
                'allocations': [20.0, 20.0, 20.0, 20.0, 20.0]
            },
            {
                'name': "Dividend-Focused Portfolio",
                'start_date': pd.to_datetime(datetime.today() - timedelta(days=365*7)),
                'end_date': pd.to_datetime(datetime.today()),
                'rf_rate': 0.02,
                'broker_fee': 0.0,
                'benchmark_symbol': 'DVY',  # Dividend Index
                'rebalance_freq': 'Q',
                'selected': ['T', 'VZ', 'PFE', 'KO', 'IBM'],
                'allocations': [20.0, 20.0, 20.0, 20.0, 20.0]
            },
            {
                'name': "Global Diversification Portfolio",
                'start_date': pd.to_datetime(datetime.today() - timedelta(days=365*5)),
                'end_date': pd.to_datetime(datetime.today()),
                'rf_rate': 0.02,
                'broker_fee': 0.0,
                'benchmark_symbol': 'ACWI',  # All Country World Index
                'rebalance_freq': 'M',
                'selected': ['AAPL', 'TSM', 'BABA', 'SAP', 'UL'],
                'allocations': [20.0, 20.0, 20.0, 20.0, 20.0]
            },
            {
                'name': "High-Risk Growth Portfolio",
                'start_date': pd.to_datetime(datetime.today() - timedelta(days=365*3)),
                'end_date': pd.to_datetime(datetime.today()),
                'rf_rate': 0.02,
                'broker_fee': 0.0,
                'benchmark_symbol': '^IXIC',
                'rebalance_freq': 'M',
                'selected': ['TSLA', 'ARKK', 'NVDA', 'SHOP', 'CRWD'],
                'allocations': [30.0, 20.0, 20.0, 15.0, 15.0]
            },
            {
                'name': "Emerging Markets Portfolio",
                'start_date': pd.to_datetime(datetime.today() - timedelta(days=365*5)),
                'end_date': pd.to_datetime(datetime.today()),
                'rf_rate': 0.02,
                'broker_fee': 0.0,
                'benchmark_symbol': 'EEM',  # Emerging Markets Index
                'rebalance_freq': 'M',
                'selected': ['BABA', 'TSM', 'PDD', 'INFY', 'VALE'],
                'allocations': [25.0, 25.0, 20.0, 15.0, 15.0]
            }
        ]

        for example in example_portfolios:
            with st.expander(example['name']):
                # Display portfolio details in a table
                df_example = pd.DataFrame({
                    'Attribute': ['Start Date', 'End Date', 'Risk-Free Rate (%)', 'Broker Fee (%)', 'Benchmark Symbol', 'Rebalance Frequency', 'Selected Assets', 'Allocations (%)'],
                    'Value': [
                        example['start_date'].strftime('%Y-%m-%d'),
                        example['end_date'].strftime('%Y-%m-%d'),
                        f"{example['rf_rate']*100:.2f}",
                        f"{example['broker_fee']*100:.6f}",
                        example['benchmark_symbol'],
                        example['rebalance_freq'],
                        ", ".join(example['selected']),
                        ", ".join([f"{alloc:.2f}" for alloc in example['allocations']])
                    ]
                })
                st.table(df_example)
                if st.button(f"Load {example['name']}", key=f"load_{example['name']}"):
                    if example['name'] in [p['name'] for p in st.session_state.portfolios]:
                        st.warning(f"Portfolio '{example['name']}' already exists.")
                    else:
                        st.session_state.portfolios.append(example.copy())
                        st.success(f"Portfolio '{example['name']}' loaded successfully!")

    with col2:
        st.subheader("➕ Add New Portfolio")
        with st.form("add_portfolio_form"):
            st.markdown("### 🔧 Configure Details")
            name = st.text_input("Portfolio Name", "", help="Enter a unique name for your portfolio.")
            start_date = st.date_input(
                "Start Date:",
                datetime.today() - timedelta(days=365 * 15),
                min_value=datetime(1900, 1, 1),
                help="Choose the start date for backtesting."
            )
            end_date = st.date_input(
                "End Date:",
                datetime.today(),
                min_value=datetime(1900, 1, 1),
                help="Choose the end date for backtesting."
            )
            rf_rate = st.number_input(
                "Risk-Free Rate (%)",
                0.0,
                10.0,
                st.session_state.default_config['rf_rate'] * 100,
                help="Enter the risk-free rate as a percentage."
            ) / 100
            broker_fee = st.number_input(
                "Broker Fee (%)",
                0.0,
                10.0,
                st.session_state.default_config['broker_fee'] * 100,
                step=0.000001,
                format="%.6f",
                help="Enter the broker fee as a percentage per transaction."
            ) / 100
            benchmark_symbol = st.text_input(
                "Benchmark Symbol (e.g., ^GSPC)",
                st.session_state.default_config['benchmark_symbol'],
                help="Enter the ticker symbol for your benchmark index."
            )
            rebalance_freq = st.selectbox(
                "Rebalance Frequency",
                ["Daily", "Weekly", "Monthly", "Quarterly", "Yearly"],
                index=2,
                help="Choose how often to rebalance the portfolio."
            )
            st.markdown("### 🛠️ Set Allocations")
            ticker_df = get_tickers()
            asset_options = ticker_df.apply(lambda row: format_asset_option(row['Ticker'], row['Company Name']), axis=1).tolist()
            selected = st.multiselect(
                "Select Assets:",
                options=asset_options,
                help="Choose the assets you want to include in your portfolio."
            )
            # After selecting assets, extract tickers
            selected_tickers = [option.split(' - ')[0] for option in selected]
            allocations = []
            if selected_tickers:
                for ticker in selected_tickers:
                    alloc = st.number_input(
                        f"{ticker} Allocation (%)",
                        min_value=0.0,
                        max_value=100.0,
                        value=0.0,
                        step=0.000001,
                        format="%.6f",
                        key=f"allocations_new_{ticker}",
                        help=f"Set the allocation percentage for {ticker}. The sum of all allocations must equal 100%."
                    )
                    allocations.append(alloc)
                current_total = sum(allocations)
                st.markdown(f"**Total Allocation:** {current_total:.6f}%")
                if not np.isclose(current_total, 100.0, atol=1e-4):
                    st.warning(f"Allocations must sum to 100%. Currently sum to {current_total:.6f}%. Please adjust the allocations.")
            submitted = st.form_submit_button("Add Portfolio")
            if submitted:
                if not name:
                    st.error("Please provide a portfolio name.")
                elif name in [p['name'] for p in st.session_state.portfolios]:
                    st.error("Portfolio name already exists. Please choose a unique name.")
                elif not selected:
                    st.error("Please select at least one asset.")
                elif not np.isclose(sum(allocations), 100.0, atol=1e-4):
                    st.error(f"Allocations must sum to 100%. Currently sum to {sum(allocations):.6f}%.")
                else:
                    new_portfolio = {
                        'name': name,
                        'start_date': pd.to_datetime(start_date),
                        'end_date': pd.to_datetime(end_date),
                        'rf_rate': rf_rate,
                        'broker_fee': broker_fee,
                        'benchmark_symbol': benchmark_symbol,
                        'rebalance_freq': frequency_mapping.get(rebalance_freq, 'M'),
                        'selected': selected_tickers,
                        'allocations': allocations
                    }
                    st.session_state.portfolios.append(new_portfolio)
                    st.success(f"Portfolio '{name}' added successfully!")
                    st.markdown("**Allocations:**")
                    allocations_summary = {ticker: f"{alloc}%" for ticker, alloc in zip(selected_tickers, allocations)}
                    st.json(allocations_summary)

    # ----------------------------
    # Run Backtest Section
    # ----------------------------
    elif step == "Run Backtest":
        st.title("📊 Run Backtest")
        if st.session_state.portfolios:
            with st.form("compare_form"):
                st.subheader("🔍 Select Portfolios to Compare")
                comparison_type = st.selectbox(
                    "Comparison Type",
                    ["Portfolio vs Benchmark", "Portfolio vs Portfolio"]
                )
                portfolio_names = [p['name'] for p in st.session_state.portfolios]
                if comparison_type == "Portfolio vs Benchmark":
                    portfolio1 = st.selectbox("Select Portfolio", portfolio_names)
                else:
                    portfolio1 = st.selectbox("Select First Portfolio", portfolio_names, key="p1")
                    portfolio2 = st.selectbox("Select Second Portfolio", portfolio_names, key="p2")

                st.subheader("⚙️ Visualizations Settings")
                # Selection inputs
                selected_time_frames = st.multiselect(
                    "Select Time Frames for CAGR",
                    options=['Weekly', 'Monthly', 'Quarterly', 'Annually'],
                    default=['Annually'],
                    key="selected_time_frames",
                    help="Choose one or more time frames to view CAGR over different horizons."
                )
                selected_rolling_periods = st.multiselect(
                    "Select Rolling Periods (Days)",
                    options=[30, 90, 180, 252],
                    default=[252],
                    key="selected_rolling_periods",
                    help="Choose one or more periods to view rolling metrics."
                )

                run = st.form_submit_button("Run Backtest")

            if run:
                with st.spinner("Running backtest..."):
                    if comparison_type == "Portfolio vs Benchmark":
                        portfolio = next(p for p in st.session_state.portfolios if p['name'] == portfolio1)

                        price_data = download_data(
                            portfolio['selected'],
                            portfolio['start_date'],
                            portfolio['end_date']
                        )
                        if price_data.empty:
                            st.error("No price data available for the selected portfolio.")
                        else:
                            available_selected = [ticker for ticker in portfolio['selected'] if ticker in price_data.columns]
                            missing_selected = list(set(portfolio['selected']) - set(available_selected))
                            if missing_selected:
                                st.warning(f"Excluded tickers with no data: {', '.join(missing_selected)}")
                            if not available_selected:
                                st.error(
                                    "No selected tickers have available data for the chosen date range. "
                                    "Please ensure that the ticker symbols are correct and data is available for the specified period."
                                )
                            else:
                                allocations = [portfolio.get('allocations', [0.0]*len(portfolio.get('selected', [])))[i]
                                              for i, ticker in enumerate(portfolio.get('selected', [])) if ticker in available_selected]
                                if not np.isclose(sum(allocations), 100.0, atol=1e-4):
                                    st.warning("Allocations do not sum to 100%. Adjusting allocations proportionally.")
                                    allocations = [a / sum(allocations) * 100 for a in allocations]
                                weights = np.array(allocations) / 100
                                returns, cum_returns = backtest(weights, price_data[available_selected],
                                                                portfolio['rebalance_freq'], portfolio['broker_fee'], debug=False)
                                if cum_returns.empty:
                                    st.error("Cumulative returns are empty. Check the data and allocations.")
                                else:
                                    benchmark_symbol = portfolio['benchmark_symbol']
                                    benchmark_data = download_data([benchmark_symbol], portfolio['start_date'], portfolio['end_date'])

                                    if benchmark_symbol not in benchmark_data.columns:
                                        st.error(f"The selected benchmark symbol '{benchmark_symbol}' does not have available data for the chosen period.")
                                        st.stop()
                                    else:
                                        st.success(f"Benchmark '{benchmark_symbol}' data successfully downloaded and will be used for comparison.")
                                        if not benchmark_data.empty:
                                            benchmark_returns = benchmark_data[benchmark_symbol].pct_change().dropna()
                                            benchmark_returns = benchmark_returns.reindex(returns.index, method='ffill').dropna()
                                            common_index = returns.index.intersection(benchmark_returns.index)
                                            if common_index.empty:
                                                st.error("No overlapping dates between portfolio returns and benchmark returns after alignment.")
                                                st.stop()
                                            returns = returns.loc[common_index]
                                            cum_returns = cum_returns.loc[common_index]
                                            # Align the data using an inner join
                                            aligned_data = pd.merge(returns.to_frame('Portfolio'), benchmark_returns.to_frame('Benchmark'), left_index=True, right_index=True, how='inner')

                                            # Check that we have data after merging
                                            if aligned_data.empty:
                                                st.error("No overlapping dates between portfolio returns and benchmark returns after alignment.")
                                                st.stop()

                                            # Assign aligned returns
                                            returns = aligned_data['Portfolio'].rename("Portfolio_Returns")
                                            benchmark_returns = aligned_data['Benchmark']
                                            benchmark_cum_returns = (1 + benchmark_returns).cumprod()
                                            benchmark_metrics = calculate_portfolio_metrics(
                                                benchmark_returns, benchmark_cum_returns, portfolio['rf_rate'], None
                                            )
                                        else:
                                            benchmark_returns = None
                                            benchmark_cum_returns = pd.Series(dtype=float)
                                            benchmark_metrics = {
                                                'Start Balance': "N/A",
                                                'End Balance': "N/A",
                                                'Annualized Return (CAGR)': "N/A",
                                                'Best Year': "N/A",
                                                'Worst Year': "N/A",
                                                'Arithmetic Mean (Monthly)': "N/A",
                                                'Arithmetic Mean (Annualized)': "N/A",
                                                'Geometric Mean (Monthly)': "N/A",
                                                'Geometric Mean (Annualized)': "N/A",
                                                'Standard Deviation (Monthly)': "N/A",
                                                'Standard Deviation (Annualized)': "N/A",
                                                'Downside Deviation (Monthly)': "N/A",
                                                'Maximum Drawdown': "N/A",
                                                'Sharpe Ratio': "N/A",
                                                'Sortino Ratio': "N/A",
                                                'Gain/Loss Ratio': "N/A",
                                                'Skewness': "N/A",
                                                'Excess Kurtosis': "N/A",
                                                'Safe Withdrawal Rate': "N/A",
                                                'Perpetual Withdrawal Rate': "N/A",
                                                'Positive Periods': "N/A",
                                                'Benchmark Correlation': "N/A",
                                                'Beta': "N/A",
                                                'Alpha (annualized)': "N/A",
                                                'R2': "N/A",
                                                'Treynor Ratio': "N/A",
                                                'Calmar Ratio': "N/A",
                                                'Modigliani–Modigliani Measure': "N/A",
                                                'Information Ratio': "N/A",
                                                'Tracking Error': "N/A",
                                                'Active Return': "N/A",
                                                'Upside Capture Ratio': "N/A",
                                                'Downside Capture Ratio': "N/A"
                                            }

                    if not cum_returns.empty:
                        portfolio_metrics = calculate_portfolio_metrics(
                            returns, cum_returns, portfolio['rf_rate'], benchmark_returns
                        )

                        # Dashboard Header with Key Stats
                        with st.container():
                            st.markdown("### 🔑 Key Metrics")
                            key_metrics = {
                                'Annualized Return (CAGR)': portfolio_metrics.get('Annualized Return (CAGR)', "N/A"),
                                'Sharpe Ratio': portfolio_metrics.get('Sharpe Ratio', "N/A"),
                                'Maximum Drawdown': portfolio_metrics.get('Maximum Drawdown', "N/A")
                            }
                            cols = st.columns(len(key_metrics))
                            for col, (metric, value) in zip(cols, key_metrics.items()):
                                with col:
                                    st.metric(label=metric, value=value)

                        # Create Tabs for Organized Sections
                        tabs = st.tabs(["Overview", "Performance Statistics", "Advanced Metrics", "Drawdowns", "Visualizations"])

                        with tabs[0]:
                            st.header("📈 Portfolio Performance Overview")
                            st.write(f"**Portfolio Name:** {portfolio['name']}")
                            st.write(f"**Start Date:** {portfolio['start_date'].strftime('%Y-%m-%d')}")
                            st.write(f"**End Date:** {portfolio['end_date'].strftime('%Y-%m-%d')}")
                            st.write(f"**Benchmark:** {portfolio['benchmark_symbol']}")

                        with tabs[1]:
                            st.header("📊 Performance Statistics")
                            
                            with st.container():
                                st.subheader("🛠️ Basic Metrics")
                                performance_data_stats = {
                                    'Metric': [
                                        'Start Balance',
                                        'End Balance',
                                        'Annualized Return (CAGR)',
                                        'Standard Deviation (Annualized)',
                                        'Best Year',
                                        'Worst Year',
                                        'Maximum Drawdown',
                                        'Sharpe Ratio',
                                        'Sortino Ratio',
                                        'Benchmark Correlation'
                                    ],
                                    'Portfolio': [
                                        portfolio_metrics['Start Balance'],
                                        portfolio_metrics['End Balance'],
                                        portfolio_metrics['Annualized Return (CAGR)'],
                                        portfolio_metrics['Standard Deviation (Annualized)'],
                                        portfolio_metrics['Best Year'],
                                        portfolio_metrics['Worst Year'],
                                        portfolio_metrics['Maximum Drawdown'],
                                        portfolio_metrics['Sharpe Ratio'],
                                        portfolio_metrics['Sortino Ratio'],
                                        portfolio_metrics['Benchmark Correlation']
                                    ],
                                    'Benchmark': [
                                        benchmark_metrics['Start Balance'],
                                        benchmark_metrics['End Balance'],
                                        benchmark_metrics['Annualized Return (CAGR)'],
                                        benchmark_metrics['Standard Deviation (Annualized)'],
                                        benchmark_metrics['Best Year'],
                                        benchmark_metrics['Worst Year'],
                                        benchmark_metrics['Maximum Drawdown'],
                                        benchmark_metrics['Sharpe Ratio'],
                                        benchmark_metrics['Sortino Ratio'],
                                        benchmark_metrics['Benchmark Correlation']
                                    ]
                                }
                                performance_df_stats = pd.DataFrame(performance_data_stats).set_index('Metric')
                                st.table(performance_df_stats)

                        with tabs[2]:
                            st.header("📋 Detail Comparisons")
                            
                            with st.container():
                                st.subheader("🔍 Risk Factor Attribution Analysis")
                                # Define date range based on portfolio returns index
                                start_date = returns.index.min().strftime('%Y-%m-%d')
                                end_date = returns.index.max().strftime('%Y-%m-%d')
                                # Fetch actual factor data
                                factors = fetch_fama_french_factors(start_date, end_date)
                                # Ensure factors are fetched
                                if factors.empty:
                                    st.write("Failed to retrieve factor data.")       
                                else:
                                    factors = factors.asfreq(returns.index.freq, method='ffill')
                                    # Calculate Attribution with aligned dates
                                    attribution, error = calculate_risk_factor_attribution(returns, factors)
                                    
                                    if error:
                                        st.error(error)
                                        st.write("Risk factor attribution analysis is unavailable.")
                                    elif attribution.empty:
                                        st.write("Risk factor attribution analysis is unavailable.")
                                    else:
                                        st.write("Risk Factor Attribution:")
                                        st.table(attribution)
                                        # Optional: Visualize Attribution
                                        fig_attribution = plot_risk_return_attribution(returns, weights)
                                        st.plotly_chart(fig_attribution, use_container_width=True)

                        with tabs[3]:
                            st.header("📉 Detailed Drawdowns")
                            st.markdown("### 📈 Drawdowns for Portfolio")
                            portfolio_drawdowns = get_drawdown_details(cum_returns)
                            if portfolio_drawdowns:
                                portfolio_drawdowns_df = pd.DataFrame(portfolio_drawdowns)
                                st.table(portfolio_drawdowns_df)
                            else:
                                st.write("No drawdowns detected for the portfolio.")

                            if benchmark_returns is not None and not benchmark_returns.empty:
                                st.markdown("### 📈 Drawdowns for Benchmark")
                                benchmark_drawdowns = get_drawdown_details(benchmark_cum_returns)
                                if benchmark_drawdowns:
                                    benchmark_drawdowns_df = pd.DataFrame(benchmark_drawdowns)
                                    st.table(benchmark_drawdowns_df)
                                else:
                                    st.write("No drawdowns detected for the benchmark.")
                            else:
                                st.write("Benchmark data not available for drawdown analysis.")

                        with tabs[4]:
                            st.header("📊 Visualizations")
                            if not cum_returns.empty and not benchmark_cum_returns.empty:
                                plot_growth_comparison(cum_returns, benchmark_cum_returns)
                            else:
                                st.warning("Insufficient data to display Growth Comparison. Ensure both portfolio and benchmark have data.")
                            
                            portfolio_drawdown_series = (cum_returns / cum_returns.expanding().max() - 1) * 100
                            benchmark_drawdown_series = (benchmark_cum_returns / benchmark_cum_returns.expanding().max() - 1) * 100
                            plot_drawdown_comparison(portfolio_drawdown_series, benchmark_drawdown_series)

                            st.subheader("📈 Compound Annual Growth Rate (CAGR) Over Time")
                            if st.session_state.selected_time_frames:
                                plot_cagr_over_time(cum_returns, time_frames=st.session_state.selected_time_frames)
                            else:
                                st.warning("Please select at least one time frame for CAGR.")

                            # Box Plot for Returns Distribution
                            st.subheader("📦 Returns Distribution Box Plot")
                            if isinstance(returns, pd.DataFrame):
                                melted_returns = returns.reset_index().melt(id_vars='Date', var_name='Asset', value_name='Return')
                                fig_box_plot = px.box(
                                    melted_returns,
                                    x='Asset',
                                    y='Return',
                                    title='Returns Distribution Box Plot',
                                    labels={'Return': 'Returns', 'Asset': 'Asset'},
                                    points='all',
                                    hover_data=['Return'],
                                    template='plotly_dark',
                                    color='Asset',
                                    color_discrete_sequence=px.colors.qualitative.Set3
                                )
                                st.plotly_chart(fig_box_plot, use_container_width=True)
                                st.markdown("**Interpretation:** The box plot visualizes the distribution of returns for each asset in the portfolio. The boxes represent the interquartile range (IQR), the line inside the box indicates the median, and the whiskers show the range of the data. Outliers are displayed as individual points.")
                            elif isinstance(returns, pd.Series):
                                fig_box_plot = px.box(
                                    returns.to_frame(name='Return'),
                                    y='Return',
                                    title='Returns Distribution Box Plot',
                                    labels={'Return': 'Returns'},
                                    points='all',
                                    hover_data=['Return'],
                                    template='plotly_dark'
                                )
                                st.plotly_chart(fig_box_plot, use_container_width=True)
                                st.markdown("**Interpretation:** The box plot visualizes the distribution of returns for the portfolio. The box represents the interquartile range (IQR), the line inside the box indicates the median, and the whiskers show the range of the data. Outliers are displayed as individual points.")
                            else:
                                st.warning("Returns data is neither a DataFrame nor a Series.")

                            # Heatmap of Correlations Between Assets
                            st.subheader("🔥 Correlation Heatmap of Assets")
                            plot_correlation_heatmap(returns)

                            # Add Risk-Return Attribution Analysis
                            st.subheader("🔍 Risk-Return Attribution Analysis")
                            plot_risk_return_attribution(returns, weights)

                            # Cumulative Returns Heatmap
                            st.subheader("🔥 Cumulative Returns Heatmap")
                            try:
                                cum_returns_normalized = cum_returns / cum_returns.max()
                                fig_cum_heatmap = px.imshow(
                                    cum_returns_normalized.to_frame().T,
                                    labels=dict(x="Date", y="Portfolio", color="Normalized Cumulative Return"),
                                    title="Cumulative Returns Heatmap",
                                    aspect="auto",
                                    color_continuous_scale='Viridis',
                                    template='plotly_dark'
                                )
                                st.plotly_chart(fig_cum_heatmap, use_container_width=True)
                                st.markdown("**Interpretation:** The heatmap visualizes the normalized cumulative returns over time, allowing for an intuitive comparison of portfolio performance across different periods.")
                            except Exception as e:
                                st.error(f"Error plotting cumulative returns heatmap: {e}")

                            # Portfolio Allocation Pie Chart
                            st.subheader("🥧 Portfolio Allocation Pie Chart")
                            plot_allocation_pie(weights, available_selected, title="Portfolio Allocation", hover_info="percent+name")

                            # Rolling Metrics Chart with User-Defined Periods
                            st.subheader("📉 Rolling Metrics")
                            if st.session_state.selected_rolling_periods:
                                plot_rolling_metrics(returns, windows=st.session_state.selected_rolling_periods)
                            else:
                                st.warning("Please select at least one rolling period to display metrics.")

                            # Recommendations
                            st.markdown("### 💡 Recommendations")
                            recommendations = generate_recommendations(
                                allocations=[alloc for alloc in portfolio['allocations'] if alloc > 0],
                                available_selected=[ticker for ticker in available_selected if portfolio['allocations'][available_selected.index(ticker)] > 0]
                            )
                            
                            if recommendations:
                                for rec in recommendations:
                                    st.write(rec)
                            else:
                                st.success("Your portfolio allocations are well-balanced!")
                                
                            # Final Score
                            st.markdown("### 📝 Final Score")
                            portfolio_score = calculate_final_score(portfolio_metrics, benchmark_metrics)
                            benchmark_score = calculate_final_score(benchmark_metrics, portfolio_metrics)
                            st.write(f"**Portfolio:** {portfolio_score:.2f} / 100")
                            st.write(f"**Benchmark:** {benchmark_score:.2f} / 100")

                            # Store results
                            st.session_state.backtest_results = {
                                'returns': returns,
                                'cum_returns': cum_returns,
                                'weights_a': weights,
                                'price_data': price_data[available_selected],
                                'metrics': portfolio_metrics,
                                'benchmark_metrics': benchmark_metrics,
                                'performance_df_stats': performance_df_stats,
                                'performance_df_advanced': benchmark_metrics,  # Adjust as necessary
                                'benchmark_cum_returns': benchmark_cum_returns
                            }
                            st.success("Backtest completed!")

        else:
            st.warning("Please add at least one portfolio to run backtest.")

        # ----------------------------
        # Onboarding Process
        # ----------------------------
        if st.session_state.first_time_user:
            st.info("Welcome! Let's get you started with your first portfolio.")
            st.write("1. Configure your portfolio by entering a name, start date, end date, and other details.")
            st.write("2. Select the assets you want to include and set their allocations.")
            st.write("3. Run a backtest to see how your portfolio performs.")
            st.write("4. Compare your portfolio with a benchmark or another portfolio.")
            st.write("5. Explore the performance metrics and visualizations.")
            st.session_state.first_time_user = False

        # ----------------------------
        # Documentation
        # ----------------------------
        with st.expander("📖 Documentation"):
            st.markdown("""
            ### Documentation

            **Portfolio Configuration:**
            - **Portfolio Name:** Enter a unique name for your portfolio.
            - **Risk-Free Rate:** The theoretical return of an investment with zero risk, often based on government bonds.
            - **Sharpe Ratio:** Measures the performance of an investment compared to a risk-free asset, after adjusting for its risk. 
                *Formula:* $$(R_p - R_f) / \sigma_p$$
            - **VaR (Value at Risk):** Estimates the maximum potential loss over a specific time frame at a given confidence level.
            - **CVaR (Conditional Value at Risk):** The expected loss exceeding the VaR, providing insight into tail risk.
            - **Sortino Ratio:** Similar to the Sharpe Ratio but only penalizes downside volatility.
                *Formula:* $$(R_p - R_f) / \sigma_d$$
            - **Maximum Drawdown:** The largest peak-to-trough decline in the portfolio's value over a specific period.
            - **Alpha:** Measures the active return on an investment compared to a market index.
                *Formula:* $$R_p - [R_f + \beta (R_m - R_f)]$$
            - **Beta:** Indicates the volatility of an investment relative to the market.
                *Formula:* $$\beta = \frac{Cov(R_p, R_m)}{Var(R_m)}$$
            - **Information Ratio:** Measures portfolio returns beyond the returns of a benchmark, adjusted for the volatility of those returns.
                *Formula:* $$(R_p - R_b) / TE$$
            - **Gain/Loss Ratio:** The ratio of total gains to total losses in the portfolio.
            - **Modigliani–Modigliani Measure (M²):** Adjusts the portfolio return to the risk of a benchmark, allowing for comparison.
                *Formula:* $$\alpha / \text{Sharpe Ratio}$$
            - **Tracking Error:** Measures the standard deviation of the difference between portfolio returns and benchmark returns.
            - **Upside/Downside Capture Ratios:** Measures how well the portfolio captures the benchmark's positive and negative movements respectively.
            - **Risk Factor Attribution:** Decomposes portfolio returns based on exposure to different risk factors like Momentum, Value, and Size.
            - **Performance Attribution:** Breaks down portfolio performance by individual assets, showing each asset's contribution to overall returns.
            - **Recommendation Engine:** Provides suggestions for portfolio adjustments to optimize performance based on current allocations and objectives.
            """)

    # ----------------------------
    # Optimize Portfolio Section
    # ----------------------------
    elif step == "Optimize Portfolio":
        st.title("🔧 Optimize Portfolio")
        if st.session_state.portfolios:
            portfolio_names = [p['name'] for p in st.session_state.portfolios]
            selected_portfolio = st.selectbox("Select Portfolio to Optimize", portfolio_names)
            if selected_portfolio:
                portfolio = next(p for p in st.session_state.portfolios if p['name'] == selected_portfolio)
                price_data = download_data(
                    portfolio['selected'],
                    portfolio['start_date'],
                    portfolio['end_date']
                )
                if price_data.empty:
                    st.error("No price data available for the selected portfolio.")
                else:
                    available_selected = [ticker for ticker in portfolio['selected'] if ticker in price_data.columns]
                    missing_selected = list(set(portfolio['selected']) - set(available_selected))
                    if missing_selected:
                        st.warning(f"Excluded tickers with no data: {', '.join(missing_selected)}")
                    
                    if not available_selected:
                        st.error("No selected tickers have available data for the chosen date range.")
                    else:
                        returns = price_data[available_selected].pct_change().dropna()
                        
                        with st.form("optimization_form"):
                            st.markdown("### 🛠️ Set Optimization Constraints")
                            # Interactive Sliders
                            max_weight = st.slider(
                                "Maximum Weight per Asset (%)",
                                min_value=0.0,
                                max_value=100.0,
                                value=100.0,
                                step=0.1,
                                help="Set the maximum allocation percentage for any single asset."
                            ) / 100  # Convert to decimal

                            min_weight = st.slider(
                                "Minimum Weight per Asset (%)",
                                min_value=0.0,
                                max_value=100.0,
                                value=0.0,
                                step=0.1,
                                help="Set the minimum allocation percentage for any single asset."
                            ) / 100  # Convert to decimal

                            # **Added Validation Check**
                            if min_weight > max_weight:
                                st.error("Minimum weight cannot exceed maximum weight.")
                                st.stop()  # Prevent further execution

                            # **Added: Multi-Objective Selection**
                            objective_options = [
                                "Sharpe Ratio", 
                                "Minimum Variance", 
                                "Maximum Return", 
                                "Minimum Drawdown",
                                "Maximize Alpha",
                                "Minimize Beta"
                            ]
                            selected_objectives = st.multiselect(
                                "Select Optimization Objectives",
                                options=objective_options,
                                default=["Sharpe Ratio"],
                                help="Choose one or more objectives to optimize your portfolio."
                            )

                            target_return = st.number_input(
                                "Target Annual Return (%)",
                                min_value=0.0,
                                max_value=100.0,
                                value=10.0,
                                step=0.1,
                                help="Set the target annual return for portfolio optimization."
                            ) / 100  # Convert to decimal

                            submitted_optimize = st.form_submit_button("Run Optimization")

                            if submitted_optimize:
                                if not selected_objectives:
                                    st.error("Please select at least one optimization objective.")
                                objective_map = {
                                    'Sharpe Ratio': 'sharpe',
                                    'Minimum Variance': 'min_variance',
                                    'Maximum Return': 'max_return',
                                    'Minimum Drawdown': 'min_drawdown',
                                    'Maximize Alpha': 'maximize_alpha',       # Added entry
                                    'Minimize Beta': 'minimize_beta'          # Added entry
                                }
                                objectives_selected = [objective_map[obj] for obj in selected_objectives]
                                with st.spinner("Optimizing portfolio..."):
                                    # Download benchmark data
                                    benchmark_symbol = portfolio['benchmark_symbol']
                                    benchmark_data = download_data([benchmark_symbol], portfolio['start_date'], portfolio['end_date'])
                                    if benchmark_data.empty:
                                        st.error(f"Benchmark symbol '{benchmark_symbol}' does not have available data for the chosen period.")
                                        st.stop()
                                    else:
                                        benchmark_returns = benchmark_data[benchmark_symbol].pct_change().dropna()
                                        benchmark_returns = benchmark_returns.reindex(returns.index, method='ffill').dropna()

                                    # Call optimize_portfolio with benchmark_returns
                                    optimized_weights = optimize_portfolio(
                                        returns,
                                        benchmark_returns=benchmark_returns,  # Passed as a new argument
                                        objectives=objectives_selected,
                                        rf=portfolio['rf_rate'],
                                        max_weight=max_weight,
                                        min_weight=min_weight,
                                        target_return=target_return
                                    )
                                    if optimized_weights is not None:
                                        # **Added Allocation Sum Check**
                                        total_allocation = np.sum(optimized_weights)
                                        if not np.isclose(total_allocation, 1.0, atol=1e-4):
                                            st.warning(f"Optimized allocations sum to {total_allocation*100:.2f}%. Adjusting proportionally.")
                                            optimized_weights = optimized_weights / total_allocation

                                        st.success("Optimization completed successfully!")

                                        # Display Optimized Weights
                                        st.subheader("📈 Optimized Weights")
                                        weights_df = pd.DataFrame(optimized_weights, index=available_selected, columns=["Weight"])
                                        weights_df['Weight'] = weights_df['Weight'].apply(lambda x: f"{x:.2%}")
                                        st.table(weights_df)

                                        # Plot Allocation Pie Chart
                                        plot_allocation_pie(optimized_weights, available_selected, title="Optimized Portfolio Allocation")

                                        # Display Performance Metrics
                                        st.subheader("📈 Optimized Portfolio Performance Metrics")
                                        optimized_return = np.dot(optimized_weights, returns.mean()) * 252
                                        optimized_volatility = np.sqrt(np.dot(optimized_weights.T, np.dot(returns.cov() * 252, optimized_weights)))
                                        optimized_sharpe = (optimized_return - portfolio['rf_rate']) / optimized_volatility if optimized_volatility > 1e-6 else np.nan

                                        optimized_metrics = {
                                            'Expected Annual Return (%)': f"{optimized_return * 100:.2f}%",
                                            'Annualized Volatility (%)': f"{optimized_volatility * 100:.2f}%",
                                            'Sharpe Ratio': f"{optimized_sharpe:.2f}"
                                        }

                                        optimized_metrics_df = pd.DataFrame(list(optimized_metrics.items()), columns=['Metric', 'Value'])
                                        st.table(optimized_metrics_df)

                                        # Store optimized weights and metrics in session state
                                        st.session_state.backtest_results.update({
                                            'optimized_weights': optimized_weights,
                                            'optimized_return': optimized_return,
                                            'optimized_volatility': optimized_volatility,
                                            'optimized_sharpe': optimized_sharpe
                                        })
                                    else:
                                        st.error("Optimization did not return any weights.")
        else:
            st.warning("Please add at least one portfolio to optimize.")

    # ----------------------------
    # Monte Carlo Simulations Section
    # ----------------------------
    elif step == "Monte Carlo Simulations":
        st.title("📈 Monte Carlo Simulations")
        if st.session_state.portfolios:
            portfolio_names = [p['name'] for p in st.session_state.portfolios]
            selected_portfolio = st.selectbox("Select Portfolio for Monte Carlo Simulations", portfolio_names)
            if selected_portfolio:
                portfolio = next(p for p in st.session_state.portfolios if p['name'] == selected_portfolio)
                price_data = download_data(
                    portfolio['selected'],
                    portfolio['start_date'],
                    portfolio['end_date']
                )
                if price_data.empty:
                    st.error("No price data available for the selected portfolio.")
                else:
                    available_selected = [ticker for ticker in portfolio['selected'] if ticker in price_data.columns]
                    missing_selected = list(set(portfolio['selected']) - set(available_selected))
                    if missing_selected:
                        st.warning(f"Excluded tickers with no data: {', '.join(missing_selected)}")
                    
                    if not available_selected:
                        st.error("No selected tickers have available data for the chosen date range.")
                    else:
                        returns = price_data[available_selected].pct_change().dropna()
                        
                        # Organize simulation settings and results into tabs
                        simulation_tabs = st.tabs(["🔧 Settings", "📊 Results"])
                        
                        with simulation_tabs[0]:
                            st.subheader("🔍 Select Simulation Parameters")
                            with st.form("mc_simulation_form"):
                                # Simulation Settings Organized in Columns
                                sim_col1, sim_col2 = st.columns(2)
                                
                                with sim_col1:
                                    num_simulations = st.number_input("Number of Simulations", min_value=1, max_value=10000, value=1000)
                                    periods = st.number_input("Number of Periods", min_value=1, max_value=252, value=252)
                                
                                with sim_col2:
                                    return_distribution = st.selectbox(
                                        "Return Distribution",
                                        options=["normal", "log-normal"],
                                        index=0,
                                        help="Select the return distribution to use in simulations."
                                    )
                                    enable_mean_reversion = st.checkbox("Enable Mean Reversion")
                                    if enable_mean_reversion:
                                        mean_reversion_speed = st.slider("Mean Reversion Speed", min_value=0.0, max_value=1.0, value=0.1)
                                        long_term_mean_input = st.number_input("Long-term Mean Return (%)", value=0.0) / 100
                                        long_term_mean = np.full(len(available_selected), long_term_mean_input / 252)
                                    else:
                                        mean_reversion_speed = 0.0
                                        long_term_mean = None
                                
                                # Additional Simulation Settings
                                st.subheader("⚙️ Additional Settings")
                                time_varying_vol = st.checkbox("Enable Time-Varying Volatility")
                                if time_varying_vol:
                                    vol_change_rate = st.number_input("Volatility Change Rate per Period", value=0.0) / np.sqrt(252)
                                else:
                                    vol_change_rate = 0.0
                                
                                # Stress Testing Settings
                                st.subheader("⚠️ Stress Testing")
                                enable_stress_testing = st.checkbox("Enable Stress Testing")
                                stress_shocks = {}
                                if enable_stress_testing:
                                    selected_stress_assets = st.multiselect(
                                        "Select Assets to Apply Stress Shocks",
                                        options=available_selected,
                                        help="Choose assets to apply stress shocks."
                                    )
                                    for ticker in selected_stress_assets:
                                        shock = st.number_input(
                                            f"Shock to {ticker} (%)",
                                            min_value=-100.0, max_value=100.0,
                                            value=0.0,
                                            step=0.1,
                                            key=f"shock_{ticker}"
                                        ) / 100  # Convert to decimal
                                        stress_shocks[ticker] = shock
                                
                                submitted_simulation = st.form_submit_button("Run Simulations")
                            
                            if submitted_simulation:
                                with st.spinner("Running Monte Carlo simulations..."):
                                    simulated_returns = monte_carlo_simulation(
                                        returns, num_simulations=num_simulations, periods=periods, 
                                        mean_returns=returns.mean(), cov_matrix=returns.cov(),
                                        mean_reversion=enable_mean_reversion, 
                                        mean_reversion_speed=mean_reversion_speed, 
                                        long_term_mean=long_term_mean,
                                        time_varying_vol=time_varying_vol, 
                                        vol_change_rate=vol_change_rate,
                                        stress_shocks=stress_shocks, 
                                        stress_period=1,  # Example value
                                        return_distribution=return_distribution
                                    )
                                    st.session_state.simulated_returns = simulated_returns
                                    st.success("Simulations completed successfully!")
                        
                        with simulation_tabs[1]:
                            if 'simulated_returns' in st.session_state:
                                simulated_returns = st.session_state.simulated_returns
                                st.subheader("📊 Simulation Results")
                                
                                # Display Basic Statistics
                                st.write("### Descriptive Statistics")
                                st.write(pd.Series(simulated_returns).describe())
                                
                                # Histogram of Simulated Returns
                                st.write("### Returns Distribution")
                                fig_hist = px.histogram(
                                    simulated_returns, 
                                    nbins=50, 
                                    title='Simulated Returns Distribution',
                                    labels={'value': 'Simulated Returns', 'count': 'Frequency'},
                                    template='plotly_dark'
                                )
                                st.plotly_chart(fig_hist, use_container_width=True)
                                
                                # Cumulative Distribution Function (CDF)
                                st.write("### Cumulative Distribution Function (CDF)")
                                fig_cdf = px.ecdf(
                                    simulated_returns, 
                                    title='Cumulative Distribution of Simulated Returns',
                                    labels={'value': 'Simulated Returns', 'cumcount': 'CDF'},
                                    template='plotly_dark'
                                )
                                st.plotly_chart(fig_cdf, use_container_width=True)
                                
                                # Box Plot for Simulated Returns
                                st.write("### Box Plot of Simulated Returns")
                                fig_box = px.box(
                                    pd.DataFrame(simulated_returns, columns=['Returns']),
                                    y='Returns',
                                    title='Box Plot of Simulated Returns',
                                    template='plotly_dark'
                                )
                                st.plotly_chart(fig_box, use_container_width=True)
                                
                                # Summary Statistics Table
                                st.write("### Summary Statistics")
                                summary_stats = pd.DataFrame({
                                    'Statistic': ['Mean', 'Median', 'Standard Deviation', 'Minimum', 'Maximum'],
                                    'Value': [
                                        f"{np.mean(simulated_returns):.4f}",
                                        f"{np.median(simulated_returns):.4f}",
                                        f"{np.std(simulated_returns):.4f}",
                                        f"{np.min(simulated_returns):.4f}",
                                        f"{np.max(simulated_returns):.4f}"
                                    ]
                                })
                                st.table(summary_stats)
                                st.markdown("""
                                **Interpretation:**
                                - **Descriptive Statistics:** Provides an overview of the distribution of simulated portfolio returns.
                                - **Returns Distribution Histogram:** Visualizes the frequency of different return outcomes.
                                - **Cumulative Distribution Function (CDF):** Shows the probability that a return is less than or equal to a particular value.
                                - **Box Plot:** Highlights the median, quartiles, and potential outliers in the return distribution.
                                - **Summary Statistics Table:** Summarizes key metrics from the simulation runs.
                                """)
                            else:
                                st.info("Run simulations to view results.")
        else:
            st.warning("Please add at least one portfolio to run Monte Carlo simulations.")

    # ----------------------------
    # Risk Analysis Section
    # ----------------------------
    elif step == "Risk Analysis":
        st.title("⚠️ Risk Analysis")
        if st.session_state.portfolios:
            portfolio_names = [p['name'] for p in st.session_state.portfolios]
            selected_portfolio = st.selectbox("Select Portfolio for Risk Analysis", portfolio_names)
            if selected_portfolio:
                portfolio = next(p for p in st.session_state.portfolios if p['name'] == selected_portfolio)
                price_data = download_data(
                    portfolio['selected'],
                    portfolio['start_date'],
                    portfolio['end_date']
                )
                if price_data.empty:
                    st.error("No price data available for the selected portfolio.")
                else:
                    available_selected = [ticker for ticker in portfolio['selected'] if ticker in price_data.columns]
                    missing_selected = list(set(portfolio['selected']).difference(set(available_selected)))
                    if missing_selected:
                        st.warning(f"Excluded tickers with no data: {', '.join(missing_selected)}")
                    if not available_selected:
                        st.error("No selected tickers have available data for the chosen date range.")
                    else:
                        returns = price_data[available_selected].pct_change().dropna()
                        
                        # Organize risk analysis settings and outputs into tabs
                        risk_tabs = st.tabs(["📉 Risk Metrics", "📈 Visualizations", "🛠️ Sensitivity Analysis"])
                        
                        with risk_tabs[0]:
                            st.subheader("🔍 Risk Metrics")
                            # Start of the form
                            with st.form("risk_analysis_form"):
                                st.subheader("Risk Analysis Parameters")
                                confidence_level = st.slider("Confidence Level", min_value=0.01, max_value=0.99, value=0.95)
                                
                                # Updated Stress Testing Inputs with Asset Selection
                                st.subheader("⚠️ Enhanced Stress Testing")
                                enable_stress_testing = st.checkbox("Enable Enhanced Stress Testing")
                                stress_shocks = {}

                                if enable_stress_testing:
                                    selected_stress_assets = st.multiselect(
                                        "Select Assets to Stress Test",
                                        options=available_selected,
                                        help="Choose multiple assets to apply simultaneous stress shocks."
                                    )
                                    for ticker in selected_stress_assets:
                                        shock = st.number_input(
                                            f"Shock to {ticker} (%)",
                                            min_value=-100.0, max_value=100.0,
                                            value=0.0,
                                            step=0.1,
                                            key=f"enhanced_shock_{ticker}"
                                        ) / 100  # Convert to decimal
                                        stress_shocks[ticker] = shock
                                    if not selected_stress_assets:
                                        st.warning("No assets selected for stress testing.")
                                
                                # Collect inputs for Sensitivity Analysis
                                st.subheader("🔎 Sensitivity Analysis")
                                enable_sensitivity_analysis = st.checkbox("Enable Sensitivity Analysis")
                                sensitivity_adjustments = {}
                                if enable_sensitivity_analysis:
                                    interest_rate_change = st.number_input("Change in Interest Rates (bps)", value=0.0) / 10000
                                    inflation_rate_change = st.number_input("Change in Inflation Rates (bps)", value=0.0) / 10000
                                    for ticker in available_selected:
                                        interest_sensitivity = st.number_input(
                                            f"Interest Rate Sensitivity for {ticker}",
                                            value=1.0,
                                            key=f"interest_sens_{ticker}"
                                        )
                                        inflation_sensitivity = st.number_input(
                                            f"Inflation Rate Sensitivity for {ticker}",
                                            value=1.0,
                                            key=f"inflation_sens_{ticker}"
                                        )
                                        adjustment = (interest_sensitivity * interest_rate_change +
                                                      inflation_sensitivity * inflation_rate_change)
                                        sensitivity_adjustments[ticker] = adjustment
                                
                                # Add Cross-Asset Sensitivity Analysis Inputs
                                st.subheader("🔄 Cross-Asset Sensitivity Analysis")
                                enable_cross_asset_sensitivity_analysis = st.checkbox("Enable Cross-Asset Sensitivity Analysis")
                                cross_asset_sensitivity_adjustments = {}

                                if enable_cross_asset_sensitivity_analysis:
                                    st.markdown("### Currency Sensitivity")
                                    currency_rate_change = st.number_input(
                                        "Change in Currency Exchange Rate (%)",
                                        min_value=-50.0, max_value=50.0,
                                        value=0.0,
                                        step=0.1,
                                        help="Enter the percentage change in currency exchange rates."
                                    ) / 100  # Convert to decimal

                                    st.markdown("### Commodity Price Sensitivity")
                                    commodity_price_change = st.number_input(
                                        "Change in Commodity Prices (%)",
                                        min_value=-100.0, max_value=100.0,
                                        value=0.0,
                                        step=0.1,
                                        help="Enter the percentage change in commodity prices."
                                    ) / 100  # Convert to decimal

                                    for ticker in available_selected:
                                        sensitivity = st.number_input(
                                            f"Sensitivity for {ticker} to Currency and Commodity Changes (%)",
                                            min_value=-10.0, max_value=10.0,
                                            value=0.0,
                                            step=0.1,
                                            key=f"sensitivity_{ticker}"
                                        ) / 100  # Convert to decimal
                                        # Combine currency and commodity changes
                                        adjustment = sensitivity * (currency_rate_change + commodity_price_change)
                                        cross_asset_sensitivity_adjustments[ticker] = adjustment
                                
                                # Submit button for the form
                                submitted = st.form_submit_button("Calculate Risk Metrics")
                            
                            if submitted:
                                with st.spinner("Calculating risk metrics..."):
                                    # Calculate portfolio returns as weighted average
                                    weights = np.array(portfolio['allocations']) / 100
                                    portfolio_returns = returns.copy()
                                    
                                    # Apply stress shocks if enabled
                                    if enable_stress_testing:
                                        for ticker in stress_shocks:
                                            portfolio_returns[ticker] += stress_shocks[ticker]
                                    
                                    # Apply sensitivity adjustments if enabled
                                    if enable_sensitivity_analysis:
                                        for ticker in sensitivity_adjustments:
                                            portfolio_returns[ticker] += sensitivity_adjustments[ticker]
                                    
                                    # Apply cross-asset sensitivity adjustments if enabled
                                    if enable_cross_asset_sensitivity_analysis:
                                        for ticker in cross_asset_sensitivity_adjustments:
                                            portfolio_returns[ticker] += cross_asset_sensitivity_adjustments[ticker]
                                    
                                    # Ensure no extreme negative returns after adjustments
                                    portfolio_returns = portfolio_returns.clip(lower=-1.0)
                                    
                                    # Calculate weighted portfolio returns
                                    portfolio_returns = portfolio_returns.dot(weights)
                                    st.session_state.portfolio_returns = portfolio_returns
                                    portfolio_skewness = skew(portfolio_returns)
                                    portfolio_kurtosis = kurtosis(portfolio_returns)
                                    
                                    # Calculate VaR and CVaR
                                    var = calculate_var(portfolio_returns, confidence_level)
                                    cvar = calculate_cvar(portfolio_returns, confidence_level)
                                    st.session_state.var = var
                                    st.session_state.cvar = cvar
                                    
                                    # Risk Metrics Summary Table
                                    st.markdown("### 📈 Risk Metrics Summary")
                                    cum_returns = (1 + portfolio_returns).cumprod()
                                    risk_metrics = {
                                        'Value at Risk (VaR)': f"{var:.2f}",
                                        'Conditional Value at Risk (CVaR)': f"{cvar:.2f}",
                                        'Skewness': f"{portfolio_skewness:.2f}",
                                        'Kurtosis': f"{portfolio_kurtosis:.2f}",
                                        'Annualized Volatility (%)': f"{portfolio_returns.std() * 100 * np.sqrt(252):.2f}%",
                                        'Maximum Drawdown (%)': f"{drawdown(cum_returns) * 100:.2f}%"
                                    }
                                    risk_df = pd.DataFrame(list(risk_metrics.items()), columns=['Metric', 'Value'])
                                    st.table(risk_df)
                                    st.markdown("""
                                    **Tooltip:**
                                    - **VaR:** Measures the worst expected loss under normal market conditions over a specific time period at a given confidence level.
                                    - **CVaR:** Provides the average loss exceeding the VaR, offering insight into tail risk.
                                    - **Annualized Volatility:** Indicates the degree of variation in portfolio returns, representing risk.
                                    - **Maximum Drawdown:** Shows the largest peak-to-trough decline, indicating potential risk exposure.
                                    """)
                                    # Real-Time Risk Warning
                                    high_var_threshold = -0.10  # Example: VaR worse than -10%
                                    if var <= high_var_threshold:
                                        st.warning(f"⚠️ High VaR! Your portfolio has a {confidence_level*100:.0f}% chance of losing over {abs(var)*100:.2f}% in the next year.")
                                    else:
                                        st.success(f"✅ VaR is within acceptable limits: {var*100:.2f}%")

                        with risk_tabs[1]:
                            st.subheader("📊 Visualizations")

                            var = st.session_state.get('var', None)
                            cvar = st.session_state.get('cvar', None)
                            portfolio_returns_local = st.session_state.get('portfolio_returns', None)

                            if var is not None and cvar is not None and portfolio_returns_local is not None:
                                
                                if portfolio_returns_local.empty:
                                    st.error("Portfolio returns data is unavailable. Please ensure calculations are correct.")
                                    st.stop()
                                
                                # Value at Risk (VaR) Plot (Assuming you want to show cumulative returns over time)
                                st.subheader("Value at Risk (VaR) Plot")
                                fig_var = px.line(
                                    portfolio_returns_local.cumsum(),
                                    title='Value at Risk (VaR) Over Time',
                                    labels={'value': 'Cumulative Returns', 'index': 'Date'},
                                    template='plotly_dark'
                                )
                                fig_var.add_hline(y=var, line_dash="dash", line_color="red", annotation_text=f"VaR: {var:.2f}", annotation_position="bottom right")
                                st.plotly_chart(fig_var, use_container_width=True)

                                # Conditional Value at Risk (CVaR) Plot
                                st.subheader("Conditional Value at Risk (CVaR) Plot")
                                fig_cvar = px.line(
                                    portfolio_returns_local.cumsum(),
                                    title='Conditional Value at Risk (CVaR) Over Time',
                                    labels={'value': 'Cumulative Returns', 'index': 'Date'},
                                    template='plotly_dark'
                                )
                                fig_cvar.add_hline(y=cvar, line_dash="dash", line_color="blue", annotation_text=f"CVaR: {cvar:.2f}", annotation_position="bottom right")
                                st.plotly_chart(fig_cvar, use_container_width=True)

                                # VaR and CVaR Distribution Plot
                                st.subheader("📊 VaR and CVaR Distribution")
                                fig_var_cvar_dist = px.histogram(
                                    portfolio_returns_local,
                                    nbins=50,
                                    title='Returns Distribution with VaR and CVaR',
                                    labels={'value': 'Returns', 'count': 'Frequency'},
                                    template='plotly_dark'
                                )
                                fig_var_cvar_dist.add_vline(x=var, line_dash="dash", line_color="red", annotation_text=f"VaR: {var:.2f}", annotation_position="top left")
                                fig_var_cvar_dist.add_vline(x=cvar, line_dash="dash", line_color="blue", annotation_text=f"CVaR: {cvar:.2f}", annotation_position="top left")
                                st.plotly_chart(fig_var_cvar_dist, use_container_width=True)
                                st.markdown("""
                                **Interpretation:**
                                - **VaR (Value at Risk):** Indicates the maximum expected loss over a specified period at a given confidence level.
                                - **CVaR (Conditional Value at Risk):** Represents the average loss exceeding the VaR, providing insights into tail risk.
                                - **Annualized Volatility:** Measures the variability of portfolio returns, indicating risk.
                                - **Maximum Drawdown:** Shows the largest peak-to-trough decline, reflecting potential risk exposure.
                                """)
                                
                                st.markdown("""
                                **VaR and CVaR Plots:**
                                - **VaR Plot:** Highlights the point below which a certain percentage of losses fall.
                                - **CVaR Plot:** Shows the average loss in scenarios where losses exceed the VaR.
                                """)

                        with risk_tabs[2]:
                            st.subheader("🛠️ Sensitivity Analysis")
                            # Only plot the Adjusted Returns Distribution if stress testing or sensitivity analysis is enabled
                            if enable_stress_testing or enable_sensitivity_analysis or enable_cross_asset_sensitivity_analysis:
                                st.subheader("📊 Adjusted Returns Distribution")
                                fig_adjusted_returns = px.histogram(
                                    portfolio_returns,
                                    nbins=50,
                                    title='Adjusted Portfolio Returns Distribution',
                                    labels={'value': 'Returns', 'count': 'Frequency'},
                                    template='plotly_dark'
                                )
                                st.plotly_chart(fig_adjusted_returns, use_container_width=True)
                                st.markdown("**Interpretation:** This histogram shows how the portfolio returns distribution changes after applying stress shocks and sensitivity adjustments.")

                            # Risk vs Return Scatter Plot
                            st.subheader("📊 Risk vs Return Scatter Plot")
                            risk = returns.std() * np.sqrt(252)
                            ret = returns.mean() * 252

                            if isinstance(risk, (float, int)):
                                scatter_df = pd.DataFrame({
                                    'Risk': [risk],
                                    'Return': [ret]
                                })
                            else:
                                scatter_df = pd.DataFrame({
                                    'Risk': risk,
                                    'Return': ret
                                })

                            if isinstance(returns, pd.DataFrame) and returns.shape[1] > 1:
                                scatter_df['Asset'] = available_selected

                            fig_risk_return = plot_risk_return_attribution(returns, weights)
                            st.plotly_chart(fig_risk_return, use_container_width=True)
        else:
            st.warning("Please add at least one portfolio to run risk analysis.")

tell me roughly how many token on this code, also help me further modular it, as it is still too long, if find anything related can also tell me to put back into utils.py plots.py metrics.py, or give me new module(tell me to open a new py file), give me full code if so