# 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):
                        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):
                        fig_box_plot = px.box(
                            asset_returns.to_frame(name='Return'),
                            y='Return',
                            title='Returns Distribution Box Plot',
                            labels={'Return': 'Returns'},
                            points='all',
                            hover_data=['Return'],
                            template='plotly_dark'
                        )
                        st.plotly_chart(fig_box_plot, use_container_width=True)
                        st.markdown("**Interpretation:** The box plot visualizes the distribution of returns for the portfolio. The box represents the interquartile range (IQR), the line inside the box indicates the median, and the whiskers show the range of the data. Outliers are displayed as individual points.")
                    else:
                        st.warning("Returns data is neither a DataFrame nor a Series.")

                    # Heatmap of Correlations Between Assets
                    st.subheader("🔥 Correlation Heatmap of Assets")
                    plot_correlation_heatmap(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.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

                    # 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 (metric, (label, value_a, value_b)) in key_metrics.items():
                                with columns[list(key_metrics.keys()).index(metric)]:
                                    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 the 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 the 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):
                        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):
                        fig_box_plot_a = px.box(
                            asset_returns_a.to_frame(name='Return'),
                            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):
                        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):
                        fig_box_plot_b = px.box(
                            asset_returns_b.to_frame(name='Return'),
                            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,  # Ensure separate stats
                        'performance_df_advanced_a': performance_df_advanced_clean,
                        'performance_df_stats_b': performance_df_stats,  # Ensure separate stats
                        'performance_df_advanced_b': performance_df_advanced_clean
                    }
            st.success("Backtest completed successfully!")
    else:
        st.warning("Please add at least one portfolio to run backtest.")

SyntaxError: File "/www/wwwroot/portfolio_optimizer/components/run_backtest.py", line 528 elif comparison_type == "Portfolio vs Portfolio" and portfolio1 and portfolio2 and portfolio1 != portfolio2: ^ SyntaxError: invalid syntax
Traceback:
File "/www/server/pyporject_evn/portfolio_optimizer_venv/lib/python3.12/site-packages/streamlit/runtime/scriptrunner/exec_code.py", line 88, in exec_func_with_error_handling
    result = func()
             ^^^^^^
File "/www/server/pyporject_evn/portfolio_optimizer_venv/lib/python3.12/site-packages/streamlit/runtime/scriptrunner/script_runner.py", line 579, in code_to_exec
    exec(code, module.__dict__)
File "/www/wwwroot/portfolio_optimizer/pages/2_Run_Backtest.py", line 3, in <module>
    from components.run_backtest import run_backtest_ui