Error: 'numpy.ndarray' object is not callable
from flask import Flask, request, jsonify
from flask_cors import CORS
import yfinance as yf
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import json
import os
from datetime import datetime
import csv
import io
from scipy import stats
from functools import lru_cache
import hashlib
from datetime import datetime, timedelta
import pickle
from scipy.stats import spearmanr

app = Flask(__name__)
CORS(app)

CACHE_DIR = 'cache'
os.makedirs(CACHE_DIR, exist_ok=True)

def get_cache_key(ticker, start_date):
    """Generate a unique cache key for stock data"""
    return hashlib.md5(f"{ticker}_{start_date}".encode()).hexdigest()

def save_to_cache(key, data):
    """Save data to cache file"""
    cache_file = os.path.join(CACHE_DIR, f"{key}.pkl")
    with open(cache_file, 'wb') as f:
        pickle.dump({
            'data': data,
            'timestamp': datetime.now()
        }, f)

def load_from_cache(key, max_age_hours=24):
    """Load data from cache if it exists and is not too old"""
    cache_file = os.path.join(CACHE_DIR, f"{key}.pkl")
    if os.path.exists(cache_file):
        with open(cache_file, 'rb') as f:
            cached = pickle.load(f)
            age = datetime.now() - cached['timestamp']
            if age < timedelta(hours=max_age_hours):
                return cached['data']
    return None

@lru_cache(maxsize=100)
def get_ticker_info(ticker):
    """Cached function to get ticker information"""
    try:
        stock = yf.Ticker(ticker)
        return {
            'sector': stock.info.get('sector', 'Unknown'),
            'industry': stock.info.get('industry', 'Unknown'),
            'name': stock.info.get('longName', ticker)
        }
    except:
        return {
            'sector': 'Unknown',
            'industry': 'Unknown',
            'name': ticker
        }

def validate_ticker(ticker):
    """Validate if a ticker exists"""
    try:
        # Strip whitespace and check if ticker is empty
        ticker = ticker.strip()
        if not ticker:
            return False
            
        stock = yf.Ticker(ticker)
        # Try to get info - will fail if ticker doesn't exist
        info = stock.info
        return bool(info)  # Return True only if info is not empty
    except:
        return False

def calculate_sortino_ratio(returns, risk_free_rate=0.02):
    """Calculate Sortino ratio using negative returns only"""
    negative_returns = returns[returns < 0]
    if len(negative_returns) == 0:
        return np.inf
    
    downside_std = np.sqrt(np.mean(negative_returns**2))
    excess_return = returns.mean() * 252 - risk_free_rate
    return excess_return / (downside_std * np.sqrt(252)) if downside_std != 0 else np.inf

def calculate_beta(portfolio_returns, market_returns):
    """Calculate portfolio beta relative to market"""
    try:
        # Ensure both inputs are pandas Series
        if isinstance(portfolio_returns, pd.Series):
            portfolio_returns = portfolio_returns.values
        if isinstance(market_returns, pd.Series):
            market_returns = market_returns.values
        elif isinstance(market_returns, pd.DataFrame):
            market_returns = market_returns.iloc[:, 0].values

        # Ensure both arrays are 1D
        portfolio_returns = portfolio_returns.flatten()
        market_returns = market_returns.flatten()

        # Calculate beta using linear regression
        slope, _, _, _, _ = stats.linregress(market_returns, portfolio_returns)
        return float(slope)
    except Exception as e:
        print(f"Error in calculate_beta: {str(e)}")
        return 1.0

def calculate_alpha(portfolio_returns, market_returns, risk_free_rate=0.02):
    """Calculate Jensen's Alpha"""
    try:
        # Ensure both inputs are pandas Series
        if isinstance(portfolio_returns, pd.Series):
            portfolio_returns = portfolio_returns.values
        if isinstance(market_returns, pd.Series):
            market_returns = market_returns.values
        elif isinstance(market_returns, pd.DataFrame):
            market_returns = market_returns.iloc[:, 0].values

        # Ensure both arrays are 1D
        portfolio_returns = portfolio_returns.flatten()
        market_returns = market_returns.flatten()

        # Calculate alpha using linear regression
        slope, intercept, _, _, _ = stats.linregress(market_returns, portfolio_returns)
        alpha = intercept + (risk_free_rate * (1 - slope))
        return float(alpha * 252)  # Annualize alpha
    except Exception as e:
        print(f"Error in calculate_alpha: {str(e)}")
        return 0.0

def calculate_portfolio_metrics(portfolio_data, weights, benchmark_returns=None):
    """Calculate portfolio performance metrics"""
    try:
        # Calculate portfolio returns
        returns = portfolio_data.pct_change()
        
        # Convert weights to numpy array and ensure proper shape
        weights = np.array(weights).reshape(-1, 1)
        
        # Calculate portfolio returns using matrix multiplication
        portfolio_returns = np.sum(returns.values * weights.T, axis=1)
        portfolio_returns = pd.Series(portfolio_returns, index=returns.index)

        # Basic metrics
        annual_return = portfolio_returns.mean() * 252
        annual_volatility = portfolio_returns.std() * np.sqrt(252)
        sharpe_ratio = annual_return / annual_volatility if annual_volatility != 0 else 0

        # Calculate drawdown
        cumulative_returns = (1 + portfolio_returns).cumprod()
        rolling_max = cumulative_returns.expanding().max()
        drawdowns = (cumulative_returns - rolling_max) / rolling_max
        max_drawdown = drawdowns.min()

        # Advanced metrics
        sortino_ratio = calculate_sortino_ratio(portfolio_returns)

        metrics = {
            'annual_return': float(annual_return) if not np.isnan(annual_return) else 0,
            'annual_volatility': float(annual_volatility) if not np.isnan(annual_volatility) else 0,
            'sharpe_ratio': float(sharpe_ratio) if not np.isnan(sharpe_ratio) else 0,
            'sortino_ratio': float(sortino_ratio) if not np.isnan(sortino_ratio) and not np.isinf(sortino_ratio) else 0,
            'max_drawdown': float(max_drawdown) if not np.isnan(max_drawdown) else 0
        }

        # Add market-relative metrics if benchmark data is provided
        if benchmark_returns is not None:
            # Ensure data alignment
            common_index = portfolio_returns.index.intersection(benchmark_returns.index)
            aligned_portfolio_returns = portfolio_returns.reindex(common_index)
            aligned_benchmark_returns = benchmark_returns.reindex(common_index)

            # Calculate beta and alpha
            beta = calculate_beta(aligned_portfolio_returns, aligned_benchmark_returns)
            alpha = calculate_alpha(aligned_portfolio_returns, aligned_benchmark_returns)
            
            metrics.update({
                'beta': float(beta),
                'alpha': float(alpha)
            })

        return metrics
    except Exception as e:
        print(f"Error in calculate_portfolio_metrics: {str(e)}")
        raise

def save_portfolio(portfolio_data):
    """Save portfolio to JSON file"""
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    filename = f"portfolio_{timestamp}.json"
    
    # Create portfolios directory if it doesn't exist
    os.makedirs('portfolios', exist_ok=True)
    
    filepath = os.path.join('portfolios', filename)
    with open(filepath, 'w') as f:
        json.dump(portfolio_data, f)
    
    return filename

def load_portfolio(filename):
    """Load portfolio from JSON file"""
    filepath = os.path.join('portfolios', filename)
    try:
        with open(filepath, 'r') as f:
            return json.load(f)
    except:
        return None

def generate_csv_report(results):
    """Generate CSV report from backtest results"""
    output = io.StringIO()
    writer = csv.writer(output)
    
    # Write portfolio metrics
    writer.writerow(['Portfolio Metrics'])
    writer.writerow(['Metric', 'Portfolio', 'Benchmark'])
    for key in results['portfolio_metrics']:
        writer.writerow([
            key,
            results['portfolio_metrics'][key],
            results['benchmark_metrics'].get(key, 'N/A')
        ])
    
    # Write historical performance
    writer.writerow([])
    writer.writerow(['Historical Performance'])
    writer.writerow(['Date', 'Portfolio Value', 'Benchmark Value'])
    for i, date in enumerate(results['historical_performance']['dates']):
        writer.writerow([
            date,
            results['historical_performance']['portfolio_values'][i],
            results['historical_performance']['benchmark_values'][i]
        ])
    
    return output.getvalue()

def get_sector_info(ticker):
    """Get sector information for a ticker"""
    try:
        stock = yf.Ticker(ticker)
        return stock.info.get('sector', 'Unknown')
    except:
        return 'Unknown'

def calculate_attribution(portfolio_data, weights, benchmark_data):
    """Calculate performance attribution"""
    try:
        # Ensure data alignment
        common_dates = portfolio_data.index.intersection(benchmark_data.index)
        portfolio_data = portfolio_data.reindex(common_dates)
        benchmark_data = benchmark_data.reindex(common_dates)
        
        # Convert weights to numpy array and ensure proper shape
        weights = np.array(weights, dtype=float)
        
        # Calculate returns
        portfolio_returns = portfolio_data.pct_change()
        benchmark_returns = benchmark_data.pct_change()
        
        # Calculate weighted returns for portfolio
        weighted_returns = pd.DataFrame()
        for i, col in enumerate(portfolio_returns.columns):
            weighted_returns[col] = portfolio_returns[col] * weights[i]
        
        # Calculate attribution metrics
        attribution = {}
        
        # Get sector information
        sectors = {}
        for ticker in portfolio_data.columns:
            sectors[ticker] = get_sector_info(ticker)
        
        # Calculate sector attribution
        sector_attribution = {}
        for sector in set(sectors.values()):
            sector_tickers = [t for t, s in sectors.items() if s == sector]
            if sector_tickers:
                # Calculate sector weight
                sector_indices = [list(portfolio_data.columns).index(t) for t in sector_tickers]
                sector_weight = np.sum(weights[sector_indices])
                
                # Calculate sector returns
                sector_returns = weighted_returns[sector_tickers].sum(axis=1)
                mean_sector_return = float(sector_returns.mean() * 252)  # Annualize
                
                sector_attribution[sector] = {
                    'weight': float(sector_weight),
                    'return': mean_sector_return,
                    'contribution': float(mean_sector_return * sector_weight)
                }
        
        # Calculate factor attribution
        total_portfolio_returns = weighted_returns.sum(axis=1)
        benchmark_returns_series = pd.Series(benchmark_returns, index=benchmark_data.index)
        
        # Perform regression
        regression = stats.linregress(
            benchmark_returns_series.dropna(),
            total_portfolio_returns.dropna()
        )
        
        factor_attribution = {
            'market_beta': float(regression.slope),
            'alpha': float(regression.intercept * 252),  # Annualize alpha
            'r_squared': float(regression.rvalue ** 2)
        }
        
        attribution['sector'] = sector_attribution
        attribution['factor'] = factor_attribution
        
        return clean_for_json(attribution)
        
    except Exception as e:
        print(f"Error in calculate_attribution: {str(e)}")
        print(f"Portfolio data shape: {portfolio_data.shape}")
        print(f"Weights shape: {weights.shape}")
        print(f"Benchmark data shape: {benchmark_data.shape}")
        raise

def calculate_rebalancing_impact(portfolio_data, initial_weights, rebalancing_frequency='M'):
    """
    Calculate portfolio performance with different rebalancing strategies
    frequency options: 'M' (monthly), 'Q' (quarterly), 'Y' (yearly), None (buy and hold)
    """
    results = {}
    
    # Calculate returns for different strategies
    for freq in [None, 'M', 'Q', 'Y']:
        # Initialize portfolio values
        portfolio_value = 1.0
        current_weights = initial_weights.copy()
        values = []
        
        # Get daily returns
        daily_returns = portfolio_data.pct_change()
        
        # Simulate portfolio growth
        for date in daily_returns.index:
            # Update portfolio value
            daily_return = (daily_returns.loc[date] * current_weights).sum()
            portfolio_value *= (1 + daily_return)
            values.append(portfolio_value)
            
            # Update weights due to price changes
            if freq is not None:
                asset_returns = 1 + daily_returns.loc[date]
                current_weights = current_weights * asset_returns
                current_weights = current_weights / current_weights.sum()
                
                # Rebalance if it's a rebalancing date
                if freq == 'M' and date.is_month_end:
                    current_weights = initial_weights.copy()
                elif freq == 'Q' and date.is_quarter_end:
                    current_weights = initial_weights.copy()
                elif freq == 'Y' and date.is_year_end:
                    current_weights = initial_weights.copy()
        
        # Calculate strategy metrics
        strategy_returns = pd.Series(values).pct_change()
        
        results[str(freq)] = {
            'final_value': float(values[-1]),
            'annualized_return': float(strategy_returns.mean() * 252),
            'volatility': float(strategy_returns.std() * np.sqrt(252)),
            'values': [float(v) for v in values],
        }
    
    return results

def fetch_stock_data(ticker, start_date):
    """Fetch stock data with caching"""
    cache_key = get_cache_key(ticker, start_date)
    cached_data = load_from_cache(cache_key)
    
    if cached_data is not None:
        return cached_data
    
    try:
        stock_data = yf.download(ticker, start=start_date, progress=False)
        if not stock_data.empty:
            save_to_cache(cache_key, stock_data)
        return stock_data
    except Exception as e:
        raise Exception(f"Failed to fetch data for {ticker}: {str(e)}")

@app.route('/api/validate-ticker', methods=['POST'])
def validate_ticker_endpoint():
    """Endpoint to validate stock tickers"""
    data = request.json
    ticker = data.get('ticker', '')
    
    return jsonify({
        'success': True,
        'valid': validate_ticker(ticker)
    })

@app.route('/api/backtest', methods=['POST'])
def backtest():
    try:
        data = request.json
        portfolio = data['portfolio']
        
        # Validate input data
        if not portfolio or not all(stock.get('ticker') and stock.get('weight') for stock in portfolio):
            return jsonify({
                'success': False,
                'error': 'Invalid portfolio data. Please provide valid tickers and weights.'
            }), 400
            
        # Strip whitespace from tickers
        portfolio = [{
            'ticker': stock['ticker'].strip().upper(),
            'weight': stock['weight']
        } for stock in portfolio]
        
        start_date = data.get('startDate', (datetime.now() - timedelta(days=365)).strftime('%Y-%m-%d'))
        benchmark = data.get('benchmark', 'SPY')
        
        print(f"Processing portfolio: {portfolio}")
        print(f"Start date: {start_date}")
        print(f"Benchmark: {benchmark}")
        
        # Get portfolio data with caching and align dates
        portfolio_data = pd.DataFrame()
        start_date = pd.to_datetime(start_date)
        
        # Fetch all data
        stock_data_dict = {}
        for ticker in [stock['ticker'] for stock in portfolio]:
            stock_data = fetch_stock_data(ticker, start_date)
            if stock_data.empty:
                return jsonify({
                    'success': False,
                    'error': f'No data available for {ticker}'
                }), 400
            stock_data_dict[ticker] = stock_data['Adj Close']

        # Fetch benchmark data
        benchmark_data = fetch_stock_data(benchmark, start_date)['Adj Close']
        if benchmark_data.empty:
            return jsonify({
                'success': False,
                'error': f'No data available for benchmark {benchmark}'
            }), 400

        # Find common dates
        all_series = list(stock_data_dict.values()) + [benchmark_data]
        common_dates = pd.Index.intersection(*[s.index for s in all_series])

        if len(common_dates) == 0:
            return jsonify({
                'success': False,
                'error': 'No overlapping dates found for the selected securities'
            }), 400

        # Align all data
        for ticker in [stock['ticker'] for stock in portfolio]:
            portfolio_data[ticker] = stock_data_dict[ticker].reindex(common_dates)
        benchmark_data = benchmark_data.reindex(common_dates)

        # Calculate returns
        weights = [float(stock['weight']) / 100 for stock in portfolio]
        portfolio_returns = portfolio_data.pct_change().dot(weights)
        
        # Ensure benchmark data is 1-dimensional
        benchmark_returns = pd.Series(
            benchmark_data.pct_change().values.flatten(),  # Flatten the array
            index=benchmark_data.index
        )

        # Calculate metrics
        portfolio_metrics = calculate_portfolio_metrics(portfolio_data, weights, benchmark_returns)
        
        # Create benchmark DataFrame with proper index and flattened values
        benchmark_df = pd.DataFrame(
            {benchmark: benchmark_data.values.flatten()},
            index=benchmark_data.index
        )
        benchmark_metrics = calculate_portfolio_metrics(
            benchmark_df,
            [1.0]
        )

        # Calculate cumulative returns
        portfolio_cumulative = (1 + portfolio_returns).cumprod()
        benchmark_cumulative = (1 + benchmark_returns).cumprod()

        # Clean data for JSON serialization
        response_data = {
            'success': True,
            'portfolio_metrics': clean_for_json(portfolio_metrics),
            'benchmark_metrics': clean_for_json(benchmark_metrics),
            'historical_performance': {
                'dates': common_dates.strftime('%Y-%m-%d').tolist(),
                'portfolio_values': clean_for_json(portfolio_cumulative),
                'benchmark_values': clean_for_json(benchmark_cumulative)
            }
        }

        return jsonify(response_data)

    except Exception as e:
        print(f"Error in backtest: {str(e)}")
        import traceback
        traceback.print_exc()
        return jsonify({
            'success': False,
            'error': str(e)
        }), 400

@app.route('/api/portfolio/save', methods=['POST'])
def save_portfolio_endpoint():
    """Save portfolio configuration"""
    try:
        portfolio_data = request.json
        filename = save_portfolio(portfolio_data)
        return jsonify({
            'success': True,
            'filename': filename
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'error': str(e)
        }), 400

@app.route('/api/portfolio/load', methods=['GET'])
def list_portfolios():
    """List all saved portfolios"""
    try:
        os.makedirs('portfolios', exist_ok=True)
        portfolios = []
        for filename in os.listdir('portfolios'):
            if filename.endswith('.json'):
                portfolio = load_portfolio(filename)
                if portfolio:
                    portfolios.append({
                        'filename': filename,
                        'portfolio': portfolio
                    })
        return jsonify({
            'success': True,
            'portfolios': portfolios
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'error': str(e)
        }), 400

@app.route('/api/export/csv', methods=['POST'])
def export_csv():
    """Export backtest results as CSV"""
    try:
        results = request.json
        csv_data = generate_csv_report(results)
        return jsonify({
            'success': True,
            'csv_data': csv_data
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'error': str(e)
        }), 400

@app.route('/api/attribution', methods=['POST'])
def get_attribution():
    try:
        data = request.json
        portfolio = data['portfolio']
        start_date = pd.to_datetime(data.get('startDate'))
        benchmark = data.get('benchmark', 'SPY')
        
        # Fetch data
        portfolio_data = pd.DataFrame()
        for stock in portfolio:
            stock_data = fetch_stock_data(stock['ticker'].strip().upper(), start_date)
            if stock_data.empty:
                return jsonify({
                    'success': False,
                    'error': f'No data available for {stock["ticker"]}'
                }), 400
            portfolio_data[stock['ticker']] = stock_data['Adj Close']
            
        benchmark_data = fetch_stock_data(benchmark, start_date)['Adj Close']
        if benchmark_data.empty:
            return jsonify({
                'success': False,
                'error': f'No data available for benchmark {benchmark}'
            }), 400
        
        # Find common dates
        all_series = list(portfolio_data.values()) + [benchmark_data]
        common_dates = pd.Index.intersection(*[s.index for s in all_series])
        
        if len(common_dates) == 0:
            return jsonify({
                'success': False,
                'error': 'No overlapping dates found for the selected securities'
            }), 400
            
        # Align all data
        portfolio_data = portfolio_data.reindex(common_dates)
        benchmark_data = benchmark_data.reindex(common_dates)
        
        # Convert weights to proper format
        weights = np.array([float(stock['weight']) / 100 for stock in portfolio])
        
        # Calculate attribution
        attribution = calculate_attribution(portfolio_data, weights, benchmark_data)
        
        return jsonify({
            'success': True,
            'attribution': attribution
        })
        
    except Exception as e:
        print(f"Error in get_attribution: {str(e)}")
        return jsonify({
            'success': False,
            'error': str(e)
        }), 400

@app.route('/api/rebalance', methods=['POST'])
def analyze_rebalancing():
    """Analyze portfolio performance with different rebalancing strategies"""
    try:
        data = request.json
        portfolio = data['portfolio']
        start_date = data.get('startDate')
        
        # Fetch data
        portfolio_data = pd.DataFrame()
        weights = []
        
        for stock in portfolio:
            stock_data = fetch_stock_data(stock['ticker'], start_date)
            portfolio_data[stock['ticker']] = stock_data['Adj Close']
            weights.append(stock['weight'])
        
        weights = np.array(weights)
        
        # Calculate rebalancing impact
        rebalancing_results = calculate_rebalancing_impact(portfolio_data, weights)
        
        return jsonify({
            'success': True,
            'rebalancing_results': rebalancing_results
        })
        
    except Exception as e:
        return jsonify({
            'success': False,
            'error': str(e)
        }), 400

@app.route('/api/cache/clear', methods=['POST'])
def clear_cache():
    """Clear all cached data"""
    try:
        for file in os.listdir(CACHE_DIR):
            if file.endswith('.pkl'):
                os.remove(os.path.join(CACHE_DIR, file))
        return jsonify({
            'success': True,
            'message': 'Cache cleared successfully'
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'error': str(e)
        }), 400

@app.route('/api/cache/status', methods=['GET'])
def cache_status():
    """Get cache status information"""
    try:
        cache_files = [f for f in os.listdir(CACHE_DIR) if f.endswith('.pkl')]
        total_size = sum(os.path.getsize(os.path.join(CACHE_DIR, f)) for f in cache_files)
        
        return jsonify({
            'success': True,
            'status': {
                'cached_items': len(cache_files),
                'total_size_mb': total_size / (1024 * 1024),
                'cache_dir': CACHE_DIR
            }
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'error': str(e)
        }), 400

def compare_portfolios(portfolios, start_date):
    """Compare multiple portfolios"""
    try:
        results = {}
        all_data = pd.DataFrame()
        
        # Fetch data for all unique tickers
        unique_tickers = set()
        for portfolio in portfolios:
            unique_tickers.update([stock['ticker'] for stock in portfolio['portfolio']])
            
        for ticker in unique_tickers:
            stock_data = fetch_stock_data(ticker, start_date)
            all_data[ticker] = stock_data['Adj Close']
        
        # Calculate metrics for each portfolio
        for idx, portfolio_data in enumerate(portfolios):
            portfolio_name = portfolio_data.get('name', f'Portfolio {idx + 1}')
            portfolio_stocks = portfolio_data['portfolio']
            
            # Extract relevant data and weights
            tickers = [stock['ticker'] for stock in portfolio_stocks]
            weights = [stock['weight'] for stock in portfolio_stocks]
            portfolio_price_data = all_data[tickers]
            
            # Calculate returns and metrics
            portfolio_returns = portfolio_price_data.pct_change().dot(weights)
            portfolio_cumulative = (1 + portfolio_returns).cumprod()
            
            # Calculate metrics
            metrics = calculate_portfolio_metrics(portfolio_price_data, weights)
            
            # Store results
            results[portfolio_name] = {
                'metrics': metrics,
                'cumulative_returns': portfolio_cumulative.tolist(),
                'weights': dict(zip(tickers, weights))
            }
        
        return {
            'success': True,
            'comparison_results': results,
            'dates': all_data.index.strftime('%Y-%m-%d').tolist()
        }
        
    except Exception as e:
        return {
            'success': False,
            'error': str(e)
        }

@app.route('/api/compare', methods=['POST'])
def compare_portfolios_endpoint():
    """Compare multiple portfolios"""
    try:
        data = request.json
        portfolios = data['portfolios']
        start_date = data.get('startDate')
        
        results = compare_portfolios(portfolios, start_date)
        return jsonify(results)
        
    except Exception as e:
        return jsonify({
            'success': False,
            'error': str(e)
        }), 400

def calculate_correlation_matrix(portfolio_data):
    """Calculate correlation matrix for portfolio assets"""
    try:
        # Calculate returns and handle single asset case
        if portfolio_data.shape[1] == 1:
            ticker = portfolio_data.columns[0]
            return {
                'pearson': {
                    'matrix': {ticker: {ticker: 1.0}},
                    'min': 1.0,
                    'max': 1.0,
                    'mean': 1.0
                },
                'spearman': {
                    'matrix': {ticker: {ticker: 1.0}},
                    'min': 1.0,
                    'max': 1.0,
                    'mean': 1.0
                }
            }

        # For multiple assets, calculate correlations
        returns = portfolio_data.pct_change().dropna()
        
        # Calculate correlations
        pearson_corr = returns.corr(method='pearson')
        spearman_corr = returns.corr(method='spearman')
        
        # Convert to dictionary format
        correlations = {
            'pearson': {
                'matrix': pearson_corr.to_dict(),
                'min': float(pearson_corr.min().min()),
                'max': float(pearson_corr.max().max()),
                'mean': float(pearson_corr.mean().mean())
            },
            'spearman': {
                'matrix': spearman_corr.to_dict(),
                'min': float(spearman_corr.min().min()),
                'max': float(spearman_corr.max().max()),
                'mean': float(spearman_corr.mean().mean())
            }
        }
        
        return clean_for_json(correlations)
        
    except Exception as e:
        print(f"Error in calculate_correlation_matrix: {str(e)}")
        raise

@app.route('/api/correlation', methods=['POST'])
def get_correlation():
    """Calculate and return correlation matrix"""
    try:
        data = request.json
        portfolio = data['portfolio']
        start_date = data.get('startDate')
        
        # Fetch data
        portfolio_data = pd.DataFrame()
        for stock in portfolio:
            stock_data = fetch_stock_data(stock['ticker'], start_date)
            if stock_data.empty:
                return jsonify({
                    'success': False,
                    'error': f'No data available for {stock["ticker"]}'
                }), 400
            portfolio_data[stock['ticker']] = stock_data['Adj Close']
        
        # Ensure we have data
        if portfolio_data.empty:
            return jsonify({
                'success': False,
                'error': 'No data available for correlation calculation'
            }), 400
        
        # Calculate correlations
        correlations = calculate_correlation_matrix(portfolio_data)
        
        return jsonify({
            'success': True,
            'correlations': correlations
        })
        
    except Exception as e:
        print(f"Error in get_correlation: {str(e)}")
        return jsonify({
            'success': False,
            'error': str(e)
        }), 400

def clean_for_json(obj):
    """Convert numpy/pandas objects and handle NaN values for JSON serialization"""
    try:
        if isinstance(obj, (np.integer, np.floating)):
            if np.isnan(obj) or np.isinf(obj):
                return None
            return float(obj)
        elif isinstance(obj, dict):
            return {k: clean_for_json(v) for k, v in obj.items()}
        elif isinstance(obj, (list, tuple)):
            return [clean_for_json(x) for x in obj]
        elif isinstance(obj, pd.Series):
            return [clean_for_json(x) for x in obj.values]
        elif isinstance(obj, np.ndarray):
            return [clean_for_json(x) for x in obj.flatten()]
        elif pd.isna(obj):  # Handle pandas NA values
            return None
        return obj
    except Exception as e:
        print(f"Error in clean_for_json: {str(e)}")
        return None

if __name__ == '__main__':
    app.run(debug=True)