"""
class DataFetcher:
    def __init__(self, tickers, start_date='1990-01-01', end_date=None, auto_adjust=False):
        """
        Initializes the DataFetcher with tickers and date range.
        
        Parameters:
        - tickers (list or str): Stock ticker symbols.
        - start_date (str): Start date in 'YYYY-MM-DD' format.
        - end_date (str): End date in 'YYYY-MM-DD' format. Defaults to today if not provided.
        - auto_adjust (bool): Whether to adjust all OHLC prices. Defaults to False.
        """
        self.tickers = tickers if isinstance(tickers, list) else [tickers]
        self.start_date = start_date
        self.end_date = end_date if end_date else datetime.now().strftime('%Y-%m-%d')
        self.auto_adjust = auto_adjust
        self.data = None

    def fetch_data(self):
        """
        Fetches historical data for the specified tickers.
        """
        self.data = yf.download(
            self.tickers, 
            start=self.start_date, 
            end=self.end_date, 
            group_by='ticker',
            auto_adjust=self.auto_adjust,
            threads=True
        )
        # Handle single ticker case
        if len(self.tickers) == 1:
            self.data = {self.tickers[0]: self.data}
        else:
            self.data = {ticker: self.data[ticker] for ticker in self.tickers}
        return self.data
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import os
from scipy.stats import skew, kurtosis

class MonthlySeasonalityAnalyzer:
    def __init__(self, data, budget=4000, odds_ratio=2, output_dir='seasonality_data'):
        """
        Initializes the MonthlySeasonalityAnalyzer with necessary parameters.
        """
        self.data = data
        self.budget = budget
        self.odds_ratio = odds_ratio
        self.output_dir = output_dir
        if not os.path.exists(self.output_dir):
            os.makedirs(self.output_dir)

    @staticmethod
    def calculate_kelly_ratio(returns):
        """
        Calculates the Kelly ratio and fraction.
        """
        win_prob = np.mean(returns > 0)
        loss_prob = 1 - win_prob
        win_return = np.mean(returns[returns > 0]) if np.any(returns > 0) else 0
        loss_return = abs(np.mean(returns[returns < 0])) if np.any(returns < 0) else 0
        if loss_return == 0:
            kelly_ratio = 0
        else:
            kelly_ratio = (win_prob - (loss_prob / (win_return / loss_return))) if (win_return / loss_return) != 0 else 0
        kelly_fraction = kelly_ratio / 2  # Using half Kelly for safety
        return kelly_ratio, kelly_fraction

    @staticmethod
    def get_cell_colors(summary_table):
        """
        Generates cell colors for the summary table based on value ranges.
        """
        def color_scale(val, cmap, vmin, vmax):
            norm = plt.Normalize(vmin, vmax)
            return sns.color_palette(cmap, as_cmap=True)(norm(val))

        cell_colors = []
        for col in summary_table.columns:
            if col in ['Avg Returns (%)', 'Pos %', 'Avg Kelly Ratio', 'Avg Kelly Fraction', 'Position Size ($)']:
                cmap = "RdYlGn"
            elif col == 'StDev (%)':
                cmap = "YlOrRd"
            else:  # Skewness and Kurtosis
                cmap = "PuOr"
            vmin = summary_table[col].min()
            vmax = summary_table[col].max()
            colors = [color_scale(x, cmap, vmin, vmax) for x in summary_table[col]]
            cell_colors.append(colors)
        # Transpose to match table cell structure
        return list(map(list, zip(*cell_colors)))

    def analyze_monthly_seasonality(self, ticker):
        """
        Analyzes monthly seasonality for a given ticker.
        """
        df = self.data[ticker]['Adj Close'].resample('M').ffill()
        monthly_returns = df.pct_change().dropna() * 100  # Percentage

        # Organize into pivot table
        monthly_returns = monthly_returns.to_frame(name='Adj Close')
        monthly_returns['Year'] = monthly_returns.index.year
        monthly_returns['Month'] = monthly_returns.index.month
        monthly_returns['Month'] = monthly_returns['Month'].apply(lambda x: pd.Timestamp(2023, x, 1).strftime('%b'))
        pivot_table = monthly_returns.pivot_table(index='Year', columns='Month', values='Adj Close', aggfunc='mean')
        pivot_table = pivot_table.fillna(0)

        # Ensure correct month order
        months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 
                  'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
        pivot_table = pivot_table.reindex(columns=months)

        # Calculate metrics
        avg_returns = pivot_table.mean()
        std_dev = pivot_table.std()
        pos_perc = (pivot_table > 0).mean() * 100
        skewness = pivot_table.apply(lambda x: skew(x.dropna()), axis=0)
        kurt = pivot_table.apply(lambda x: kurtosis(x.dropna()), axis=0)

        # Kelly ratio and fraction
        kelly_ratios = pivot_table.apply(lambda x: self.calculate_kelly_ratio(x.dropna()), axis=0)
        avg_kelly_ratio = kelly_ratios.apply(lambda x: x[0])
        avg_kelly_fraction = kelly_ratios.apply(lambda x: x[1])

        # Position size based on Kelly fraction
        position_sizes = avg_kelly_fraction * self.budget

        # Summary table
        summary_table = pd.DataFrame({
            'Avg Returns (%)': avg_returns,
            'StDev (%)': std_dev,
            'Pos %': pos_perc,
            'Skewness': skewness,
            'Kurtosis': kurt,
            'Avg Kelly Ratio': avg_kelly_ratio,
            'Avg Kelly Fraction': avg_kelly_fraction,
            'Position Size ($)': position_sizes
        })

        return pivot_table, summary_table

    def visualize_monthly_seasonality(self, ticker):
        """
        Visualizes monthly seasonality with a heatmap and summary table.
        """
        pivot_table, summary_table = self.analyze_monthly_seasonality(ticker)

        fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(24, 8))
        
        # Heatmap
        sns.heatmap(
            pivot_table, 
            annot=True, 
            fmt=".2f", 
            cmap="coolwarm", 
            vmin=-5, 
            vmax=5, 
            center=0, 
            linewidths=.5, 
            cbar_kws={"shrink": .75}, 
            ax=ax1
        )
        ax1.set_title(f'Monthly Returns Heatmap for {ticker}', fontsize=16)
        ax1.set_xlabel('Month', fontsize=12)
        ax1.set_ylabel('Year', fontsize=12)
        ax1.tick_params(axis='both', which='major', labelsize=10)

        # Summary table
        ax2.axis('tight')
        ax2.axis('off')
        
        cell_colors = self.get_cell_colors(summary_table)
        
        table = ax2.table(
            cellText=summary_table.values.round(2), 
            colLabels=summary_table.columns, 
            rowLabels=summary_table.index, 
            cellLoc='center', 
            loc='center',
            cellColours=cell_colors
        )
        
        table.auto_set_font_size(False)
        table.set_fontsize(8)
        table.scale(1.2, 1.5)
        
        ax2.set_title(f'Summary Table for {ticker}', fontsize=16)

        plt.tight_layout()
        plt.show()

    def export_monthly_seasonality_data(self, ticker):
        """
        Exports monthly seasonality data to a CSV file.
        """
        _, summary_table = self.analyze_monthly_seasonality(ticker)
        output_path = os.path.join(self.output_dir, f'{ticker}_monthly_seasonality.csv')
        summary_table.to_csv(output_path)
        print(f'Saved {ticker} monthly seasonality data to {output_path}')
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import os
from scipy.stats import skew, kurtosis
import logging

class WeeklySeasonalityAnalyzer:
    def __init__(self, data, triple_witching_weeks=None, odds_ratio=2, output_dir='seasonality_data', budget=4000):
        """
        Initializes the WeeklySeasonalityAnalyzer with necessary parameters.

        Parameters:
        - data (dict): Dictionary of ticker data fetched by DataFetcher.
        - triple_witching_weeks (list): List of weeks considered as triple witching weeks.
        - odds_ratio (float): Odds ratio for Kelly calculation.
        - output_dir (str): Directory to save output CSV files.
        - budget (float): Default budget for position sizing.
        """
        self.data = data
        self.triple_witching_weeks = triple_witching_weeks or []
        self.odds_ratio = odds_ratio
        self.output_dir = output_dir
        self.budget = budget
        if not os.path.exists(self.output_dir):
            os.makedirs(self.output_dir)
        logging.info("WeeklySeasonalityAnalyzer initialized.")

    @staticmethod
    def calculate_kelly_ratio(returns_series, odds_ratio=2):
        """
        Calculates the Kelly ratio and fraction.

        Parameters:
        - returns_series (pd.Series): Series of returns.
        - odds_ratio (float): Odds ratio for calculation.

        Returns:
        - tuple: Kelly ratio and Kelly fraction.
        """
        win_prob = np.mean(returns_series > 0)
        loss_prob = 1 - win_prob
        win_return = np.mean(returns_series[returns_series > 0]) if np.any(returns_series > 0) else 0
        loss_return = abs(np.mean(returns_series[returns_series < 0])) if np.any(returns_series < 0) else 0
        if loss_return == 0 or win_return == 0:
            kelly_ratio = 0
        else:
            kelly_ratio = (win_prob - (loss_prob / (win_return / loss_return))) if (win_return / loss_return) != 0 else 0
        kelly_fraction = kelly_ratio / odds_ratio  # Using half Kelly for safety
        return kelly_ratio, kelly_fraction

    @staticmethod
    def get_cell_colors_weekly(weekly_summary):
        """
        Generates cell colors for the weekly summary table based on value ranges.

        Parameters:
        - weekly_summary (pd.DataFrame): DataFrame containing weekly summary metrics.

        Returns:
        - list: Nested list of color codes for each cell.
        """
        def color_scale(val, cmap, vmin, vmax):
            norm = plt.Normalize(vmin, vmax)
            return sns.color_palette(cmap, as_cmap=True)(norm(val))

        cell_colors = []
        for col in weekly_summary.columns:
            if col in ['Avg Returns (%)', 'Pos %', 'Kelly Ratio']:
                cmap = "RdYlGn"
            elif col == 'Volatility (%)':
                cmap = "YlOrRd"
            else:  # For any additional columns
                cmap = "PuOr"
            vmin = weekly_summary[col].min()
            vmax = weekly_summary[col].max()
            # Avoid vmin == vmax which causes issues in normalization
            if vmin == vmax:
                vmin = vmin - 1 if vmin != 0 else -1
                vmax = vmax + 1 if vmax != 0 else 1
            colors = [color_scale(x, cmap, vmin, vmax) for x in weekly_summary[col]]
            cell_colors.append(colors)
        # Transpose to match table cell structure
        return list(map(list, zip(*cell_colors)))

    def analyze_weekly_seasonality(self, ticker):
        """
        Analyzes weekly seasonality for a given ticker.

        Parameters:
        - ticker (str): Stock ticker symbol.

        Returns:
        - pd.DataFrame: Summary metrics table with average values per week.
        """
        try:
            df = self.data[ticker].copy()
            df['Return'] = df['Adj Close'].pct_change()
        except KeyError:
            logging.warning(f"'Adj Close' not found for {ticker}. Using 'Close' instead.")
            df = self.data[ticker].copy()
            df['Return'] = df['Close'].pct_change()

        # Remove the first row with NaN return
        df.dropna(subset=['Return'], inplace=True)

        # Extract week number and year
        df['Week_Number'] = df.index.isocalendar().week
        df['Year'] = df.index.isocalendar().year

        # Aggregate weekly data
        weekly_data = df.groupby(['Year', 'Week_Number']).agg({
            'Return': 'sum',
            'Volume': 'mean'
        }).reset_index()

        # Rename columns for clarity
        weekly_data.rename(columns={'Week_Number': 'Week'}, inplace=True)

        # Ensure all weeks (1-52) are present
        all_weeks = pd.DataFrame({'Week': range(1, 53)})
        unique_years = weekly_data['Year'].unique()
        complete_weekly_data = pd.DataFrame()

        for year in unique_years:
            year_weeks = all_weeks.copy()
            year_weeks['Year'] = year
            merged = pd.merge(year_weeks, weekly_data[weekly_data['Year'] == year], on=['Year', 'Week'], how='left')
            merged['Return'].fillna(0, inplace=True)
            merged['Volume'].fillna(0, inplace=True)
            complete_weekly_data = pd.concat([complete_weekly_data, merged], ignore_index=True)

        # Calculate average metrics per week across all years
        weekly_summary = complete_weekly_data.groupby('Week').agg({
            'Return': ['mean', 'std'],
            'Volume': 'mean'
        }).reset_index()
        weekly_summary.columns = ['Week', 'Avg Return', 'Volatility', 'Avg Volume']

        # Calculate Positive Returns Percentage and Kelly Ratio
        positive_returns = complete_weekly_data.groupby('Week')['Return'].apply(lambda x: (x > 0).mean()).reset_index(name='Pos %')
        weekly_summary = pd.merge(weekly_summary, positive_returns, on='Week')

        # Calculate Kelly Ratio
        weekly_summary[['Kelly Ratio', 'Kelly Fraction']] = weekly_summary.apply(
            lambda x: pd.Series(
                self.calculate_kelly_ratio(
                    complete_weekly_data.loc[complete_weekly_data['Week'] == x['Week'], 'Return'],
                    self.odds_ratio
                )
            ), axis=1
        )

        # Convert Avg Return to percentage
        weekly_summary['Avg Returns (%)'] = weekly_summary['Avg Return'] * 100
        weekly_summary['Volatility (%)'] = weekly_summary['Volatility'] * 100

        # Calculate Position Size ($)
        weekly_summary['Position Size ($)'] = (weekly_summary['Kelly Fraction'] * self.budget).round(2)

        # Select relevant columns
        summary_table = weekly_summary[['Week', 'Avg Returns (%)', 'Volatility (%)', 'Pos %', 'Kelly Ratio', 'Position Size ($)']]

        return summary_table

    def visualize_weekly_seasonality(self, ticker):
        """
        Visualizes weekly seasonality with a summary table.

        Parameters:
        - ticker (str): Stock ticker symbol.
        """
        summary_table = self.analyze_weekly_seasonality(ticker)

        fig, ax = plt.subplots(figsize=(14, 28))  # Increased figure height for better table display

        # Summary table
        ax.axis('tight')
        ax.axis('off')

        # Prepare summary metrics: Avg Returns, Volatility, Positive Returns, Kelly Ratio, Position Size
        summary_display = summary_table.copy()
        summary_display['Avg Returns (%)'] = summary_display['Avg Returns (%)'].round(2)
        summary_display['Volatility (%)'] = summary_display['Volatility (%)'].round(2)
        summary_display['Pos %'] = (summary_display['Pos %'] * 100).round(2)  # Convert to percentage
        summary_display['Kelly Ratio'] = summary_display['Kelly Ratio'].round(4)
        summary_display['Position Size ($)'] = summary_display['Position Size ($)']

        # Generate cell colors
        cell_colors = self.get_cell_colors_weekly(summary_display[['Avg Returns (%)', 'Volatility (%)', 'Pos %', 'Kelly Ratio', 'Position Size ($)']])

        # Create the table
        table = ax.table(
            cellText=summary_display[['Avg Returns (%)', 'Volatility (%)', 'Pos %', 'Kelly Ratio', 'Position Size ($)']].values,
            colLabels=['Avg Returns (%)', 'Volatility (%)', 'Pos %', 'Kelly Ratio', 'Position Size ($)'],
            rowLabels=summary_display['Week'],
            cellLoc='center',
            loc='center',
            cellColours=cell_colors,
            colColours=["#f1c40f"] * 5  # Optional: Add color to header
        )

        # Style the table
        table.auto_set_font_size(False)
        table.set_fontsize(10)
        table.scale(1.2, 1.2)

        # Bold headers
        for (i, j), cell in table.get_celld().items():
            if i == 0:
                cell.set_text_props(weight='bold', color='black')
                cell.set_facecolor("#f1c40f")
            elif j == -1:
                cell.set_facecolor("#ecf0f1")

        # Set title using fig.suptitle for better placement
        fig.suptitle(f'Weekly Summary for {ticker}', fontsize=18, fontweight='bold', y=1.02)  # Increased 'y' to move title higher

        # Adjust layout to accommodate the title
        plt.tight_layout(rect=[0, 0.03, 1, 0.95])  # Adjust the top rect value to make more space for the title
        plt.subplots_adjust(top=0.85)  # Manually adjust top padding to ensure proper distance between title and table

        plt.show()

    def export_weekly_seasonality_data(self, ticker):
        """
        Exports weekly seasonality data to a CSV file.

        Parameters:
        - ticker (str): Stock ticker symbol.
        """
        try:
            weekly_summary = self.analyze_weekly_seasonality(ticker)
            output_path = os.path.join(self.output_dir, f'{ticker}_weekly_seasonality_summary.csv')
            weekly_summary.to_csv(output_path, index=False)
            logging.info(f'Saved {ticker} weekly seasonality summary to {output_path}')
        except Exception as e:
            logging.error(f"Error exporting weekly seasonality summary for {ticker}: {e}")

import logging
from datetime import datetime

# Configure logging to display INFO level messages
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

tickers = [
    "TSLA",
    "MSFT",
    "NVDA",
    "UPRO",  # UltraPro S&P500: 3x leveraged ETF of the S&P 500 Index
    "TQQQ",  # ProShares UltraPro QQQ: 3x leveraged ETF of the NASDAQ-100 Index
    "VIX",   # CBOE Volatility Index
    "UVXY",  # ProShares Ultra VIX Short-Term Futures ETF: 1.5x leveraged VIX futures
    "XLK",   # Technology Select Sector SPDR Fund
    "SOXL",  # Direxion Daily Semiconductor Bull 3X Shares
    "SOXS",  # Direxion Daily Semiconductor Bear 3X Shares
    "XLV",   # Health Care Select Sector SPDR Fund
    "CURE",  # Direxion Daily Healthcare Bull 2X Shares
    # "SICK",  # Direxion Daily Healthcare Bear 2X Shares (SICK)
    "XLF",   # Financial Select Sector SPDR Fund
    "FAS",   # Direxion Daily Financial Bull 3X Shares
    "FAZ",   # Direxion Daily Financial Bear 3X Shares
    "XLY",   # Consumer Discretionary Select Sector SPDR Fund
    "ERX",   # Direxion Daily Energy Bull 2X Shares
    "SCC",   # ProShares UltraShort Consumer Services
    "XLE",   # Energy Select Sector SPDR Fund
    "ERY",   # Direxion Daily Energy Bear 2X Shares
    "XLI",   # Industrial Select Sector SPDR Fund
    "DUSL",  # Direxion Daily Industrials Bull 3X Shares
    "SIJ",   # ProShares UltraShort Industrials
    "XLC",   # Communication Services Select Sector SPDR Fund
    "FNGU",  # MicroSectors FANG+ Index 3X Leveraged ETN
    "FNGD",  # MicroSectors FANG+ Index -3X Inverse Leveraged ETN
    "XLU",   # Utilities Select Sector SPDR Fund
    "UPW",   # ProShares Ultra Utilities
    "SDP",   # ProShares UltraShort Utilities
    "XLB",   # Materials Select Sector SPDR Fund
    "UYM",   # ProShares Ultra Basic Materials
    "SMN"    # ProShares UltraShort Basic Materials
]

# Define triple witching weeks (typically the 11th, 24th, 37th, and 50th weeks)
triple_witching_weeks = [11, 24, 37, 50]

# ------------------------- Data Fetching -------------------------
# Initialize DataFetcher with auto_adjust=False to include 'Adj Close'
data_fetcher = DataFetcher(
    tickers=tickers,  # Use the list of tickers directly
    start_date="2010-01-01",
    end_date="2024-01-01",
    auto_adjust=False
)
data = data_fetcher.fetch_data()

# ------------------ Weekly Seasonality Analysis ------------------
# Get the current week number of the year (1-52)
current_week = datetime.now().isocalendar()[1]

# Print the current week in bold
print(f"\033[1mCurrent Week: {current_week}\033[0m")

# Example usage of the MonthlySeasonalityAnalyzer and WeeklySeasonalityAnalyzer classes
if __name__ == "__main__":
    # Sample data fetching (replace with actual data fetching logic)
    data_fetcher = DataFetcher(tickers=tickers, start_date='2010-01-01')  # Use the list of tickers directly
    data = data_fetcher.fetch_data()

    # Create an instance of MonthlySeasonalityAnalyzer
    monthly_analyzer = MonthlySeasonalityAnalyzer(data=data)

    # Create an instance of WeeklySeasonalityAnalyzer
    weekly_analyzer = WeeklySeasonalityAnalyzer(
        data=data,
        triple_witching_weeks=[11, 24, 37, 50],
        odds_ratio=2,
        output_dir='seasonality_data',
        budget=4000
    )

    # Analyze and visualize monthly and weekly seasonality for each ticker in the list
    for ticker in tickers:
        try:
            logging.info(f"Analyzing Monthly Seasonality for {ticker}")
            monthly_analyzer.visualize_monthly_seasonality(ticker)

            logging.info(f"Analyzing Weekly Seasonality for {ticker}")
            weekly_analyzer.visualize_weekly_seasonality(ticker)
            weekly_analyzer.export_weekly_seasonality_data(ticker)
        except ValueError as ve:
            logging.error(f"ValueError for {ticker}: {ve}")
        except Exception as e:
            logging.error(f"Error analyzing {ticker}: {e}")

"""
add code at the very bottom that gets the current week and month, then prints out the positions sizes in the summary table for each asset until December