import pandas as pd
import numpy as np
import yfinance as yf
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import datetime

sns.set_style("whitegrid")

class DCAStrategy:
    def __init__(self, ticker, start_date, end_date=None, initial_investment=10000,
                 investment_frequency='ME', reinvest_dividends=True):
        self.ticker = ticker
        self.start_date = start_date
        self.end_date = end_date or datetime.now().strftime('%Y-%m-%d')
        self.initial_investment = initial_investment
        self.investment_frequency = investment_frequency
        self.reinvest_dividends = reinvest_dividends

        self.data = self._fetch_data()
        self.investment_dates = self._get_investment_dates()
        self.periodic_investment = self._calculate_periodic_investment()

    def _fetch_data(self):
        data = yf.download(self.ticker, start=self.start_date)
        if isinstance(data.columns, pd.MultiIndex):
            data.columns = data.columns.get_level_values(0)
        return data

    def _get_investment_dates(self):
        return pd.date_range(
            start=self.data.index[0],
            end=self.data.index[-1],
            freq=self.investment_frequency
        )

    def _calculate_periodic_investment(self):
        num_periods = len(self.investment_dates)
        return self.initial_investment / num_periods if num_periods > 0 else 0

    def _get_next_trading_day(self, date):
        next_dates = self.data.index[self.data.index >= date]
        return next_dates[0] if len(next_dates) > 0 else None

    def calculate_portfolio(self):
        # Initialize series with the full date range
        holdings = pd.Series(0.0, index=self.data.index)
        portfolio_value = pd.Series(0.0, index=self.data.index)
        total_invested = pd.Series(0.0, index=self.data.index)

        # Initial investment on first trading day
        first_trading_day = self.data.index[0]
        initial_price = self.data.loc[first_trading_day, 'Close']
        initial_shares = self.periodic_investment / initial_price

        holdings.iloc[0] = initial_shares
        total_invested.iloc[0] = self.periodic_investment

        # Process all investments including the first one
        for i, inv_date in enumerate(self.investment_dates[1:], 1):  # Start from second investment
            trading_date = self._get_next_trading_day(inv_date)
            if trading_date is not None:
                idx = self.data.index.get_loc(trading_date)
                price = self.data.loc[trading_date, 'Close']
                new_shares = self.periodic_investment / price

                # Update holdings and total invested
                holdings.iloc[idx:] = holdings.iloc[idx-1] + new_shares
                total_invested.iloc[idx:] = total_invested.iloc[idx-1] + self.periodic_investment

        # Handle dividends if enabled
        if self.reinvest_dividends and 'Dividends' in self.data.columns:
            dividends = self.data['Dividends']
            dividend_shares = (dividends * holdings) / self.data['Close']
            holdings = holdings + dividend_shares.fillna(0)

        # Calculate portfolio value
        portfolio_value = holdings * self.data['Close']

        # Forward fill any missing values
        holdings = holdings.ffill()
        total_invested = total_invested.ffill()
        portfolio_value = portfolio_value.ffill()

        return portfolio_value, holdings, total_invested

    def analyze_performance(self):
        portfolio_value, holdings, total_invested = self.calculate_portfolio()

        if total_invested.iloc[-1] == 0:
            print("Warning: Total invested is zero. Check calculations.")
            return None

        final_value = portfolio_value.iloc[-1]
        total_return = (final_value - total_invested.iloc[-1]) / total_invested.iloc[-1]
        years = (self.data.index[-1] - self.data.index[0]).days / 365.25
        annualized_return = (1 + total_return) ** (1/years) - 1

        daily_returns = portfolio_value.pct_change()
        daily_returns = daily_returns.replace([np.inf, -np.inf], np.nan).dropna()

        volatility = daily_returns.std() * np.sqrt(252)
        risk_free_rate = 0.02
        sharpe_ratio = (annualized_return - risk_free_rate) / volatility

        return {
            'Initial Investment': self.initial_investment,
            'Final Value': final_value,
            'Total Return': total_return,
            'Annualized Return': annualized_return,
            'Volatility': volatility,
            'Sharpe Ratio': sharpe_ratio
        }

    def plot_performance(self):
        portfolio_value, holdings, total_invested = self.calculate_portfolio()

        # Ensure the initial portfolio value starts at exactly 10000
        portfolio_value.iloc[0] = self.initial_investment
        total_invested.iloc[0] = self.initial_investment

        fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(15, 10))

        # Plot 1: Portfolio Value
        sns.lineplot(x=portfolio_value.index, y=portfolio_value.values, ax=ax1, linewidth=2, label='Portfolio Value')
        sns.lineplot(x=total_invested.index, y=total_invested.values, ax=ax1, linewidth=2, linestyle='--', color='red', label='Total Invested')
        ax1.set_title('Portfolio Value Over Time')
        ax1.set_xlabel('Date')
        ax1.set_ylabel('Portfolio Value ($)')
        ax1.legend()
        ax1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'${x:,.0f}'))

        # Set y-axis minimum to 10000
        ax1.set_ylim(bottom=10000)

        # Plot 2: Returns
        returns_percentage = ((portfolio_value - total_invested) / total_invested * 100)
        # Ensure the initial return is exactly 0%
        returns_percentage.iloc[0] = 0

        sns.lineplot(x=returns_percentage.index, y=returns_percentage.values, ax=ax2, linewidth=2, label='Cumulative Returns')
        ax2.set_title('Cumulative Returns')
        ax2.set_xlabel('Date')
        ax2.set_ylabel('Return (%)')
        ax2.legend()
        ax2.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'{x:.1f}%'))
        ax2.axhline(y=0, color='black', linestyle='-', alpha=0.2)

        # Set y-axis minimum to 0%
        ax2.set_ylim(bottom=0)

        plt.tight_layout()
        plt.show()
# Example usage
strategy = DCAStrategy(
    ticker='^IBEX',
    start_date='2020-01-01',
    initial_investment=10000,
    investment_frequency='ME',
    reinvest_dividends=True
)

results = strategy.analyze_performance()
if results:
    print("\nPerformance Metrics:")
    print("-" * 50)
    for metric, value in results.items():
        if metric in ['Initial Investment', 'Final Value']:
            print(f"{metric}: ${value:,.2f}")
        else:
            print(f"{metric}: {value:.2%}")

strategy.plot_performance()

...simplfy the Nick Shawn strategy for unique investments, with no frequency