Risk Factor Attribution Analysis (only work in the portfolio vs portfolio mode, when switched to benchmark one it becomes always unavailable)
Benchmark risk factor attribution is unavailable. 

Final Score (improve the score system)
Portfolio: 100.00 / 100
Benchmark: 20.11 / 100
Skipped metrics due to invalid values: Information Ratio, Calmar Ratio, Treynor Ratio, Alpha (annualized), Modigliani–Modigliani Measure, Upside Capture Ratio (improve it)

Safe Withdrawal Rate and Perpetual Withdrawal Rate use 2 decimal points

fix the above and improve it if u can

# utils.py
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from pandas_datareader import data as pdr
import yfinance as yf
import numpy as np
import pandas as pd
import streamlit as st

frequency_mapping = {
    "Daily": "D",
    "Weekly": "W",
    "Monthly": "M",
    "Quarterly": "Q",
    "Yearly": "Y"
}

def calculate_cvar(portfolio_returns, confidence_level=0.95):
    var = calculate_var(portfolio_returns, confidence_level)
    portfolio_returns = np.array(portfolio_returns)
    return portfolio_returns[portfolio_returns <= var].mean()
    
def calculate_var(portfolio_returns, confidence_level=0.95):
    if not isinstance(portfolio_returns, np.ndarray):
        portfolio_returns = np.array(portfolio_returns)
    return np.percentile(portfolio_returns, (1 - confidence_level) * 100)

def get_company_name(ticker_df, ticker):
    """
    Retrieve the company name based on the ticker symbol.
    """
    match = ticker_df[ticker_df['Ticker'] == ticker]
    if not match.empty:
        return match.iloc[0]['Company Name']
    return "Unknown"

def format_asset_option(ticker, company_name):
    """
    Format the asset option string for display.
    """
    return f"{ticker} - {company_name}"

@st.cache_data(show_spinner=False)
def get_tickers():
    """
    Fetch and combine S&P 500 and NASDAQ-100 tickers from Wikipedia.
    """
    try:
        # Fetch S&P 500 companies
        sp500_url = 'https://en.wikipedia.org/wiki/List_of_S%26P_500_companies'
        sp500_response = requests.get(sp500_url, verify=False)
        sp500_table = pd.read_html(sp500_response.text)[0]
        sp500 = sp500_table[['Symbol', 'Security']].rename(columns={'Symbol': 'Ticker', 'Security': 'Company Name'})
        sp500['Ticker'] = sp500['Ticker'].str.replace('.', '-', regex=False)

        # Fetch NASDAQ-100 companies
        nasdaq100_url = 'https://en.wikipedia.org/wiki/NASDAQ-100'
        nasdaq100_response = requests.get(nasdaq100_url, verify=False)
        nasdaq100_tables = pd.read_html(nasdaq100_response.text)
        
        nasdaq100 = pd.DataFrame()
        for table in nasdaq100_tables:
            if 'Ticker' in table.columns and 'Company' in table.columns:
                nasdaq100 = table[['Ticker', 'Company']].rename(columns={'Ticker': 'Ticker', 'Company': 'Company Name'})
                nasdaq100['Ticker'] = nasdaq100['Ticker'].str.replace('.', '-', regex=False)
                break

        combined = pd.concat([sp500, nasdaq100], ignore_index=True)
        combined = combined.drop_duplicates(subset=['Ticker'])
        return combined.sort_values('Ticker').reset_index(drop=True)
    except Exception as e:
        st.error(f"Error fetching tickers: {e}")
        return pd.DataFrame(columns=['Ticker', 'Company Name'])

@st.cache_data(show_spinner=False)
def download_data(tickers, start, end, retries=3, backoff_factor=0.3):
    """
    Download historical price data for given tickers using yfinance with retry strategy.
    """
    try:
        # Configure retry strategy for requests
        session = requests.Session()
        retry = Retry(
            total=retries,
            read=retries,
            connect=retries,
            backoff_factor=backoff_factor,
            status_forcelist=(500, 502, 504),
        )
        adapter = HTTPAdapter(max_retries=retry)
        session.mount('http://', adapter)
        session.mount('https://', adapter)
        
        # Attempt to download data
        data = yf.download(tickers, start=start, end=end, progress=False, session=session)['Adj Close']
        
        # Handle potential empty data
        if isinstance(data, pd.Series):
            data = data.to_frame()
        if data.empty:
            st.warning("No price data available for the selected portfolio. Please check the ticker symbols and date range.")
            return pd.DataFrame()
        if data.index.tz is not None:
            data.index = data.index.tz_localize(None)
        
        # Fill missing data
        data = data.fillna(method='ffill').fillna(method='bfill')
        if data.isnull().values.any():
            st.warning("Data contains missing values after filling. Some calculations may be affected.")
        return data
    except Exception as e:
        st.error(f"Error downloading data: {e}")
        return pd.DataFrame()

def get_common_benchmarks(selected_tickers):
    """
    Suggest common benchmarks based on the selected tickers.
    """
    # Define benchmark suggestions based on asset sectors or indices
    sp500 = {"AAPL", "MSFT", "GOOGL", "AMZN", "META", "TSLA"}  # Example S&P 500 tech companies
    nasdaq_tech = {"AAPL", "MSFT", "GOOGL", "AMZN", "META", "TSLA", "NVDA", "AMD"}

    selected_set = set(selected_tickers)

    if selected_set.issubset(sp500):
        return {
            "S&P 500": "^GSPC",
            "Dow Jones Industrial Average": "^DJI",
            "Russell 2000": "^RUT",
            "Custom": "CUSTOM"
        }
    elif selected_set.issubset(nasdaq_tech):
        return {
            "NASDAQ Composite": "^IXIC",
            "QQQ (Invesco QQQ ETF)": "QQQ",
            "Custom": "CUSTOM"
        }
    else:
        return {
            "S&P 500": "^GSPC",
            "NASDAQ Composite": "^IXIC",
            "Dow Jones Industrial Average": "^DJI",
            "Russell 2000": "^RUT",
            "Custom": "CUSTOM"
        }

def generate_recommendations(allocations, available_selected):
    """
    Generate basic portfolio recommendations based on current allocations.
    """
    recommendations = []
    max_alloc = max(allocations)
    min_alloc = min(allocations)
    total_alloc = sum(allocations)
    
    if max_alloc > 40.0:
        idx = allocations.index(max_alloc)
        ticker = available_selected[idx]
        recommendations.append(f"🔹 Consider reducing the allocation to **{ticker}** since it constitutes **{max_alloc:.2f}%** of your portfolio.")
    
    if min_alloc < 5.0:
        idx = allocations.index(min_alloc)
        ticker = available_selected[idx]
        recommendations.append(f"🔸 Consider increasing the allocation to **{ticker}** to at least **5.00%** for better diversification.")
    
    if total_alloc < 100.0:
        recommendations.append("🔹 Consider allocating the remaining funds to additional assets to reach a total of **100%**.")
    
    return recommendations

def fetch_fama_french_factors(start_date, end_date):
    """
    Fetch daily Fama-French factors from a data source.
    """
    try:
        # Fetch daily Fama-French factors
        ff_data = pdr.get_data_famafrench('F-F_Research_Data_Factors_daily', start=start_date, end=end_date)
        if not ff_data:
            st.error("Fama-French data not found.")
            return pd.DataFrame()
        
        factors = ff_data[0]
        factors = factors.rename(columns=lambda x: x.strip())  # Remove any leading/trailing spaces
        factors /= 100  # Convert percentage returns to decimal format
        
        # Fill missing data by forward and backward filling
        factors = factors.fillna(method='ffill').fillna(method='bfill')
        
        # Check for necessary columns
        required_columns = {'Mkt-RF', 'SMB', 'HML', 'RF'}
        if not required_columns.issubset(factors.columns):
            missing = required_columns - set(factors.columns)
            st.error(f"Missing factor columns: {', '.join(missing)}")
            return pd.DataFrame()
        
        return factors
    except Exception as e:
        st.error(f"Error fetching factor data: {e}")
        return pd.DataFrame()
        
def generate_portfolio(returns, rf):
    try:
        weights = np.random.random(returns.shape[1])
        weights /= np.sum(weights)
        portfolio_return = np.dot(weights, returns.mean()) * 252
        portfolio_volatility = np.sqrt(np.dot(weights.T, np.dot(returns.cov() * 252, weights)))
        sharpe_ratio = (portfolio_return - rf) / portfolio_volatility if portfolio_volatility > 0 else 0
        return portfolio_volatility, portfolio_return, sharpe_ratio
    except Exception as e:
        st.error(f"Error generating portfolio: {e}")
        return np.nan, np.nan, np.nan  # Return NaNs to indicate failure
        
def monte_carlo_simulation(returns, num_simulations=1000, periods=252, mean_returns=None, cov_matrix=None,
                           mean_reversion=False, mean_reversion_speed=0.1, long_term_mean=None,
                           time_varying_vol=False, vol_change_rate=0.0,
                           stress_shocks=None, stress_period=None,
                           return_distribution='normal'):
    if mean_returns is None:
        mean_returns = returns.mean()
    if cov_matrix is None:
        cov_matrix = returns.cov()

    num_assets = returns.shape[1]
    available_selected = returns.columns.tolist()

    def simulate():
        weights = np.random.random(num_assets)
        weights /= np.sum(weights)
        simulated_prices = [1]  # Start with initial price of 1
        current_mean = mean_returns.copy()
        current_vol = np.sqrt(np.diag(cov_matrix))
        for t in range(periods):
            if mean_reversion:
                current_mean += mean_reversion_speed * (long_term_mean - current_mean)
            if time_varying_vol:
                current_vol += vol_change_rate
            adjusted_cov_matrix = np.outer(current_vol, current_vol) * np.corrcoef(returns.T)
            if return_distribution == 'log-normal':
                simulated_returns = np.random.lognormal(mean=np.log(1 + current_mean), sigma=current_vol) - 1
            else:
                simulated_returns = np.random.multivariate_normal(current_mean, adjusted_cov_matrix)
            
            # Apply stress shocks if applicable
            if stress_shocks and t == stress_period:
                for idx, ticker in enumerate(available_selected):
                    simulated_returns[idx] += stress_shocks.get(ticker, 0)
            
            portfolio_return = np.dot(simulated_returns, weights)
            simulated_prices.append(simulated_prices[-1] * (1 + portfolio_return))
        cumulative_return = simulated_prices[-1] - 1
        return cumulative_return

    portfolio_returns = Parallel(n_jobs=-1)(
        delayed(simulate)() for _ in range(num_simulations)
    )

    return np.array(portfolio_returns)
    
def backtest(weights, prices, rebalance_freq='M', broker_fee=0.0, debug=False):
    try:
        # Calculate returns and drop any NaN values
        returns = prices.pct_change().dropna()
        if returns.empty:
            st.error("Returns data is empty after calculating percentage changes.")
            return pd.Series(dtype=float), pd.Series(dtype=float)
        
        # Calculate portfolio returns
        portfolio_returns = returns.dot(weights)
        if not isinstance(portfolio_returns, pd.Series):
            portfolio_returns = portfolio_returns.squeeze()

        # Identify rebalancing dates
        if rebalance_freq == 'D':
            rebalance_dates = returns.index
        else:
            rebalance_dates = returns.resample(rebalance_freq).last().dropna().index

        # Debug: Show rebalancing dates
        if debug:
            st.write(f"Rebalancing Dates: {rebalance_dates.tolist()}")

        # Ensure rebalance_dates are in the portfolio_returns index
        valid_rebalance_dates = rebalance_dates.intersection(portfolio_returns.index)
        if debug:
            st.write(f"Valid Rebalancing Dates: {valid_rebalance_dates.tolist()}")

        # Apply broker fees on rebalancing dates
        if not valid_rebalance_dates.empty:
            portfolio_returns.loc[valid_rebalance_dates] -= broker_fee / 100  # Convert to decimal
        else:
            st.warning("No valid rebalancing dates found within the returns data.")

        # Calculate cumulative returns
        cum_returns = (1 + portfolio_returns).cumprod()
        if cum_returns.empty:
            st.error("Cumulative returns are empty. Check the data and allocations.")
            return pd.Series(dtype=float), pd.Series(dtype=float)

        # Debug: Show cumulative returns
        if debug:
            st.write("Cumulative Returns:")
            st.write(cum_returns)

        return portfolio_returns, cum_returns
    except Exception as e:
        st.error(f"Error during backtesting: {e}")
        return pd.Series(dtype=float), pd.Series(dtype=float)
    
def optimize_portfolio(
    returns, 
    benchmark_returns=None, 
    objectives=['sharpe'], 
    rf=0.02, 
    max_weight=1.0, 
    min_weight=0.0, 
    target_return=None
):
    """
    Optimize a portfolio based on specified objectives.

    Parameters:
    - returns (pd.DataFrame or np.ndarray): Historical returns of assets.
    - benchmark_returns (pd.Series or np.ndarray, optional): Returns of the benchmark index.
    - objectives (list of str): Objectives to optimize. 
        Options include 'sharpe', 'min_variance', 'max_return', 'min_drawdown', 'maximize_alpha', 'minimize_beta'.
    - rf (float): Risk-free rate for alpha calculation.
    - max_weight (float): Maximum weight per asset.
    - min_weight (float): Minimum weight per asset.
    - target_return (float, optional): Target return for the portfolio.

    Returns:
    - np.ndarray: Optimized asset weights or None if optimization fails.
    """
    
    # Define helper functions (ensure these are implemented elsewhere)
    def calculate_beta(portfolio_returns, benchmark_returns):
        covariance = np.cov(portfolio_returns, benchmark_returns)[0, 1]
        variance = np.var(benchmark_returns)
        return covariance / variance if variance != 0 else 0

    def calculate_var(portfolio_returns, confidence_level=0.95):
        if not isinstance(portfolio_returns, np.ndarray):
            portfolio_returns = np.array(portfolio_returns)
        return np.percentile(portfolio_returns, (1 - confidence_level) * 100)

    def calculate_cvar(portfolio_returns, confidence_level=0.95):
        var = calculate_var(portfolio_returns, confidence_level)
        portfolio_returns = np.array(portfolio_returns)
        return portfolio_returns[portfolio_returns <= var].mean()

    # Define individual objective functions
    def sharpe_ratio(weights):
        portfolio_return = np.dot(returns.mean(), weights) * 252
        portfolio_volatility = np.sqrt(np.dot(weights.T, np.dot(returns.cov() * 252, weights)))
        return -portfolio_return / portfolio_volatility if portfolio_volatility > 1e-6 else 0  # Negative for maximization

    def min_variance(weights):
        return np.dot(weights.T, np.dot(returns.cov() * 252, weights))

    def max_return(weights):
        return -np.dot(returns.mean(), weights) * 252  # Negative for maximization

    def min_drawdown(weights):
        # Simplistic drawdown minimization using expected shortfall (CVaR)
        portfolio_returns = returns.dot(weights)
        cvar = calculate_cvar(portfolio_returns, 0.95)
        return cvar  # Minimizing CVaR as a proxy for drawdown

    # Initialize list of objective functions
    objective_functions = []
    
    # Iterate through selected objectives and append corresponding functions
    for obj in objectives:
        if obj == 'sharpe':
            objective_functions.append(sharpe_ratio)
        elif obj == 'min_variance':
            objective_functions.append(min_variance)
        elif obj == 'max_return':
            objective_functions.append(max_return)
        elif obj == 'min_drawdown':
            objective_functions.append(min_drawdown)
        elif obj == 'maximize_alpha':
            if benchmark_returns is None:
                st.error("benchmark_returns must be provided for 'maximize_alpha' objective.")
                return None
            
            benchmark_return = benchmark_returns.mean() * 252  # Assuming daily returns annualized
            def maximize_alpha(weights):
                portfolio_return = np.dot(returns.mean(), weights) * 252
                portfolio_volatility = np.sqrt(np.dot(weights.T, np.dot(returns.cov() * 252, weights)))
                portfolio_returns = returns.dot(weights)
                beta = calculate_beta(portfolio_returns, benchmark_returns)
                alpha = portfolio_return - (rf + beta * (benchmark_return - rf))
                return -alpha  # Negative for maximization

            objective_functions.append(maximize_alpha)
        elif obj == 'minimize_beta':
            if benchmark_returns is None:
                st.error("benchmark_returns must be provided for 'minimize_beta' objective.")
                return None
            
            def minimize_beta(weights):
                portfolio_returns = returns.dot(weights)
                beta = calculate_beta(portfolio_returns, benchmark_returns)
                return beta

            objective_functions.append(minimize_beta)
        else:
            st.error(f"Invalid optimization objective: {obj}")
            return None

    # Composite objective function: weighted sum of individual objectives
    def composite_objective(weights):
        return sum(fn(weights) for fn in objective_functions)

    # Define constraints
    constraints = [
        {'type': 'eq', 'fun': lambda x: np.sum(x) - 1},
    ]

    if target_return is not None:
        def target_return_constraint(x):
            return np.dot(x, returns.mean()) * 252 - target_return
        constraints.append({
            'type': 'ineq',
            'fun': target_return_constraint
        })

    # Define bounds for weights
    bounds = tuple((min_weight, max_weight) for _ in range(returns.shape[1]))
    
    # Initial guess (equally distributed weights)
    initial_guess = np.array([1.0 / returns.shape[1]] * returns.shape[1])

    # Perform optimization
    result = minimize(
        composite_objective, 
        initial_guess, 
        method='SLSQP', 
        bounds=bounds, 
        constraints=constraints
    )
    
    if result.success:
        optimized_weights = result.x
        portfolio_volatility = np.sqrt(np.dot(optimized_weights.T, np.dot(returns.cov() * 252, optimized_weights)))
        
        if portfolio_volatility <= 1e-6:
            st.error("Optimized portfolio has near-zero volatility. Optimization constraints may be too restrictive.")
            return None
        
        return optimized_weights
    else:
        st.error("Optimization failed. Try adjusting your constraints or target return.")
        return None

# plots.py
import plotly.graph_objects as go
import plotly.express as px
import pandas as pd
import streamlit as st
import numpy as np
from plotly.subplots import make_subplots

def plot_cagr_over_time(cum_returns, time_frames=['Weekly', 'Monthly', 'Quarterly', 'Annually'], label="Portfolio"):
    """
    Plot the Compound Annual Growth Rate (CAGR) over multiple time frames for a single portfolio.
    
    Parameters:
    - cum_returns (pd.Series): Cumulative returns of the portfolio.
    - time_frames (list): List of time frames to calculate CAGR.
    - label (str): Label for the portfolio.
    """
    try:
        frequency_map = {
            'Weekly': 'W',
            'Monthly': 'M',
            'Quarterly': 'Q',
            'Annually': 'Y'
        }
        fig = go.Figure()
        for tf in time_frames:
            freq = frequency_map.get(tf)
            if not freq:
                continue
            rolled = cum_returns.resample(freq).last()
            if rolled.empty:
                st.warning(f"Not enough data to calculate {tf} CAGR.")
                continue
            years = (rolled.index[-1] - rolled.index[0]).days / 365.25
            if years <= 0:
                st.warning(f"Not enough time elapsed to calculate {tf} CAGR.")
                continue
            cagr = (rolled / rolled.iloc[0]) ** (1/years) - 1
            fig.add_trace(go.Scatter(x=rolled.index, y=cagr, mode='lines', name=f'{tf} CAGR - {label}'))
        fig.update_layout(
            title=f'CAGR Over Multiple Time Frames - {label}',
            xaxis_title='Date',
            yaxis_title='CAGR',
            hovermode='x unified'
        )
        st.plotly_chart(fig, use_container_width=True)
    except Exception as e:
        st.error(f"Error plotting CAGR over time: {e}")

def plot_rolling_metrics(returns, windows=[30, 90, 180, 252], label='Portfolio'):
    """
    Plot rolling metrics (Volatility and Sharpe Ratio) over specified rolling window periods.

    Parameters:
    - returns (pd.Series): Portfolio's daily returns.
    - windows (list): List of rolling window sizes in days (e.g., [30, 90, 180, 252]).
    - label (str): Label for the portfolio.
    """
    try:
        # Ensure returns are in DataFrame format with 'Return' column
        if isinstance(returns, pd.Series):
            returns = returns.to_frame(name='Return')
        elif isinstance(returns, pd.DataFrame):
            if 'Return' not in returns.columns:
                # If only one column exists, assume it's the return
                if returns.shape[1] == 1:
                    returns.columns = ['Return']
                else:
                    st.error("Returns DataFrame must contain a 'Return' column.")
                    return
        
        # Initialize a DataFrame to store metrics
        metrics_df = pd.DataFrame(index=returns.index)
        
        for window in windows:
            if window <= 0:
                st.warning(f"Ignored non-positive rolling window: {window}")
                continue
            
            # Calculate rolling volatility (annualized)
            rolling_vol = returns['Return'].rolling(window=window).std() * np.sqrt(252)
            metrics_df[f'Rolling {window}-Day Volatility'] = rolling_vol
            
            # Calculate rolling Sharpe Ratio (assuming risk-free rate of 2%)
            risk_free_rate = 0.02
            rolling_return = returns['Return'].rolling(window=window).mean() * 252
            rolling_sharpe = (rolling_return - risk_free_rate) / rolling_vol
            metrics_df[f'Rolling {window}-Day Sharpe Ratio'] = rolling_sharpe
        
        # Drop rows with all NaN values
        metrics_df.dropna(how='all', inplace=True)
        
        if metrics_df.empty:
            st.warning("No data available to plot rolling metrics. Please check the rolling windows and data.")
            return
        
        # Create subplots: one for Volatility, one for Sharpe Ratio
        fig = make_subplots(
            rows=2, cols=1,
            subplot_titles=(f'Rolling Volatility - {label}', f'Rolling Sharpe Ratio - {label}'),
            shared_xaxes=True,
            vertical_spacing=0.1,
            row_heights=[0.5, 0.5]
        )
        
        # Plot Rolling Volatility
        for window in windows:
            col_name = f'Rolling {window}-Day Volatility'
            if col_name in metrics_df.columns:
                fig.add_trace(
                    go.Scatter(
                        x=metrics_df.index,
                        y=metrics_df[col_name],
                        mode='lines',
                        name=f'{window}-Day Volatility'
                    ),
                    row=1, col=1
                )
        
        # Plot Rolling Sharpe Ratio
        for window in windows:
            col_name = f'Rolling {window}-Day Sharpe Ratio'
            if col_name in metrics_df.columns:
                fig.add_trace(
                    go.Scatter(
                        x=metrics_df.index,
                        y=metrics_df[col_name],
                        mode='lines',
                        name=f'{window}-Day Sharpe'
                    ),
                    row=2, col=1
                )
        
        # Update layout
        fig.update_layout(
            title=f'Rolling Metrics Over Time - {label}',
            height=800,
            template='plotly_dark',
            hovermode='x unified'
        )
        
        fig.update_xaxes(title_text="Date")
        fig.update_yaxes(title_text="Volatility", row=1, col=1)
        fig.update_yaxes(title_text="Sharpe Ratio", row=2, col=1)
        
        st.plotly_chart(fig, use_container_width=True)
    
    except Exception as e:
        st.error(f"Error plotting rolling metrics: {e}")

def plot_risk_return_attribution_comparison(returns_a, weights_a, returns_b, weights_b, label_a='Portfolio A', label_b='Portfolio B'):
    """
    Plot a comparative Risk-Return Attribution analysis between two portfolios.

    Parameters:
    - returns_a (pd.Series or pd.DataFrame): Returns of the first portfolio's assets.
    - weights_a (array-like): Allocation weights for the first portfolio.
    - returns_b (pd.Series or pd.DataFrame): Returns of the second portfolio's assets.
    - weights_b (array-like): Allocation weights for the second portfolio.
    - label_a (str): Label for the first portfolio.
    - label_b (str): Label for the second portfolio.
    """
    try:
        # Ensure returns are in DataFrame format
        if isinstance(returns_a, pd.Series):
            returns_a = returns_a.to_frame(name='Asset_A')
        elif isinstance(returns_a, pd.DataFrame):
            pass
        else:
            st.error("Returns_a should be a pandas DataFrame or Series.")
            return

        if isinstance(returns_b, pd.Series):
            returns_b = returns_b.to_frame(name='Asset_B')
        elif isinstance(returns_b, pd.DataFrame):
            pass
        else:
            st.error("Returns_b should be a pandas DataFrame or Series.")
            return

        # Align assets by taking the union of both asset sets
        all_assets = list(set(returns_a.columns).union(set(returns_b.columns)))
        returns_a = returns_a.reindex(columns=all_assets, fill_value=0)
        returns_b = returns_b.reindex(columns=all_assets, fill_value=0)

        # Ensure weights are numpy arrays
        weights_a = np.array(weights_a)
        weights_b = np.array(weights_b)

        # Align weights with all_assets
        if len(weights_a) != len(returns_a.columns):
            st.error("Number of weights for Portfolio A does not match the number of assets after alignment.")
            return
        if len(weights_b) != len(returns_b.columns):
            st.error("Number of weights for Portfolio B does not match the number of assets after alignment.")
            return

        # Normalize weights to sum to 1
        weights_a = weights_a / weights_a.sum() if weights_a.sum() != 0 else weights_a
        weights_b = weights_b / weights_b.sum() if weights_b.sum() != 0 else weights_b

        # Calculate annualized returns and volatilities for Portfolio A
        annual_returns_a = returns_a.mean() * 252
        annual_volatility_a = returns_a.std() * np.sqrt(252)

        # Calculate annualized returns and volatilities for Portfolio B
        annual_returns_b = returns_b.mean() * 252
        annual_volatility_b = returns_b.std() * np.sqrt(252)

        # Calculate contribution to portfolio return and volatility for Portfolio A
        contribution_return_a = weights_a * annual_returns_a
        contribution_volatility_a = weights_a * annual_volatility_a * returns_a.corr().dot(weights_a)

        # Calculate contribution to portfolio return and volatility for Portfolio B
        contribution_return_b = weights_b * annual_returns_b
        contribution_volatility_b = weights_b * annual_volatility_b * returns_b.corr().dot(weights_b)

        # Create DataFrame for Portfolio A
        attribution_a = pd.DataFrame({
            'Asset': all_assets,
            'Weight': weights_a,
            'Annual Return': annual_returns_a,
            'Contribution to Return': contribution_return_a,
            'Annual Volatility': annual_volatility_a,
            'Contribution to Volatility': contribution_volatility_a
        })

        # Create DataFrame for Portfolio B
        attribution_b = pd.DataFrame({
            'Asset': all_assets,
            'Weight': weights_b,
            'Annual Return': annual_returns_b,
            'Contribution to Return': contribution_return_b,
            'Annual Volatility': annual_volatility_b,
            'Contribution to Volatility': contribution_volatility_b
        })

        # Plotting
        fig = make_subplots(
            rows=2, cols=2,
            subplot_titles=(
                f'Contribution to Return - {label_a}',
                f'Contribution to Volatility - {label_a}',
                f'Contribution to Return - {label_b}',
                f'Contribution to Volatility - {label_b}'
            ),
            specs=[[{'type': 'bar'}, {'type': 'bar'}],
                   [{'type': 'bar'}, {'type': 'bar'}]]
        )

        # Portfolio A - Contribution to Return
        fig.add_trace(
            go.Bar(
                x=attribution_a['Asset'],
                y=attribution_a['Contribution to Return'],
                marker_color=px.colors.qualitative.Set3,
                name='Return Contribution'
            ),
            row=1, col=1
        )

        # Portfolio A - Contribution to Volatility
        fig.add_trace(
            go.Bar(
                x=attribution_a['Asset'],
                y=attribution_a['Contribution to Volatility'],
                marker_color=px.colors.qualitative.Set3,
                name='Volatility Contribution'
            ),
            row=1, col=2
        )

        # Portfolio B - Contribution to Return
        fig.add_trace(
            go.Bar(
                x=attribution_b['Asset'],
                y=attribution_b['Contribution to Return'],
                marker_color=px.colors.qualitative.Set3,
                name='Return Contribution'
            ),
            row=2, col=1
        )

        # Portfolio B - Contribution to Volatility
        fig.add_trace(
            go.Bar(
                x=attribution_b['Asset'],
                y=attribution_b['Contribution to Volatility'],
                marker_color=px.colors.qualitative.Set3,
                name='Volatility Contribution'
            ),
            row=2, col=2
        )

        fig.update_layout(
            title_text='Risk-Return Attribution Comparison',
            template='plotly_dark',
            showlegend=False,
            height=800
        )

        fig.update_xaxes(title_text="Assets", row=1, col=1)
        fig.update_yaxes(title_text="Contribution", row=1, col=1)
        fig.update_xaxes(title_text="Assets", row=1, col=2)
        fig.update_yaxes(title_text="Contribution", row=1, col=2)
        fig.update_xaxes(title_text="Assets", row=2, col=1)
        fig.update_yaxes(title_text="Contribution", row=2, col=1)
        fig.update_xaxes(title_text="Assets", row=2, col=2)
        fig.update_yaxes(title_text="Contribution", row=2, col=2)

        st.plotly_chart(fig, use_container_width=True)

    except Exception as e:
        st.error(f"Error plotting risk-return attribution comparison: {e}")

def plot_risk_return_attribution(returns, weights, label='Portfolio'):
    """
    Plot a Risk-Return Attribution analysis for the portfolio.
    
    Parameters:
    - returns (pd.DataFrame or pd.Series): Returns of the portfolio's assets.
    - weights (array-like): Allocation weights corresponding to each asset.
    - label (str): Label for the portfolio.
    """
    try:
        # Ensure returns are in DataFrame format
        if isinstance(returns, pd.Series):
            returns = returns.to_frame(name='Asset')
        elif isinstance(returns, pd.DataFrame):
            pass
        else:
            st.error("Returns should be a pandas DataFrame or Series.")
            return
        
        # Ensure weights are a numpy array
        weights = np.array(weights)
        
        # Check if the number of weights matches the number of assets
        if len(weights) != returns.shape[1]:
            st.error("Number of weights must match number of assets.")
            return
        
        # Calculate annualized returns and volatilities
        annual_returns = returns.mean() * 252
        annual_volatility = returns.std() * np.sqrt(252)
        
        # Calculate contribution to portfolio return and volatility
        portfolio_return = np.dot(weights, annual_returns)
        portfolio_volatility = np.sqrt(np.dot(weights.T, np.dot(returns.cov() * 252, weights)))
        
        # Calculate contribution of each asset to portfolio return and volatility
        contribution_return = weights * annual_returns
        # Correct method for contribution to volatility
        contribution_volatility = weights * annual_volatility * returns.corr().dot(weights)
        
        # Create a DataFrame for plotting
        attribution_df = pd.DataFrame({
            'Asset': returns.columns,
            'Weight': weights,
            'Annual Return': annual_returns,
            'Contribution to Return': contribution_return,
            'Annual Volatility': annual_volatility,
            'Contribution to Volatility': contribution_volatility
        })
        
        # Plotting
        fig = make_subplots(
            rows=1, cols=2,
            subplot_titles=(f'Contribution to Return - {label}', f'Contribution to Volatility - {label}'),
            specs=[[{'type': 'bar'}, {'type': 'bar'}]]
        )
        
        # Contribution to Return
        fig.add_trace(
            go.Bar(
                x=attribution_df['Asset'],
                y=attribution_df['Contribution to Return'],
                marker_color=px.colors.qualitative.Set3,
                name='Return Contribution'
            ),
            row=1, col=1
        )
        
        # Contribution to Volatility
        fig.add_trace(
            go.Bar(
                x=attribution_df['Asset'],
                y=attribution_df['Contribution to Volatility'],
                marker_color=px.colors.qualitative.Set3,
                name='Volatility Contribution'
            ),
            row=1, col=2
        )
        
        fig.update_layout(
            title_text=f'Risk-Return Attribution Analysis - {label}',
            yaxis_title='Contribution',
            yaxis2_title='Contribution',
            template='plotly_dark',
            showlegend=False
        )
        
        st.plotly_chart(fig, use_container_width=True)
        
    except Exception as e:
        st.error(f"Error plotting risk-return attribution: {e}")

def plot_correlation_heatmap(returns):
    """
    Plot a correlation heatmap of the asset returns if there are multiple assets.

    Parameters:
    - returns (pd.DataFrame or pd.Series): Returns of the portfolio's assets.
    """
    try:
        # Ensure returns are in DataFrame format
        if isinstance(returns, pd.Series):
            returns = returns.to_frame(name='Asset')
        elif isinstance(returns, pd.DataFrame):
            pass
        else:
            st.error("Returns should be a pandas DataFrame or Series.")
            return

        # Proceed only if there are multiple assets
        if returns.shape[1] < 2:
            st.info("Correlation Heatmap is not available for single-asset portfolios.")
            return

        # Calculate correlation matrix
        corr_matrix = returns.corr()

        if corr_matrix.empty:
            st.warning("Correlation matrix is empty. Unable to plot heatmap.")
            return

        # Debug: Print correlation matrix shape
        st.write(f"Correlation Matrix Shape: {corr_matrix.shape}")

        # Verify that the number of columns matches the expected number of labels
        if corr_matrix.shape[0] != corr_matrix.shape[1]:
            st.error("Correlation matrix is not square. Check the asset data.")
            return

        # Create heatmap using Plotly Express
        fig = px.imshow(
            corr_matrix,
            title='Correlation Heatmap of Assets',
            labels=dict(x="Assets", y="Assets", color="Correlation"),
            color_continuous_scale='Viridis',
            zmin=-1,
            zmax=1,
            text_auto=True,
            aspect="auto",
            template='plotly_dark',
            x=corr_matrix.columns,
            y=corr_matrix.columns
        )

        # Improve layout
        fig.update_xaxes(side="top")
        fig.update_layout(
            xaxis_title="Assets",
            yaxis_title="Assets",
            coloraxis_colorbar=dict(title="Correlation"),
            margin=dict(l=50, r=50, t=80, b=50)
        )

        st.plotly_chart(fig, use_container_width=True)
    except Exception as e:
        st.error(f"Error plotting correlation heatmap: {e}")

def plot_allocation_pie(weights, assets, title="Portfolio Allocation", hover_info="percent+name"):
    """
    Plot a pie chart showing the portfolio allocation.
    
    Parameters:
    - weights (array-like): Allocation weights (should sum to 1 or 100).
    - assets (list): List of asset names.
    - title (str): Title of the pie chart.
    - hover_info (str): Information to display on hover, e.g., "percent+name".
    """
    try:
        # Ensure weights sum to 1
        total = sum(weights)
        if not np.isclose(total, 1.0):
            weights = np.array(weights) / total
        
        fig = go.Figure(data=[go.Pie(
            labels=assets,
            values=weights,
            hoverinfo=hover_info,
            textinfo='percent',
            marker=dict(colors=px.colors.qualitative.Set3)
        )])
        
        fig.update_layout(
            title=title,
            template='plotly_dark'
        )
        
        st.plotly_chart(fig, use_container_width=True)
    except Exception as e:
        st.error(f"Error plotting allocation pie chart: {e}")

def plot_growth_comparison(cum_returns_a, cum_returns_b, labels=None):
    """
    Plot the growth comparison between two portfolios.
    
    Parameters:
    - cum_returns_a (pd.Series): Cumulative returns of the first portfolio.
    - cum_returns_b (pd.Series): Cumulative returns of the second portfolio.
    - labels (list, optional): List containing labels for the two portfolios.
    """
    try:
        # Align the indices to ensure matching dates
        common_index = cum_returns_a.index.intersection(cum_returns_b.index)
        cum_returns_a = cum_returns_a.loc[common_index]
        cum_returns_b = cum_returns_b.loc[common_index]

        if cum_returns_a.empty or cum_returns_b.empty:
            st.warning("No overlapping data to plot growth comparison.")
            return

        df = pd.DataFrame({
            'Date': cum_returns_a.index,
            'Portfolio 1': cum_returns_a.values,
            'Portfolio 2': cum_returns_b.values
        })

        # Assign labels if provided
        if labels and isinstance(labels, list) and len(labels) == 2:
            df.rename(columns={'Portfolio 1': labels[0], 'Portfolio 2': labels[1]}, inplace=True)
        else:
            df.rename(columns={'Portfolio 1': 'Portfolio A', 'Portfolio 2': 'Portfolio B'}, inplace=True)

        fig = px.line(
            df,
            x='Date',
            y=df.columns[1:],  # Dynamically select the plotted columns
            title='Growth Comparison',
            labels={'value': 'Cumulative Returns', 'Date': 'Date'},
            hover_data={'Date': '|%B %d, %Y'},  # Enhanced hover format
            template='plotly_dark'  # Use dark template for better contrast
        )

        fig.update_layout(
            hovermode='x unified',
            xaxis=dict(rangeslider=dict(visible=True), type='date'),
        )
        fig.update_traces(line=dict(width=2))
        st.plotly_chart(fig, use_container_width=True, config={
            'scrollZoom': True
        })
    except Exception as e:
        st.error(f"Error plotting growth comparison: {e}")

def plot_drawdown_comparison(drawdown_a, drawdown_b, labels=None):
    """
    Plot the drawdown comparison between two portfolios.
    
    Parameters:
    - drawdown_a (pd.Series): Drawdown series of the first portfolio.
    - drawdown_b (pd.Series): Drawdown series of the second portfolio.
    - labels (list, optional): List containing labels for the two portfolios.
    """
    try:
        # Align the indices to ensure matching dates
        common_index = drawdown_a.index.intersection(drawdown_b.index)
        drawdown_a = drawdown_a.loc[common_index]
        drawdown_b = drawdown_b.loc[common_index]

        if drawdown_a.empty or drawdown_b.empty:
            st.warning("No overlapping data to plot drawdown comparison.")
            return

        df = pd.DataFrame({
            'Date': drawdown_a.index,
            'Drawdown 1': drawdown_a.values,
            'Drawdown 2': drawdown_b.values
        })

        # Assign labels if provided
        if labels and isinstance(labels, list) and len(labels) == 2:
            df.rename(columns={'Drawdown 1': labels[0], 'Drawdown 2': labels[1]}, inplace=True)
        else:
            df.rename(columns={'Drawdown 1': 'Portfolio A', 'Drawdown 2': 'Portfolio B'}, inplace=True)

        fig = px.line(
            df,
            x='Date',
            y=df.columns[1:],  # Dynamically select the plotted columns
            title='Drawdown Comparison',
            labels={'value': 'Drawdown (%)', 'Date': 'Date'},
            hover_data={'Date': '|%B %d, %Y'},  # Enhanced hover format
            template='plotly_dark'  # Use dark template for better contrast
        )

        fig.update_layout(
            hovermode='x unified',
            xaxis=dict(rangeslider=dict(visible=True), type='date'),
        )
        fig.update_traces(line=dict(width=2))
        st.plotly_chart(fig, use_container_width=True, config={
            'scrollZoom': True
        })
    except Exception as e:
        st.error(f"Error plotting drawdown comparison: {e}")

def plot_cagr_comparison(cum_returns_a, cum_returns_b, time_frames=['Weekly', 'Monthly', 'Quarterly', 'Annually']):
    """
    Plot the Compound Annual Growth Rate (CAGR) over multiple time frames for comparison.
    
    Parameters:
    - cum_returns_a (pd.Series): Cumulative returns of the first portfolio.
    - cum_returns_b (pd.Series): Cumulative returns of the second portfolio.
    - time_frames (list): List of time frames to calculate CAGR.
    """
    try:
        frequency_map = {
            'Weekly': 'W',
            'Monthly': 'M',
            'Quarterly': 'Q',
            'Annually': 'Y'
        }
        fig = go.Figure()
        for tf in time_frames:
            freq = frequency_map.get(tf)
            if not freq:
                continue
            rolled_a = cum_returns_a.resample(freq).last()
            rolled_b = cum_returns_b.resample(freq).last()
            if rolled_a.empty or rolled_b.empty:
                st.warning(f"Not enough data to calculate {tf} CAGR.")
                continue
            years_a = (rolled_a.index[-1] - rolled_a.index[0]).days / 365.25
            years_b = (rolled_b.index[-1] - rolled_b.index[0]).days / 365.25
            if years_a <= 0 or years_b <= 0:
                st.warning(f"Not enough time elapsed to calculate {tf} CAGR.")
                continue
            cagr_a = (rolled_a / rolled_a.iloc[0]) ** (1/years_a) - 1
            cagr_b = (rolled_b / rolled_b.iloc[0]) ** (1/years_b) - 1
            fig.add_trace(go.Scatter(x=rolled_a.index, y=cagr_a, mode='lines', name=f'{tf} CAGR - Portfolio A'))
            fig.add_trace(go.Scatter(x=rolled_b.index, y=cagr_b, mode='lines', name=f'{tf} CAGR - Portfolio B'))
        fig.update_layout(
            title='CAGR Over Multiple Time Frames - Comparison',
            xaxis_title='Date',
            yaxis_title='CAGR',
            hovermode='x unified'
        )
        st.plotly_chart(fig, use_container_width=True)
    except Exception as e:
        st.error(f"Error plotting CAGR comparison: {e}")

def plot_allocation_comparison(weights_a, assets_a, weights_b, assets_b, title='Portfolio Allocation Comparison'):
    """
    Plot side-by-side pie charts to compare portfolio allocations.
    
    Parameters:
    - weights_a (array-like): Allocation weights for the first portfolio.
    - assets_a (list): List of asset names for the first portfolio.
    - weights_b (array-like): Allocation weights for the second portfolio.
    - assets_b (list): List of asset names for the second portfolio.
    - title (str): Title of the plot.
    """
    try:
        allocation_df_a = pd.DataFrame({
            'Asset': assets_a,
            'Weight': weights_a
        })

        allocation_df_b = pd.DataFrame({
            'Asset': assets_b,
            'Weight': weights_b
        })

        # Create subplots for side-by-side pie charts
        fig = make_subplots(rows=1, cols=2, specs=[[{'type':'domain'}, {'type':'domain'}]],
                            subplot_titles=('Portfolio A', 'Portfolio B'))

        fig.add_trace(go.Pie(labels=allocation_df_a['Asset'], values=allocation_df_a['Weight'],
                             name='Portfolio A',
                             hoverinfo='label+percent',
                             textinfo='percent',
                             marker=dict(colors=px.colors.qualitative.Set3)),
                      row=1, col=1)

        fig.add_trace(go.Pie(labels=allocation_df_b['Asset'], values=allocation_df_b['Weight'],
                             name='Portfolio B',
                             hoverinfo='label+percent',
                             textinfo='percent',
                             marker=dict(colors=px.colors.qualitative.Set3)),
                      row=1, col=2)

        fig.update_traces(textposition='inside', showlegend=False)

        fig.update_layout(
            title_text=title,
            annotations=[dict(text='Portfolio A', x=0.18, y=0.5, font_size=12, showarrow=False),
                         dict(text='Portfolio B', x=0.82, y=0.5, font_size=12, showarrow=False)],
            template='plotly_dark'
        )

        st.plotly_chart(fig, use_container_width=True)
    except Exception as e:
        st.error(f"Error plotting allocation comparison: {e}")

def plot_rolling_metrics_comparison(returns_a, weights_a, returns_b, weights_b, windows=[30, 90, 180, 252]):
    """
    Plot rolling metrics (volatility and Sharpe ratio) over specified windows for comparison.
    
    Parameters:
    - returns_a (pd.Series): Returns of the first portfolio.
    - weights_a (array-like): Allocation weights of the first portfolio.
    - returns_b (pd.Series): Returns of the second portfolio.
    - weights_b (array-like): Allocation weights of the second portfolio.
    - windows (list): List of rolling window sizes in days.
    """
    try:
        metrics_a = {}
        metrics_b = {}
        for window in windows:
            rolling_return_a = returns_a.rolling(window).mean() * 252
            rolling_vol_a = returns_a.rolling(window).std() * np.sqrt(252)
            rolling_sharpe_a = (rolling_return_a - 0.02) / rolling_vol_a  # Assuming rf rate of 2%

            rolling_return_b = returns_b.rolling(window).mean() * 252
            rolling_vol_b = returns_b.rolling(window).std() * np.sqrt(252)
            rolling_sharpe_b = (rolling_return_b - 0.02) / rolling_vol_b  # Assuming rf rate of 2%

            metrics_a[f'Rolling {window}-Day Volatility - Portfolio A'] = rolling_vol_a
            metrics_a[f'Rolling {window}-Day Sharpe Ratio - Portfolio A'] = rolling_sharpe_a

            metrics_b[f'Rolling {window}-Day Volatility - Portfolio B'] = rolling_vol_b
            metrics_b[f'Rolling {window}-Day Sharpe Ratio - Portfolio B'] = rolling_sharpe_b

        fig = go.Figure()

        for metric_name, metric_series in metrics_a.items():
            fig.add_trace(go.Scatter(x=metric_series.index, y=metric_series, mode='lines', name=metric_name))

        for metric_name, metric_series in metrics_b.items():
            fig.add_trace(go.Scatter(x=metric_series.index, y=metric_series, mode='lines', name=metric_name))

        fig.update_layout(
            title='Rolling Metrics Over User-Defined Periods - Comparison',
            xaxis_title='Date',
            yaxis_title='Value',
            hovermode='x unified',
            template='plotly_dark'
        )
        st.plotly_chart(fig, use_container_width=True)
    except Exception as e:
        st.error(f"Error plotting rolling metrics comparison: {e}")

def plot_correlation_heatmap_comparison(returns_a, returns_b):
    """
    Plot a heatmap of the correlation matrix for both portfolios combined.
    
    Parameters:
    - returns_a (pd.DataFrame or pd.Series): Returns of the first portfolio.
    - returns_b (pd.DataFrame or pd.Series): Returns of the second portfolio.
    """
    try:
        combined_returns = pd.concat([returns_a, returns_b], axis=1)
        combined_returns.columns = ['Portfolio A'] + (returns_b.columns.tolist() if isinstance(returns_b, pd.DataFrame) else ['Portfolio B'])

        if combined_returns.shape[1] < 2:
            st.warning("Correlation heatmap requires at least two assets with variability.")
            return

        corr_matrix = combined_returns.corr()

        # Check if the correlation matrix is not empty
        if corr_matrix.empty:
            st.warning("Correlation matrix is empty. Unable to plot heatmap.")
            return

        fig = px.imshow(
            corr_matrix,
            title='Correlation Heatmap - Comparison',
            labels={'x': 'Portfolio', 'y': 'Portfolio', 'color': 'Correlation'},
            color_continuous_scale='Portland',
            zmin=-1,
            zmax=1,
            text_auto=True,
            aspect="auto",
            template='plotly_dark'
        )
        fig.update_xaxes(side="top")  # Move x-axis labels to the top for better readability
        st.plotly_chart(fig, use_container_width=True)
    except Exception as e:
        st.error(f"Error plotting correlation heatmap: {e}")

def plot_risk_return_attribution_comparison(returns_a, weights_a, returns_b, weights_b, label_a='Portfolio A', label_b='Portfolio B'):
    """
    Plot the risk-return attribution comparison between two portfolios.
    
    Parameters:
    - returns_a (pd.Series or pd.DataFrame): Returns of the first portfolio.
    - weights_a (array-like): Allocation weights of the first portfolio.
    - returns_b (pd.Series or pd.DataFrame): Returns of the second portfolio.
    - weights_b (array-like): Allocation weights of the second portfolio.
    - label_a (str): Label for the first portfolio.
    - label_b (str): Label for the second portfolio.
    """
    try:
        # Portfolio A
        asset_returns_a = returns_a.mean() * 252  # Annualized return
        asset_volatility_a = returns_a.std() * np.sqrt(252)  # Annualized volatility
        asset_contribution_a = weights_a * asset_volatility_a

        # Portfolio B
        asset_returns_b = returns_b.mean() * 252  # Annualized return
        asset_volatility_b = returns_b.std() * np.sqrt(252)  # Annualized volatility
        asset_contribution_b = weights_b * asset_volatility_b

        # Create DataFrames for plotting
        df_a = pd.DataFrame({
            'Asset': returns_a.columns if isinstance(returns_a, pd.DataFrame) else ['Asset A'],
            'Volatility': asset_volatility_a if isinstance(asset_volatility_a, np.ndarray) else [asset_volatility_a],
            'Return': asset_returns_a if isinstance(asset_returns_a, np.ndarray) else [asset_returns_a],
            'Contribution': asset_contribution_a if isinstance(asset_contribution_a, np.ndarray) else [asset_contribution_a]
        })

        df_b = pd.DataFrame({
            'Asset': returns_b.columns if isinstance(returns_b, pd.DataFrame) else ['Asset B'],
            'Volatility': asset_volatility_b if isinstance(asset_volatility_b, np.ndarray) else [asset_volatility_b],
            'Return': asset_returns_b if isinstance(asset_returns_b, np.ndarray) else [asset_returns_b],
            'Contribution': asset_contribution_b if isinstance(asset_contribution_b, np.ndarray) else [asset_contribution_b]
        })

        # Combine for plotting
        df_a['Portfolio'] = label_a
        df_b['Portfolio'] = label_b
        combined_df = pd.concat([df_a, df_b], ignore_index=True)

        fig = px.scatter(
            combined_df,
            x='Volatility',
            y='Return',
            size='Contribution',
            color='Portfolio',
            hover_name='Asset',
            title='Risk-Return Attribution Comparison',
            labels={'Volatility': 'Annualized Volatility', 'Return': 'Annualized Return'},
            template='plotly_dark',
            size_max=60
        )

        st.plotly_chart(fig, use_container_width=True)
    except Exception as e:
        st.error(f"Error plotting risk-return attribution comparison: {e}")

def plot_efficient_frontier_comparison(returns_a, returns_b, num_portfolios=1000, rf=0.0):
    """
    Plot the Efficient Frontier comparison between two portfolios.
    
    Parameters:
    - returns_a (pd.DataFrame or pd.Series): Returns of the first portfolio.
    - returns_b (pd.DataFrame or pd.Series): Returns of the second portfolio.
    - num_portfolios (int): Number of portfolios to simulate.
    - rf (float): Risk-free rate.
    """
    try:
        num_assets_a = returns_a.shape[1] if isinstance(returns_a, pd.DataFrame) else 1
        num_assets_b = returns_b.shape[1] if isinstance(returns_b, pd.DataFrame) else 1

        def generate_portfolio(returns, num_assets, rf):
            weights = np.random.random(num_assets)
            weights /= np.sum(weights)
            portfolio_return = np.dot(returns.mean(), weights) * 252
            portfolio_std_dev = np.sqrt(np.dot(weights.T, np.dot(returns.cov() * 252, weights)))
            sharpe_ratio = (portfolio_return - rf) / portfolio_std_dev if portfolio_std_dev > 0 else 0
            return portfolio_std_dev, portfolio_return, sharpe_ratio

        # Generate portfolios for Portfolio A
        results_a = []
        for _ in range(num_portfolios):
            s, r, sr = generate_portfolio(returns_a, num_assets_a, rf)
            results_a.append([s, r, sr])
        results_a = np.array(results_a).T

        # Generate portfolios for Portfolio B
        results_b = []
        for _ in range(num_portfolios):
            s, r, sr = generate_portfolio(returns_b, num_assets_b, rf)
            results_b.append([s, r, sr])
        results_b = np.array(results_b).T

        # Create DataFrame
        ef_df_a = pd.DataFrame({
            'Std Dev': results_a[0],
            'Return': results_a[1],
            'Sharpe Ratio': results_a[2],
            'Portfolio': 'Portfolio A'
        })

        ef_df_b = pd.DataFrame({
            'Std Dev': results_b[0],
            'Return': results_b[1],
            'Sharpe Ratio': results_b[2],
            'Portfolio': 'Portfolio B'
        })

        ef_df = pd.concat([ef_df_a, ef_df_b], ignore_index=True)

        fig = px.scatter(
            ef_df,
            x='Std Dev',
            y='Return',
            color='Sharpe Ratio',
            facet_col='Portfolio',
            title='Efficient Frontier Comparison',
            labels={
                'Std Dev': 'Annualized Volatility (Std Dev)',
                'Return': 'Annualized Return',
                'Sharpe Ratio': 'Sharpe Ratio'
            },
            hover_data={
                'Sharpe Ratio': ':.2f',
                'Std Dev': ':.2f',
                'Return': ':.2f'
            },
            template='plotly_dark'
        )

        # Highlight maximum Sharpe ratio portfolio for each
        for portfolio in ['Portfolio A', 'Portfolio B']:
            subset = ef_df[ef_df['Portfolio'] == portfolio]
            if subset.empty:
                continue
            max_sharpe_idx = subset['Sharpe Ratio'].idxmax()
            if pd.isna(max_sharpe_idx):
                continue
            max_sharpe = subset.loc[max_sharpe_idx]
            fig.add_trace(go.Scatter(
                x=[max_sharpe['Std Dev']],
                y=[max_sharpe['Return']],
                mode='markers+text',
                marker=dict(color='red', size=14, symbol='star'),
                name=f'Max Sharpe {portfolio}',
                text=["Max Sharpe"],
                textposition="top center",
                hoverinfo='text',
                showlegend=False
            ))

        fig.update_layout(
            hovermode='closest',
            coloraxis_colorbar=dict(title='Sharpe Ratio'),
            template='plotly_dark'
        )
        st.plotly_chart(fig, use_container_width=True, config={
            'responsive': True,
            'scrollZoom': True,
            'displayModeBar': True
        })
    except Exception as e:
        st.error(f"Error plotting efficient frontier comparison: {e}")

# components/run_backtest.py

import streamlit as st
import pandas as pd
import numpy as np
import plotly.express as px

# Importing all necessary functions from utils, metrics, and plots
from utils import (
    download_data, 
    generate_recommendations, 
    backtest,  
    fetch_fama_french_factors, 
)

from metrics import (
    calculate_portfolio_metrics, 
    calculate_final_score, 
    calculate_risk_factor_attribution, 
    get_drawdown_details
)

from plots import (
    plot_growth_comparison, 
    plot_drawdown_comparison, 
    plot_cagr_over_time, 
    plot_allocation_pie, 
    plot_correlation_heatmap, 
    plot_risk_return_attribution, 
    plot_rolling_metrics,
    plot_correlation_heatmap_comparison,
    plot_cagr_comparison,
    plot_allocation_comparison,
    plot_rolling_metrics_comparison,
    plot_risk_return_attribution_comparison,
    plot_efficient_frontier_comparison
)

def run_backtest_ui():
    # Initialize portfolios in session state if not already initialized
    if 'portfolios' not in st.session_state:
        st.session_state.portfolios = []
    
    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)
                portfolio2 = None
            else:
                portfolio1 = st.selectbox("Select First Portfolio", portfolio_names, key="p1")
                portfolio2 = st.selectbox("Select Second Portfolio", portfolio_names, key="p2")
                if portfolio1 == portfolio2:
                    st.warning("Please select two different portfolios for comparison.")
            
            st.subheader("⚙️ Visualizations Settings")
            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":
                    # Handle Portfolio vs Benchmark
                    try:
                        portfolio = next(p for p in st.session_state.portfolios if p['name'] == portfolio1)
                    except StopIteration:
                        st.error("Selected portfolio not found.")
                        return

                    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:
                            # Extract corresponding allocations
                            original_allocations = portfolio.get('allocations', [0.0]*len(portfolio.get('selected', [])))
                            adjusted_allocations = [original_allocations[i] for i, ticker in enumerate(portfolio.get('selected', [])) if ticker in available_selected]
                            
                            # Error Handling for Allocation-Axis
                            if len(adjusted_allocations) != len(available_selected):
                                st.error("Mismatch between allocations and available assets after filtering.")
                                return
                            
                            # Normalize allocations to sum to 1
                            weights = np.array(adjusted_allocations) / 100
                            total_alloc = weights.sum()
                            if not np.isclose(total_alloc, 1.0, atol=1e-4):
                                if total_alloc == 0:
                                    st.error("Total allocations sum to 0. Cannot adjust allocations.")
                                    return
                                st.warning("Allocations do not sum to 100%. Adjusting allocations proportionally.")
                                weights = weights / total_alloc
                            
                            # Final check to ensure weights match the number of assets
                            if len(weights) != len(available_selected):
                                st.error("Final allocation weights do not match the number of available assets.")
                                return
                            
                            # Calculate per-asset returns
                            asset_returns = price_data[available_selected].pct_change().dropna()
                            
                            # Run backtest to get portfolio returns
                            returns, cum_returns = backtest(
                                weights, 
                                price_data[available_selected],
                                portfolio['rebalance_freq'], 
                                portfolio['broker_fee'], 
                                debug=False
                            )
                            
                            # Align asset returns with portfolio returns
                            common_index = returns.index.intersection(asset_returns.index)
                            asset_returns = asset_returns.loc[common_index]
                            returns = returns.loc[common_index]
                            cum_returns = cum_returns.loc[common_index]
                            
                            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_benchmark = returns.index.intersection(benchmark_returns.index)
                                        if common_index_benchmark.empty:
                                            st.error("No overlapping dates between portfolio returns and benchmark returns after alignment.")
                                            st.stop()
                                        returns = returns.loc[common_index_benchmark]
                                        cum_returns = cum_returns.loc[common_index_benchmark]
                                        asset_returns = asset_returns.loc[common_index_benchmark]
                                        benchmark_returns = benchmark_returns.loc[common_index_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.get('Start Balance', "N/A"),
                                                    benchmark_metrics.get('End Balance', "N/A"),
                                                    benchmark_metrics.get('Annualized Return (CAGR)', "N/A"),
                                                    benchmark_metrics.get('Standard Deviation (Annualized)', "N/A"),
                                                    benchmark_metrics.get('Best Year', "N/A"),
                                                    benchmark_metrics.get('Worst Year', "N/A"),
                                                    benchmark_metrics.get('Maximum Drawdown', "N/A"),
                                                    benchmark_metrics.get('Sharpe Ratio', "N/A"),
                                                    benchmark_metrics.get('Sortino Ratio', "N/A"),
                                                    benchmark_metrics.get('Benchmark Correlation', "N/A")
                                                ]
                                            }
                                            performance_df_stats = pd.DataFrame(performance_data_stats).set_index('Metric')

                                            # Format metrics to two decimal places
                                            def format_value(val):
                                                if isinstance(val, str):
                                                    return val
                                                elif isinstance(val, float) or isinstance(val, int):
                                                    return f"{val:.2f}"
                                                else:
                                                    return "N/A"

                                            performance_df_stats_formatted = performance_df_stats.applymap(format_value)
                                            st.table(performance_df_stats_formatted)

                                    with tabs[2]:
                                        st.header("📋 Detail Comparisons")
                                        with st.container():
                                            st.subheader("🧮 Advanced Metrics")
                                            performance_data_advanced = {
                                                'Metric': list(portfolio_metrics.keys()),
                                                'Portfolio': list(portfolio_metrics.values()),
                                                'Benchmark': list(benchmark_metrics.values())
                                            }
                                            performance_df_advanced = pd.DataFrame(performance_data_advanced).set_index('Metric')

                                            # Replace NaN with empty strings for better visualization
                                            performance_df_advanced = performance_df_advanced.replace(np.nan, "")
                                            # Drop rows where both Portfolio and Benchmark values are NaN
                                            performance_df_advanced_clean = performance_df_advanced.dropna(how='all')

                                            # Format metrics to two decimal places
                                            performance_df_advanced_clean = performance_df_advanced_clean.applymap(format_value)
                                            
                                            st.table(performance_df_advanced_clean)
                                            st.markdown("---")
                                            st.write("**Note:** Some metrics in the benchmark column are empty because they are portfolio-specific measurements that can't be calculated for the benchmark alone...")

                                        st.subheader("🔍 Risk Factor Attribution Analysis")
                                        
                                        # Ensure 'returns' is defined and is a pd.Series
                                        if 'returns' not in locals():
                                            st.error("Portfolio returns data ('returns') is not defined.")
                                        else:
                                            # Define date range based on portfolio returns index
                                            start_date_str = returns.index.min().strftime('%Y-%m-%d')
                                            end_date_str = returns.index.max().strftime('%Y-%m-%d')
                                            # Fetch actual factor data
                                            factors = fetch_fama_french_factors(start_date_str, end_date_str)
                                            # Ensure factors are fetched
                                            if factors.empty:
                                                st.write("Failed to retrieve factor data.")       
                                            else:
                                                if isinstance(returns.index, pd.DatetimeIndex):
                                                    if returns.index.freq is None:
                                                        inferred_freq = pd.infer_freq(returns.index)
                                                        if inferred_freq is None:
                                                            inferred_freq = 'D'  # Set default frequency to daily
                                                    else:
                                                        inferred_freq = returns.index.freq
                                                    
                                                    try:
                                                        factors = factors.asfreq(inferred_freq, method='ffill')
                                                    except TypeError as e:
                                                        st.error(f"Error setting frequency for factors: {e}")
                                                        st.stop()
                                                else:
                                                    st.error("Returns index is not a DatetimeIndex.")
                                                    st.stop()
                                                # 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:
                                                    # Combined Attribution (Portfolio vs Benchmark)
                                                    combined_attribution, _ = calculate_risk_factor_attribution(benchmark_returns, factors)
                                                    if combined_attribution.empty:
                                                        st.write("Benchmark risk factor attribution is unavailable.")
                                                    else:
                                                        # Merge both attributions
                                                        combined_df = attribution.set_index('Factor').join(
                                                            combined_attribution.set_index('Factor'), lsuffix=f' ({portfolio["name"]})', rsuffix=f' ({portfolio["benchmark_symbol"]})'
                                                        ).reset_index()
                                                        
                                                        st.write("### Risk Factor Attribution Comparison")
                                                        st.table(combined_df)
                                                        
                                                        # Visualization
                                                        fig_attribution = px.bar(
                                                            combined_df.melt(id_vars='Factor', var_name='Category', value_name='Contribution (%)'),
                                                            x='Factor',
                                                            y='Contribution (%)',
                                                            color='Category',
                                                            barmode='group',
                                                            title='Risk Factor Attribution Comparison',
                                                            template='plotly_dark'
                                                        )
                                                        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.")

                                        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.")

                                    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, labels=["Portfolio", "Benchmark"])
                                        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, labels=["Portfolio", "Benchmark"])

                                        st.subheader("📈 Compound Annual Growth Rate (CAGR) Over Time")
                                        if selected_time_frames:
                                            plot_cagr_over_time(cum_returns, time_frames=selected_time_frames, label="Portfolio")
                                        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(asset_returns, pd.DataFrame):
                                            # Assuming the index is named 'Date'
                                            if asset_returns.index.name != 'Date':
                                                asset_returns = asset_returns.copy()
                                                asset_returns.index.name = 'Date'
                                            
                                            melted_returns = asset_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(asset_returns, pd.Series):
                                            # Assuming the index is named 'Date'
                                            if asset_returns.index.name != 'Date':
                                                asset_returns = asset_returns.copy()
                                                asset_returns.index.name = 'Date'
                                            
                                            melted_returns = asset_returns.reset_index().rename(columns={'Date': 'Date', 0: 'Return'})
                                            fig_box_plot = px.box(
                                                melted_returns,
                                                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(asset_returns)

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

                                        # Cumulative Returns Heatmap
                                        st.subheader("🔥 Cumulative Returns Heatmap")
                                        try:
                                            cum_returns_normalized = cum_returns / cum_returns.expanding().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 selected_rolling_periods:
                                            plot_rolling_metrics(returns, windows=selected_rolling_periods, label='Portfolio')
                                        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")
                                        skipped_metrics_set = set()
                                        portfolio_score = calculate_final_score(portfolio_metrics, benchmark_metrics, global_skipped=skipped_metrics_set)
                                        benchmark_score = calculate_final_score(benchmark_metrics, portfolio_metrics, global_skipped=skipped_metrics_set)
                                        st.write(f"**Portfolio:** {portfolio_score:.2f} / 100")
                                        st.write(f"**Benchmark:** {benchmark_score:.2f} / 100")
                                        
                                        # Show skipped metrics once
                                        if skipped_metrics_set:
                                            st.warning(f"Skipped metrics due to invalid values: {', '.join(skipped_metrics_set)}")

                                        # 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_formatted,
                                            'performance_df_advanced': performance_df_advanced_clean,  # Corrected to use the cleaned DataFrame
                                            'benchmark_cum_returns': benchmark_cum_returns
                                        }

                elif comparison_type == "Portfolio vs Portfolio" and portfolio1 and portfolio2 and portfolio1 != portfolio2:
                    # Handle Portfolio vs Portfolio
                    try:
                        portfolio_a = next(p for p in st.session_state.portfolios if p['name'] == portfolio1)
                        portfolio_b = next(p for p in st.session_state.portfolios if p['name'] == portfolio2)
                    except StopIteration:
                        st.error("One or both selected portfolios were not found.")
                        return

                    # Initialize cum_returns_a and cum_returns_b to prevent UnboundLocalError
                    cum_returns_a = pd.Series(dtype=float)
                    cum_returns_b = pd.Series(dtype=float)

                    # Backtest Portfolio A
                    price_data_a = download_data(
                        portfolio_a['selected'],
                        portfolio_a['start_date'],
                        portfolio_a['end_date']
                    )
                    # Backtest Portfolio B
                    price_data_b = download_data(
                        portfolio_b['selected'],
                        portfolio_b['start_date'],
                        portfolio_b['end_date']
                    )

                    if price_data_a.empty or price_data_b.empty:
                        st.error("No price data available for one or both selected portfolios.")
                    else:
                        # Process Portfolio A
                        available_a = [ticker for ticker in portfolio_a['selected'] if ticker in price_data_a.columns]
                        missing_a = list(set(portfolio_a['selected']) - set(available_a))
                        if missing_a:
                            st.warning(f"Excluded tickers from {portfolio_a['name']} with no data: {', '.join(missing_a)}")
                        if not available_a:
                            st.error(f"No selected tickers have available data for the portfolio '{portfolio_a['name']}'.")
                            return
                        # Extract corresponding allocations for Portfolio A
                        original_allocations_a = portfolio_a.get('allocations', [0.0]*len(portfolio_a.get('selected', [])))
                        adjusted_allocations_a = [original_allocations_a[i] for i, ticker in enumerate(portfolio_a.get('selected', [])) if ticker in available_a]
                        
                        if len(adjusted_allocations_a) != len(available_a):
                            st.error(f"Mismatch between allocations and available assets after filtering for '{portfolio_a['name']}'.")
                            return
                        
                        # Normalize allocations to sum to 1
                        weights_a = np.array(adjusted_allocations_a) / 100
                        total_alloc_a = weights_a.sum()
                        if not np.isclose(total_alloc_a, 1.0, atol=1e-4):
                            if total_alloc_a == 0:
                                st.error(f"Total allocations for '{portfolio_a['name']}' sum to 0. Cannot adjust allocations.")
                                return
                            st.warning(f"Allocations for '{portfolio_a['name']}' do not sum to 100%. Adjusting allocations proportionally.")
                            weights_a = weights_a / total_alloc_a
                        
                        # Calculate per-asset returns for Portfolio A
                        asset_returns_a = price_data_a[available_a].pct_change().dropna()
                        
                        # Run backtest for Portfolio A
                        returns_a, cum_returns_a = backtest(
                            weights_a, 
                            price_data_a[available_a],
                            portfolio_a['rebalance_freq'], 
                            portfolio_a['broker_fee'], 
                            debug=False
                        )
                        
                        # Process Portfolio B
                        available_b = [ticker for ticker in portfolio_b['selected'] if ticker in price_data_b.columns]
                        missing_b = list(set(portfolio_b['selected']) - set(available_b))
                        if missing_b:
                            st.warning(f"Excluded tickers from {portfolio_b['name']} with no data: {', '.join(missing_b)}")
                        if not available_b:
                            st.error(f"No selected tickers have available data for the portfolio '{portfolio_b['name']}'.")
                            return
                        # Extract corresponding allocations for Portfolio B
                        original_allocations_b = portfolio_b.get('allocations', [0.0]*len(portfolio_b.get('selected', [])))
                        adjusted_allocations_b = [original_allocations_b[i] for i, ticker in enumerate(portfolio_b.get('selected', [])) if ticker in available_b]
                        
                        if len(adjusted_allocations_b) != len(available_b):
                            st.error(f"Mismatch between allocations and available assets after filtering for '{portfolio_b['name']}'.")
                            return
                        
                        # Normalize allocations to sum to 1
                        weights_b = np.array(adjusted_allocations_b) / 100
                        total_alloc_b = weights_b.sum()
                        if not np.isclose(total_alloc_b, 1.0, atol=1e-4):
                            if total_alloc_b == 0:
                                st.error(f"Total allocations for '{portfolio_b['name']}' sum to 0. Cannot adjust allocations.")
                                return
                            st.warning(f"Allocations for '{portfolio_b['name']}' do not sum to 100%. Adjusting allocations proportionally.")
                            weights_b = weights_b / total_alloc_b
                        
                        # Calculate per-asset returns for Portfolio B
                        asset_returns_b = price_data_b[available_b].pct_change().dropna()
                        
                        # Run backtest for Portfolio B
                        returns_b, cum_returns_b = backtest(
                            weights_b, 
                            price_data_b[available_b],
                            portfolio_b['rebalance_freq'], 
                            portfolio_b['broker_fee'], 
                            debug=False
                        )
                        
                        # Align the data for comparison
                        common_dates = returns_a.index.intersection(returns_b.index)
                        if common_dates.empty:
                            st.error("No overlapping dates between the two portfolios after alignment.")
                            return
                        cum_returns_a = cum_returns_a.loc[common_dates]
                        cum_returns_b = cum_returns_b.loc[common_dates]
                        returns_a = returns_a.loc[common_dates]
                        returns_b = returns_b.loc[common_dates]
                        asset_returns_a = asset_returns_a.loc[common_dates]
                        asset_returns_b = asset_returns_b.loc[common_dates]
                        
                        # Calculate Metrics
                        metrics_a = calculate_portfolio_metrics(
                            returns_a, cum_returns_a, portfolio_a['rf_rate'], None
                        )
                        metrics_b = calculate_portfolio_metrics(
                            returns_b, cum_returns_b, portfolio_b['rf_rate'], None
                        )
                        
                        # Dashboard Header with Key Stats
                        with st.container():
                            st.markdown("### 🔑 Key Metrics")
                            key_metrics = {
                                'Annualized Return (CAGR)': ('CAGR', metrics_a.get('Annualized Return (CAGR)', "N/A"), metrics_b.get('Annualized Return (CAGR)', "N/A")),
                                'Sharpe Ratio': ('Sharpe Ratio', metrics_a.get('Sharpe Ratio', "N/A"), metrics_b.get('Sharpe Ratio', "N/A")),
                                'Maximum Drawdown': ('Maximum Drawdown', metrics_a.get('Maximum Drawdown', "N/A"), metrics_b.get('Maximum Drawdown', "N/A"))
                            }
                            columns = st.columns(len(key_metrics))
                            for idx, (metric, (label, value_a, value_b)) in enumerate(key_metrics.items()):
                                with columns[idx]:
                                    st.metric(label=f"{label} ({portfolio_a['name']})", value=value_a)
                                    st.metric(label=f"{label} ({portfolio_b['name']})", value=value_b)

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

                        with tabs[0]:
                            st.header("📈 Portfolios Performance Overview")
                            # Determine the aligned start and end dates
                            aligned_start_date = common_dates.min().strftime('%Y-%m-%d')
                            aligned_end_date = common_dates.max().strftime('%Y-%m-%d')
                            
                            st.write(f"**Aligned Date Range:** {aligned_start_date} to {aligned_end_date}")
                            st.write(f"**Portfolio A:** {portfolio_a['name']}")
                            st.write(f"**Original Start Date:** {portfolio_a['start_date'].strftime('%Y-%m-%d')}")
                            st.write(f"**Portfolio B:** {portfolio_b['name']}")
                            st.write(f"**Original Start Date:** {portfolio_b['start_date'].strftime('%Y-%m-%d')}")

                        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'
                                    ],
                                    f'{portfolio_a["name"]}': [
                                        metrics_a['Start Balance'],
                                        metrics_a['End Balance'],
                                        metrics_a['Annualized Return (CAGR)'],
                                        metrics_a['Standard Deviation (Annualized)'],
                                        metrics_a['Best Year'],
                                        metrics_a['Worst Year'],
                                        metrics_a['Maximum Drawdown'],
                                        metrics_a['Sharpe Ratio'],
                                        metrics_a['Sortino Ratio'],
                                        metrics_a['Benchmark Correlation']
                                    ],
                                    f'{portfolio_b["name"]}': [
                                        metrics_b['Start Balance'],
                                        metrics_b['End Balance'],
                                        metrics_b['Annualized Return (CAGR)'],
                                        metrics_b['Standard Deviation (Annualized)'],
                                        metrics_b['Best Year'],
                                        metrics_b['Worst Year'],
                                        metrics_b['Maximum Drawdown'],
                                        metrics_b['Sharpe Ratio'],
                                        metrics_b['Sortino Ratio'],
                                        metrics_b['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("🧮 Advanced Metrics")
                                performance_data_advanced = {
                                    'Metric': list(metrics_a.keys()),
                                    f'{portfolio_a["name"]}': list(metrics_a.values()),
                                    f'{portfolio_b["name"]}': list(metrics_b.values())
                                }
                                performance_df_advanced = pd.DataFrame(performance_data_advanced).set_index('Metric')

                                # Replace NaN with empty strings for better visualization
                                performance_df_advanced = performance_df_advanced.replace(np.nan, "")
                                # Drop rows where both Portfolio A and Portfolio B values are NaN
                                performance_df_advanced_clean = performance_df_advanced.dropna(how='all', subset=[f'{portfolio_a["name"]}', f'{portfolio_b["name"]}'])

                                st.table(performance_df_advanced_clean)
                                st.markdown("---")
                                st.write("**Note:** Some metrics may be specific to certain portfolios or may not be directly comparable. Ensure to interpret the metrics in the context of each portfolio's strategy and objectives.")

                            st.subheader("🔍 Risk Factor Attribution Analysis")
                            
                            # Define date range based on the overlapping index
                            start_date = common_dates.min().strftime('%Y-%m-%d')
                            end_date = common_dates.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:
                                # Align factors frequency with portfolios
                                if isinstance(returns_a.index, pd.DatetimeIndex):
                                    if returns_a.index.freq is None:
                                        inferred_freq = pd.infer_freq(returns_a.index)
                                        if inferred_freq is None:
                                            inferred_freq = 'D'  # Set default frequency to daily
                                    else:
                                        inferred_freq = returns_a.index.freq
                                    
                                    try:
                                        factors = factors.asfreq(inferred_freq, method='ffill')
                                    except TypeError as e:
                                        st.error(f"Error setting frequency for factors: {e}")
                                        st.stop()
                                else:
                                    st.error("Returns index is not a DatetimeIndex.")
                                    st.stop()
                                
                                # Calculate Attribution for Portfolio A
                                attribution_a, error_a = calculate_risk_factor_attribution(returns_a, factors)
                                
                                # Calculate Attribution for Portfolio B
                                attribution_b, error_b = calculate_risk_factor_attribution(returns_b, factors)
                                
                                if error_a or error_b:
                                    st.error("Risk factor attribution analysis is unavailable for one or both portfolios.")
                                else:
                                    if attribution_a.empty or attribution_b.empty:
                                        st.write("Risk factor attribution analysis is unavailable for one or both portfolios.")
                                    else:
                                        st.write(f"**Risk Factor Attribution for {portfolio_a['name']}:**")
                                        st.table(attribution_a)
                                        fig_attribution_a = px.bar(
                                            attribution_a,
                                            x='Factor',
                                            y='Contribution (%)',
                                            title=f'Risk Factor Attribution for {portfolio_a["name"]}',
                                            labels={'Contribution (%)': 'Contribution (%)'},
                                            template='plotly_dark'
                                        )
                                        st.plotly_chart(fig_attribution_a, use_container_width=True)

                                        st.write(f"**Risk Factor Attribution for {portfolio_b['name']}:**")
                                        st.table(attribution_b)
                                        fig_attribution_b = px.bar(
                                            attribution_b,
                                            x='Factor',
                                            y='Contribution (%)',
                                            title=f'Risk Factor Attribution for {portfolio_b["name"]}',
                                            labels={'Contribution (%)': 'Contribution (%)'},
                                            template='plotly_dark'
                                        )
                                        st.plotly_chart(fig_attribution_b, use_container_width=True)

                                        # Combined Bar Chart for Comparison
                                        combined_attribution = pd.concat([
                                            attribution_a.set_index('Factor')['Contribution (%)'],
                                            attribution_b.set_index('Factor')['Contribution (%)']
                                        ], axis=1)
                                        combined_attribution.columns = [portfolio_a['name'], portfolio_b['name']]
                                        combined_attribution = combined_attribution.fillna(0)

                                        st.markdown(f"### 📊 Risk Factor Attribution Comparison")
                                        fig_combined_attribution = px.bar(
                                            combined_attribution,
                                            x=combined_attribution.index,
                                            y=combined_attribution.columns,
                                            title='Risk Factor Attribution Comparison',
                                            labels={'value': 'Contribution (%)', 'Factor': 'Risk Factor'},
                                            template='plotly_dark',
                                            barmode='group'
                                        )
                                        st.plotly_chart(fig_combined_attribution, use_container_width=True)

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

                            st.markdown("### 📈 Drawdowns for Portfolio B")
                            portfolio_b_drawdowns = get_drawdown_details(cum_returns_b)
                            if portfolio_b_drawdowns:
                                portfolio_b_drawdowns_df = pd.DataFrame(portfolio_b_drawdowns)
                                st.table(portfolio_b_drawdowns_df)
                            else:
                                st.write("No drawdowns detected for Portfolio B.")

                        with tabs[4]:
                            st.header("📊 Visualizations")
                            if not cum_returns_a.empty and not cum_returns_b.empty:
                                plot_growth_comparison(cum_returns_a, cum_returns_b, labels=[portfolio_a['name'], portfolio_b['name']])
                            else:
                                st.warning("Insufficient data to display Growth Comparison. Ensure both portfolios have data.")
                        
                            portfolio_a_drawdown_series = (cum_returns_a / cum_returns_a.expanding().max() - 1) * 100
                            portfolio_b_drawdown_series = (cum_returns_b / cum_returns_b.expanding().max() - 1) * 100
                            plot_drawdown_comparison(portfolio_a_drawdown_series, portfolio_b_drawdown_series, labels=[portfolio_a['name'], portfolio_b['name']])
                        
                            st.subheader("📈 Compound Annual Growth Rate (CAGR) Over Time")
                            if selected_time_frames:
                                plot_cagr_comparison(cum_returns_a, cum_returns_b, time_frames=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")
                            # For portfolio A
                            if isinstance(asset_returns_a, pd.DataFrame):
                                # Ensure the index is named 'Date'
                                if asset_returns_a.index.name != 'Date':
                                    asset_returns_a = asset_returns_a.copy()
                                    asset_returns_a.index.name = 'Date'
                                
                                melted_returns_a = asset_returns_a.reset_index().melt(id_vars='Date', var_name='Asset', value_name='Return')
                                fig_box_plot_a = px.box(
                                    melted_returns_a,
                                    x='Asset',
                                    y='Return',
                                    title='Returns Distribution Box Plot - Portfolio A',
                                    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_a, use_container_width=True)
                                st.markdown("**Interpretation:** The box plot visualizes the distribution of returns for each asset in Portfolio A. 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(asset_returns_a, pd.Series):
                                # Ensure the index is named 'Date'
                                if asset_returns_a.index.name != 'Date':
                                    asset_returns_a = asset_returns_a.copy()
                                    asset_returns_a.index.name = 'Date'
                                
                                melted_returns_a = asset_returns_a.reset_index().rename(columns={'Date': 'Date', 'Return': 'Return'})
                                fig_box_plot_a = px.box(
                                    melted_returns_a,
                                    y='Return',
                                    title='Returns Distribution Box Plot - Portfolio A',
                                    labels={'Return': 'Returns'},
                                    points='all',
                                    hover_data=['Return'],
                                    template='plotly_dark'
                                )
                                st.plotly_chart(fig_box_plot_a, use_container_width=True)
                                st.markdown("**Interpretation:** The box plot visualizes the distribution of returns for Portfolio A. 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 for Portfolio A is neither a DataFrame nor a Series.")

                            # For portfolio B
                            if isinstance(asset_returns_b, pd.DataFrame):
                                # Ensure the index is named 'Date'
                                if asset_returns_b.index.name != 'Date':
                                    asset_returns_b = asset_returns_b.copy()
                                    asset_returns_b.index.name = 'Date'
                                
                                melted_returns_b = asset_returns_b.reset_index().melt(id_vars='Date', var_name='Asset', value_name='Return')
                                fig_box_plot_b = px.box(
                                    melted_returns_b,
                                    x='Asset',
                                    y='Return',
                                    title='Returns Distribution Box Plot - Portfolio B',
                                    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_b, use_container_width=True)
                                st.markdown("**Interpretation:** The box plot visualizes the distribution of returns for each asset in Portfolio B. 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(asset_returns_b, pd.Series):
                                # Ensure the index is named 'Date'
                                if asset_returns_b.index.name != 'Date':
                                    asset_returns_b = asset_returns_b.copy()
                                    asset_returns_b.index.name = 'Date'
                                
                                melted_returns_b = asset_returns_b.reset_index().rename(columns={'Date': 'Date', 'Return': 'Return'})
                                fig_box_plot_b = px.box(
                                    melted_returns_b,
                                    y='Return',
                                    title='Returns Distribution Box Plot - Portfolio B',
                                    labels={'Return': 'Returns'},
                                    points='all',
                                    hover_data=['Return'],
                                    template='plotly_dark'
                                )
                                st.plotly_chart(fig_box_plot_b, use_container_width=True)
                                st.markdown("**Interpretation:** The box plot visualizes the distribution of returns for Portfolio B. 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 for Portfolio B is neither a DataFrame nor a Series.")

                            # Correlation Heatmap Comparison
                            st.subheader("🔥 Correlation Heatmap Comparison")
                            plot_correlation_heatmap_comparison(asset_returns_a, asset_returns_b)
                            
                            # Efficient Frontier Comparison
                            st.subheader("📈 Efficient Frontier Comparison")
                            plot_efficient_frontier_comparison(asset_returns_a, asset_returns_b, num_portfolios=1000, rf=portfolio_a['rf_rate'])

                            # Risk-Return Attribution Comparison
                            st.subheader("🔍 Risk-Return Attribution Comparison")
                            plot_risk_return_attribution_comparison(
                                returns_a, weights_a, 
                                returns_b, weights_b, 
                                label_a=portfolio_a['name'], 
                                label_b=portfolio_b['name']
                            )

                            # Recommendations
                            st.markdown("### 💡 Recommendations")
                            recommendations_a = generate_recommendations(
                                allocations=[alloc for alloc in portfolio_a['allocations'] if alloc > 0],
                                available_selected=[ticker for ticker in available_a if portfolio_a['allocations'][available_a.index(ticker)] > 0]
                            )
                            recommendations_b = generate_recommendations(
                                allocations=[alloc for alloc in portfolio_b['allocations'] if alloc > 0],
                                available_selected=[ticker for ticker in available_b if portfolio_b['allocations'][available_b.index(ticker)] > 0]
                            )
                            
                            if recommendations_a:
                                st.write(f"**{portfolio_a['name']} Recommendations:**")
                                for rec in recommendations_a:
                                    st.write(rec)
                            else:
                                st.success(f"{portfolio_a['name']} allocations are well-balanced!")
                            
                            if recommendations_b:
                                st.write(f"**{portfolio_b['name']} Recommendations:**")
                                for rec in recommendations_b:
                                    st.write(rec)
                            else:
                                st.success(f"{portfolio_b['name']} allocations are well-balanced!")
                                
                            # Final Score
                            st.markdown("### 📝 Final Score")
                            portfolio_score_a = calculate_final_score(metrics_a, metrics_b)
                            portfolio_score_b = calculate_final_score(metrics_b, metrics_a)
                            st.write(f"**{portfolio_a['name']}:** {portfolio_score_a:.2f} / 100")
                            st.write(f"**{portfolio_b['name']}:** {portfolio_score_b:.2f} / 100")

                            # Store results
                            st.session_state.backtest_results = {
                                'returns_a': returns_a,
                                'cum_returns_a': cum_returns_a,
                                'weights_a': weights_a,
                                'price_data_a': price_data_a[available_a],
                                'metrics_a': metrics_a,
                                'returns_b': returns_b,
                                'cum_returns_b': cum_returns_b,
                                'weights_b': weights_b,
                                'price_data_b': price_data_b[available_b],
                                'metrics_b': metrics_b,
                                'performance_df_stats_a': performance_df_stats.loc[:, portfolio_a['name']],
                                'performance_df_advanced_a': performance_df_advanced_clean[[f'{portfolio_a["name"]}']],
                                'performance_df_stats_b': performance_df_stats.loc[:, portfolio_b['name']],
                                'performance_df_advanced_b': performance_df_advanced_clean[[f'{portfolio_b["name"]}']]
                            }

            st.success("Backtest completed successfully!")
    else:
        st.warning("Please add at least one portfolio to run backtest.")