import logging
import sys
from datetime import datetime, timedelta, time 
from typing import List
import pytz
import pandas as pd
import numpy as np
import requests
import json
from thefirstock import thefirstock

# Set up logger with custom formatter
class CustomFormatter(logging.Formatter):
    def format(self, record):
        ist = pytz.timezone('Asia/Kolkata')
        record.created_ist = datetime.now(ist).strftime('%Y-%m-%d %H:%M:%S')
        return f"{record.created_ist} - {record.levelname} - {record.funcName} - {record.getMessage()}"

# Configure the logger
logger = logging.getLogger("StockLogger")
logger.setLevel(logging.DEBUG)

# Remove existing handlers
for handler in logger.handlers[:]:
    logger.removeHandler(handler)

# Add console handler with custom formatter
console_handler = logging.StreamHandler()
console_handler.setFormatter(CustomFormatter())
logger.addHandler(console_handler)
logger.propagate = False

# Create a simple decorator-style approach for logging
def log_call(func):
    """Decorator to log function calls"""
    def wrapper(*args, **kwargs):
        logger.info(f"Entering {func.__name__}")
        try:
            result = func(*args, **kwargs)
            logger.info(f"Exiting {func.__name__}")
            return result
        except Exception as e:
            logger.error(f"Error in {func.__name__}: {e}")
            raise
    return wrapper

class StockDataFetcher:
    def __init__(self, elapsed , client_details: List[str], nuetralLimit, positiveThreshold):
        self.elapsed = elapsed
        self.client_details = client_details
        self.user_id = client_details[0]
        self.ist = pytz.timezone('Asia/Kolkata')
        logger.info(f"StockDataFetcher initialized for user: {self.user_id}")

        self.num_previous_days_from_elapsed = 5
        self.ha_smooth_period = 10
        self.openTradeExitTime = time(14, 40)  # Exit time for open positions
        self.cooling_period_ticks = 14  # Set cooldown period after losing trade

        self.total_pnl = 0
        self.num_trades = 0
        self.day_executed = 0

        self.HA_nuetral_limit = nuetralLimit    # don't play zone/close open position limit
        self.safeEntryLimit = self.HA_nuetral_limit + 8  # Safe entry limit for Close_Diff
        self.positiveThreshold = positiveThreshold  # Points threshold for profit taking

        # variables for order creation
        self.lotCount = 1
        self.StrikeOffsetPoints = 0  # Offset for strike price calculation - multiples of 50
        self.createEntries = 0  # If live , will get set..Don't modify the value here
        self.previousInstrument = None # To track previous instrument during order close
        

        
    def login(self):
        try:
            logger.info(f"Attempting login for {self.user_id}")
            response = thefirstock.firstock_login(*self.client_details)
            if response.get("status") == "success":
                logger.info("Login successful!")
            else:
                logger.error(f"Login failed: {response}")
                sys.exit()
        except Exception as e:
            logger.error(f"Login error: {e}")
            sys.exit()

    def filter_last_day(self, df):
        """
        Filter DataFrame to include only rows from the last day based on the 'time' column.
        
        Parameters:
        - df: DataFrame with a 'time' column (datetime) and other columns.
        
        Returns:
        - Filtered DataFrame containing only the last day's data.
        """
        # Ensure 'time' is in datetime format
        df = df.copy()  # Create a copy to avoid modifying the original
        df['time'] = pd.to_datetime(df['time'])
        
        # Extract the date from 'time' and find the last date
        last_date = df['time'].dt.date.max()
        self.day_executed = last_date
        
        # Filter for rows where the date matches the last date
        filtered_df = df[df['time'].dt.date == last_date]
        
        return filtered_df


    def heikin_ashi(self, df: pd.DataFrame) -> pd.DataFrame:
        """
        Smoothed Heikin-Ashi (double EMA smoothing)

        1. EMA-smooth raw price data (period = self.ha_smooth_period)
        2. Build classic Heikin-Ashi candles from the smoothed data
        3. EMA-smooth the HA candles again (same period)

        Returns a **copy** of `df` with all new columns appended.
        """
        # ------------------------------------------------------------------
        # 0. Work on a copy – never touch the original
        # ------------------------------------------------------------------
        ha_df = df.copy()

        # ------------------------------------------------------------------
        # 1. First EMA layer – smooth raw price
        # ------------------------------------------------------------------
        p = self.ha_smooth_period                     
        logger.info(f"heikin_ashi: smooth_period: {p}")
        ha_df["Smooth_Open"]  = df["into"].ewm(span=p, adjust=False).mean()
        ha_df["Smooth_High"]  = df["inth"].ewm(span=p, adjust=False).mean()
        ha_df["Smooth_Low"]   = df["intl"].ewm(span=p, adjust=False).mean()
        ha_df["Smooth_Close"] = df["intc"].ewm(span=p, adjust=False).mean()

        # ------------------------------------------------------------------
        # 2. Heikin-Ashi on the smoothed series
        # ------------------------------------------------------------------
        # HA_Close – simple average of the four smoothed values
        ha_df["HA_Close"] = (
            ha_df["Smooth_Open"]
            + ha_df["Smooth_High"]
            + ha_df["Smooth_Low"]
            + ha_df["Smooth_Close"]
        ) / 4

        # HA_Open – recursive: (prev HA_Open + prev HA_Close) / 2
        #   first row = smoothed open
        ha_df["HA_Open"] = (
            ha_df["Smooth_Open"].shift(1) + ha_df["Smooth_Close"].shift(1)
        ) / 2
        ha_df.loc[0, "HA_Open"] = ha_df.loc[0, "Smooth_Open"]

        # sequential fill (exact HA logic)
        for i in range(1, len(ha_df)):
            ha_df.loc[i, "HA_Open"] = (
                ha_df.loc[i - 1, "HA_Open"] + ha_df.loc[i - 1, "HA_Close"]
            ) / 2

        # HA_High / HA_Low – max/min of the three candidates
        ha_df["HA_High"] = pd.concat(
            [ha_df["Smooth_High"], ha_df["HA_Open"], ha_df["HA_Close"]], axis=1
        ).max(axis=1)

        ha_df["HA_Low"] = pd.concat(
            [ha_df["Smooth_Low"], ha_df["HA_Open"], ha_df["HA_Close"]], axis=1
        ).min(axis=1)

        # ------------------------------------------------------------------
        # 3. Second EMA layer – smooth the HA candles
        # ------------------------------------------------------------------
        ha_df["HA_Open_Smooth"]  = ha_df["HA_Open"].ewm(span=p, adjust=False).mean()
        ha_df["HA_High_Smooth"]  = ha_df["HA_High"].ewm(span=p, adjust=False).mean()
        ha_df["HA_Low_Smooth"]   = ha_df["HA_Low"].ewm(span=p, adjust=False).mean()
        ha_df["HA_Close_Smooth"] = ha_df["HA_Close"].ewm(span=p, adjust=False).mean()

        # ------------------------------------------------------------------
        # 4. Add HA_Color and HA_Diff column
        # ------------------------------------------------------------------
        #ha_df["HA_Color"] = np.where(ha_df["HA_Open_Smooth"] <= ha_df["HA_Close_Smooth"], "green", "red")
        ha_df["HA_Diff"] = (ha_df["HA_Close_Smooth"] - ha_df["HA_Open_Smooth"]).round(2)
        ha_df["Close_Diff"] = (ha_df["intc"] - ha_df["HA_Close_Smooth"]).round(2)
        #ha_df["HA_Color"] = np.where(ha_df["HA_Diff"] > self.HA_nuetral_limit, "green",np.where(ha_df["HA_Diff"] < -self.HA_nuetral_limit, "red", "neutral"))
        ha_df["HA_Color"] = np.where(ha_df["Close_Diff"] > self.HA_nuetral_limit, "green",np.where(ha_df["Close_Diff"] < -self.HA_nuetral_limit, "red", "neutral"))

        # ------------------------------------------------------------------
        # 5. Remove unwanted columns
        # ------------------------------------------------------------------
        columns_to_remove = ['Smooth_Open', 'Smooth_High', 'Smooth_Low', 'Smooth_Close', 'HA_Open', 'HA_High', 'HA_Low', 'HA_Close', 'HA_High_Smooth', 'HA_Low_Smooth']
        for col in columns_to_remove:
            if col in ha_df.columns:
                ha_df.drop(col, axis=1, inplace=True)

        # ------------------------------------------------------------------
        # 5. Filter to last day only
        # ------------------------------------------------------------------
        #ha_df = self.filter_last_day(ha_df)
        

        return ha_df

    def fetch_time_price_series(self, exchange, trading_symbol, start_time, end_time, interval):
        logger.info(f"Requesting {trading_symbol} for {start_time} to {end_time} @interval {interval}")
        try:
            response = thefirstock.firstock_TimePriceSeries(
                userId=self.user_id, exchange=exchange, tradingSymbol=trading_symbol,
                startTime=start_time, endTime=end_time, interval=interval
            )
            if response.get("status") == "success":
                return pd.DataFrame(response.get("data", []))
            logger.error(f"Fetch failed: {response}")
            return pd.DataFrame()
        except Exception as e:
            logger.error(f"Error fetching series: {e}")
            return pd.DataFrame()

    def process_symbol_data(self, symbol, interval, start_time, end_time):
        exchange, trading_symbol = symbol.split(":")
        df = self.fetch_time_price_series(
            exchange, trading_symbol,
            start_time.strftime("%d/%m/%Y %H:%M:%S"),
            end_time.strftime("%d/%m/%Y %H:%M:%S"),
            str(interval),
        )
        
        if df.empty:
            logger.warning("Empty DataFrame returned, cannot process further")
            return df
        
        numeric_cols = ['into', 'inth', 'intl', 'intc']
        logger.info(f"Converting numeric columns: {numeric_cols}")
        for col in numeric_cols:
            if col in df.columns:
                df[col] = pd.to_numeric(df[col], errors='coerce')
            else:
                logger.warning(f"Column {col} not found in DataFrame")

        columns_to_remove = ['stat', 'ssboe', 'intvwap', 'intv', 'intoi', 'v', 'oi']
        logger.info(f"Removing columns: {columns_to_remove}")
        for col in columns_to_remove:
            if col in df.columns:
                df.drop(col, axis=1, inplace=True)
        

        if not pd.api.types.is_datetime64_any_dtype(df['time']):
            df['time'] = pd.to_datetime(df['time'], format='%d-%m-%Y %H:%M:%S')
        df = df[(df['time'].dt.time >= time(9, 15)) & (df['time'].dt.time <= time(15, 30))].reset_index(drop=True)

        df = df.sort_values('time').reset_index(drop=True)
        df = self.heikin_ashi(df)

        return df
    
    def print_entire_df(self, df):

        # Temporarily adjust pandas display settings to show all rows and columns
        pd.set_option('display.max_rows', None)  # Show all rows
        pd.set_option('display.max_columns', None)  # Show all columns
        pd.set_option('display.width', None)  # Auto-adjust width to fit content
        pd.set_option('display.colheader_justify', 'center')  # Center-align column headers

        # Print the entire DataFrame
        print(df)

        # Reset display options to default (optional, to avoid affecting other outputs)
        pd.reset_option('display.max_rows')
        pd.reset_option('display.max_columns')
        pd.reset_option('display.width')
        pd.reset_option('display.colheader_justify')

    def create_order(self, tickTimeStr, instrument, price, action, lotCount):

        print(f"Creating order: Time:{tickTimeStr}, Instrument:{instrument}, Price:{price}, Action:{action}, LotCount:{lotCount}")

        if self.createEntries:
            url = "http://143.244.141.41/php/createEntries.php"
            params = {
                'tickTime': str(tickTimeStr),
                'instrument': str(instrument),
                'closePrice': str(price),
                'signal': str(action),
                'orderType': str(lotCount)
            }
            try:
                response = requests.get(url, params=params)
                if response.status_code != 200:
                    self.logger.error(f"Order failed with status {response.status_code}: {response.text}")
            except Exception as e:
                self.logger.error(f"Order request failed: {e}")

    def get_strike_price(self, current_price, option_type):
        strike_Price = 0
        try:
            price = float(current_price)
            remainder = price % 50
            if remainder > 25:
                strike_Price = int(price + (50 - remainder))
            else:
                strike_Price = int(price - remainder)
            
            if option_type == 'CE':
                strike_Price -= self.StrikeOffsetPoints
            elif option_type == 'PE':
                strike_Price += self.StrikeOffsetPoints

            return strike_Price

        except (ValueError, TypeError):
            self.logger.error(f"Invalid price for strike calculation: {current_price}")
            return None

    def trading_simulation(self, niftydf):
        """
        Execute trading simulation on the provided DataFrame
        
        Entry rules:
        - If no previous position exists and
        - If HA_Color transitions from neutral to green, initiate BUY
        - If HA_Color transitions from neutral to red, initiate SELL
        - Additional condition: abs(Close_Diff) should be less than self.safeEntryLimit
        
        Exit rules:
        - If position exists and price moves beyond positiveThreshold, close position
        - If position exists and time exceeds openTradeExitTime, close position
        - If position exists and HA_Color changes to neutral, close position
        
        Returns:
        - trades: List of dictionaries with trade details
        - summary: Dictionary with trading summary statistics
        """
        
        # Trading parameters
        positiveThreshold = self.positiveThreshold  # Points threshold for profit taking
        openTradeExitTime = self.openTradeExitTime  # Exit time for open positions
        safeEntryLimit = self.safeEntryLimit  # Safe entry limit for Close_Diff
        
        # Initialize trading variables
        current_position = None  # None, 'BUY', or 'SELL'
        entry_price = 0
        entry_time = None
        entry_type = None  # 'neutral_to_green' or 'neutral_to_red'
        num_ticks_to_skip = 0
        close_color = None
        
        trades = []  # List to store all completed trades
        
        logger.info(f"Starting trading simulation...for {len(niftydf)} rows")
        
        # Parse the DataFrame starting from the second row (index 1)
        for index in range(1, len(niftydf)):
            current_row = niftydf.iloc[index]
            prev_row = niftydf.iloc[index - 1]
            
            current_time = current_row['time']
            current_ha_color = current_row['HA_Color']
            current_intc = current_row['intc']
            current_close_diff = current_row['Close_Diff']
            
            prev_ha_color = prev_row['HA_Color']
            
            #logger.debug(f"Time: {current_time}, Prev_HA_Color: {prev_ha_color}, Current_HA_Color: {current_ha_color}, intc: {current_intc}, Position: {current_position}")

            # Skip if current or previous HA_Color is not available
            if pd.isna(current_ha_color) or pd.isna(prev_ha_color) or pd.isna(current_close_diff):
                continue

            # Skip during cooldown period after a losing trade
            if num_ticks_to_skip > 0:
                num_ticks_to_skip -= 1
                continue
                
           
            # ENTRY RULES - Only enter if no position exists and allowed trade time
            if current_position is None and current_time.time() < openTradeExitTime:
                # Check for neutral to green transition AND safe entry condition
                if (prev_ha_color == "neutral" and current_ha_color == "green" and 
                    abs(current_close_diff) < safeEntryLimit):
                    close_color = "red"
                    current_position = "BUY"
                    entry_price = current_intc
                    entry_time = current_time
                    entry_type = "neutral_to_green"
                    logger.info(f"🟢 BUY Entry at {current_time}, Price: {entry_price:.2f}, Close_Diff: {current_close_diff:.2f}")

                    tickTimeStr = entry_time.strftime('%Y-%m-%d %H:%M:%S')
                    strike = self.get_strike_price(entry_price, 'CE')
                    instrument = f"NIFTY{strike}CE"
                    self.previousInstrument = instrument
                    self.create_order(tickTimeStr, instrument, entry_price, 'BUY', self.lotCount)
                
                # Check for neutral to red transition AND safe entry condition
                elif (prev_ha_color == "neutral" and current_ha_color == "red" and 
                    abs(current_close_diff) < safeEntryLimit):
                    close_color = "green"
                    current_position = "SELL"
                    entry_price = current_intc
                    entry_time = current_time
                    entry_type = "neutral_to_red"
                    logger.info(f"🔴 SELL Entry at {current_time}, Price: {entry_price:.2f}, Close_Diff: {current_close_diff:.2f}")
                    tickTimeStr = entry_time.strftime('%Y-%m-%d %H:%M:%S')
                    strike = self.get_strike_price(entry_price, 'PE')
                    instrument = f"NIFTY{strike}PE"
                    self.previousInstrument = instrument
                    self.create_order(tickTimeStr, instrument, entry_price, 'BUY', self.lotCount)
                
                # Log if entry condition met but Close_Diff condition failed
                elif (prev_ha_color == "neutral" and current_ha_color == "green" and 
                    abs(current_close_diff) >= safeEntryLimit):
                    logger.info(f"⚠️  BUY Entry skipped at {current_time} - Close_Diff {current_close_diff:.2f} >= safeEntryLimit {safeEntryLimit}")
                
                elif (prev_ha_color == "neutral" and current_ha_color == "red" and 
                    abs(current_close_diff) >= safeEntryLimit):
                    logger.info(f"⚠️  SELL Entry skipped at {current_time} - Close_Diff {current_close_diff:.2f} >= safeEntryLimit {safeEntryLimit}")
            
            # EXIT RULES - Check if we have an open position
            elif current_position is not None:
                exit_reason = None
                should_exit = False
                
                # Calculate current P&L
                if current_position == "BUY":
                    pnl_points = current_intc - entry_price
                else:  # SELL position
                    pnl_points = entry_price - current_intc
                
                # Exit Rule 1: Profit threshold reached
                if pnl_points >= positiveThreshold:
                    exit_reason = "profit_target"
                    should_exit = True
                    logger.info(f"🎯 Profit target hit for {current_position} position at {current_time}. P&L: {pnl_points:.2f} points")
                
                # Exit Rule 2: Time-based exit
                elif current_time.time() >= openTradeExitTime:
                    exit_reason = "time_based"
                    should_exit = True
                    logger.info(f"⏰ Time-based exit for {current_position} position at {current_time}. P&L: {pnl_points:.2f} points")
                
                # Exit Rule 3: HA_Color changes to neutral
                elif current_ha_color == "neutral":
                    exit_reason = "color_to_neutral"
                    should_exit = True
                    logger.info(f"⚪ HA Color changed to neutral for {current_position} position at {current_time}. P&L: {pnl_points:.2f} points")

                # Exit Rule 4: HA_Color changes directly changes to opposite color
                elif current_ha_color == close_color:
                    exit_reason = "color_to_opposite_color"
                    should_exit = True
                    logger.info(f" direct color change to opposite: close_color-{close_color} current_ha_color:{current_ha_color} at {current_time}")
                    logger.info(f"⚪ HA Color changed to Opposite color for {current_position} position at {current_time}. P&L: {pnl_points:.2f} points")


                # Execute exit if conditions met
                if should_exit:
                    trade = {
                        'entry_time': entry_time,
                        'exit_time': current_time,
                        'position_type': current_position,
                        'entry_price': entry_price,
                        'exit_price': current_intc,
                        'pnl_points': pnl_points,
                        'entry_type': entry_type,
                        'exit_reason': exit_reason
                    }
                    trades.append(trade)
                    logger.info(f"✅ Trade Closed: {current_position} | Entry: {entry_price:.2f} | Exit: {current_intc:.2f} | P&L: {pnl_points:.2f} points | Reason: {exit_reason}")

                    tickTimeStr = current_time.strftime('%Y-%m-%d %H:%M:%S')
                    instrument = self.previousInstrument
                    self.create_order(tickTimeStr, instrument, current_intc, 'SELL', self.lotCount)
                    
                    # Reset position
                    current_position = None
                    entry_price = 0
                    entry_time = None
                    entry_type = None
                    close_color = None
                    if pnl_points < 0:
                        num_ticks_to_skip = self.cooling_period_ticks  
            

        # Generate summary
        summary = self.generate_trading_summary(trades)
        
        return trades, summary
        
        
    def generate_trading_summary(self, trades):
        """Generate summary statistics from trades"""
        if not trades:
            return {
                'total_trades': 0,
                'winning_trades': 0,
                'losing_trades': 0,
                'neutral_trades': 0,
                'total_pnl': 0,
                'average_pnl': 0,
                'win_rate': 0,
                'profit_factor': 0,
                'exit_reasons': {}
            }
        
        total_pnl = sum(trade['pnl_points'] for trade in trades)
        winning_trades = len([t for t in trades if t['pnl_points'] > 0])
        losing_trades = len([t for t in trades if t['pnl_points'] < 0])
        neutral_trades = len([t for t in trades if t['pnl_points'] == 0])
        
        # Calculate exit reasons breakdown
        exit_reasons = {}
        for trade in trades:
            reason = trade['exit_reason']
            exit_reasons[reason] = exit_reasons.get(reason, 0) + 1
        
        # Calculate profit factor (avoid division by zero)
        total_profit = sum(t['pnl_points'] for t in trades if t['pnl_points'] > 0)
        total_loss = abs(sum(t['pnl_points'] for t in trades if t['pnl_points'] < 0))
        profit_factor = total_profit / total_loss if total_loss > 0 else float('inf')
        
        summary = {
            'total_trades': len(trades),
            'winning_trades': winning_trades,
            'losing_trades': losing_trades,
            'neutral_trades': neutral_trades,
            'total_pnl': total_pnl,
            'average_pnl': total_pnl / len(trades) if trades else 0,
            'win_rate': (winning_trades / len(trades)) * 100 if trades else 0,
            'profit_factor': profit_factor,
            'exit_reasons': exit_reasons
        }

        self.total_pnl = total_pnl
        self.num_trades = len(trades)
        
        return summary

    def print_trading_results(self, trades, summary):
        """Print detailed trading results"""
        print("\n" + "="*100)
        print("TRADING SIMULATION RESULTS")
        print("="*100)
        
        if not trades:
            print("No trades executed.")
            return
        
        # Print individual trades
        print("\nINDIVIDUAL TRADES:")
        print("-" * 120)
        print(f"{'#':<3} {'Type':<6} {'Entry Time':<20} {'Exit Time':<20} {'Entry':<8} {'Exit':<8} {'P&L':<10} {'Reason':<15}")
        print("-" * 120)
        
        for i, trade in enumerate(trades, 1):
            pnl_color = "🟢" if trade['pnl_points'] > 0 else "🔴" if trade['pnl_points'] < 0 else "⚪"
            print(f"{i:<3} {trade['position_type']:<6} "
                f"{trade['entry_time'].strftime('%H:%M:%S'):<20} "
                f"{trade['exit_time'].strftime('%H:%M:%S'):<20} "
                f"{trade['entry_price']:<8.2f} "
                f"{trade['exit_price']:<8.2f} "
                f"{pnl_color} {trade['pnl_points']:<8.2f} "
                f"{trade['exit_reason']:<15}")
        
        # Print summary
        print("\n" + "="*100)
        print("TRADING SUMMARY")
        print("="*100)
        print(f"Total Trades: {summary['total_trades']}")
        print(f"Winning Trades: {summary['winning_trades']}")
        print(f"Losing Trades: {summary['losing_trades']}")
        print(f"Neutral Trades: {summary['neutral_trades']}")
        print(f"Win Rate: {summary['win_rate']:.2f}%")
        print(f"Total P&L: {summary['total_pnl']:.2f} points")
        print(f"Average P&L: {summary['average_pnl']:.2f} points")
        print(f"Profit Factor: {summary['profit_factor']:.2f}")
        
        # Print exit reasons breakdown
        print(f"\nExit Reasons Breakdown:")
        for reason, count in summary['exit_reasons'].items():
            percentage = (count / summary['total_trades']) * 100
            print(f"  {reason}: {count} trades ({percentage:.1f}%)")

            
    def fetch_and_process_data(self):
        elapsed = self.elapsed
        num_previous_days_from_elapsed = self.num_previous_days_from_elapsed
        now_ist = datetime.now(self.ist)
        logger.info(f"Current time IST: {now_ist}")
        if elapsed == 0:
            #num_previous_days_from_elapsed = 1
            self.createEntries = 1  # Enable order creation for live trading

        start_time = now_ist.replace(hour=8, minute=0, second=0) - timedelta(days = (elapsed + num_previous_days_from_elapsed))
        end_time = now_ist.replace(hour=16, minute=0, second=0) - timedelta(days = elapsed)
        interval = 1
        logger.info(f"Fetching Nifty data for elapsed days from:{elapsed + num_previous_days_from_elapsed} to:{elapsed}")
        
        niftydf = self.process_symbol_data('NSE:Nifty 50', interval, start_time, end_time)

        if niftydf.empty:
            logger.error("No data received, cannot proceed with trading")
            return
        
        self.print_entire_df(niftydf)

        '''
        try:
            niftydf.to_excel("nifty_trading_results.xlsx", index=False, engine='openpyxl')
            logger.info("Results saved to nifty_trading_results.xlsx")
        except Exception as e:
            logger.error(f"Error saving to Excel: {e}")
        '''
        
        # Execute trading simulation
        trades, summary = self.trading_simulation(niftydf)
        
        # Print trading results
        self.print_trading_results(trades, summary)

        return self.total_pnl, self.day_executed, self.num_trades


def get_client_details():
    try:
        logger.info("Fetching client details from API")
        url = 'http://143.244.141.41/php/getUserDetails.php'
        response = requests.get(url)
        response.raise_for_status()
        data = response.json()
        if not data.get('success', False):
            raise ValueError("getUserDetails endpoint returned unsuccessful response")
        user_data = data['data']
        return [user_data['field1'], user_data['field2'], user_data['field3'], user_data['field4'], user_data['field5']]
    except (requests.exceptions.RequestException, json.JSONDecodeError, KeyError) as e:
        logger.error(f"Client details request/parse error: {e}")
        return None

if __name__ == "__main__":

    #LIVE = False
    LIVE = True

    client_details = get_client_details()
    summary = {}
    overall_total_pnl = 0
    num_trades = 0

    if LIVE:
        elapsed = 0
        nuetralLimit = 7
        positiveThreshold = 10

        print(f"\n\n===== Starting trading for today =====")
        if client_details:
            stock_fetcher = StockDataFetcher( elapsed, client_details, nuetralLimit, positiveThreshold)
            stock_fetcher.login()
            total_pnl, day_executed, num_trades = stock_fetcher.fetch_and_process_data()
            tmp_summary = {
                'num_trades': num_trades,
                'day_executed': day_executed,
                'elapsed': elapsed,
                'day_executed': day_executed,
                'total_pnl': total_pnl,
                'neutralLimit': nuetralLimit,
                'positiveThreshold': positiveThreshold
            }
            summary[day_executed] = tmp_summary
            overall_total_pnl = overall_total_pnl + total_pnl
        else:
            logger.error("Failed to get client details, exiting")

    else:
        # For simulation */
        for elapsed in [2, 3, 6, 7, 8, 9, 10, 13, 14, 15, 16, 17, 20, 22, 23, 24, 27, 28, 29, 30, 31]:  
            for nuetralLimit in range(7, 8):  
                for positiveThreshold in range(5, 15):  

                    # not needed here but to ensure API rate limit is not hit - so change client details each time
                    client_details = get_client_details()
                    
                    print(f"\n\n===== Starting trading simulation for elapsed days: {elapsed}, neutralLimit: {nuetralLimit}, positiveThreshold: {positiveThreshold} =====")
                    if client_details:
                        stock_fetcher = StockDataFetcher(elapsed, client_details, nuetralLimit, positiveThreshold)
                        stock_fetcher.login()
                        total_pnl, day_executed, num_trades = stock_fetcher.fetch_and_process_data()
                        tmp_summary = {
                            'num_trades': num_trades,
                            'day_executed': day_executed,
                            'elapsed': elapsed,
                            'day_executed': day_executed,
                            'total_pnl': total_pnl,
                            'neutralLimit': nuetralLimit,
                            'positiveThreshold': positiveThreshold
                        }
                        # Create unique key by combining date with parameters
                        unique_key = f"{day_executed}_NL{nuetralLimit}_PT{positiveThreshold}"
                        if unique_key not in summary:
                            overall_total_pnl = overall_total_pnl + total_pnl
                        summary[unique_key] = tmp_summary
                    else:
                        logger.error("Failed to get client details, exiting")



    # Print final summary
    print("\n\n" + "="*60)
    print("TRADING SUMMARY")
    print("="*60)

    if summary:
        # Header
        print(f"{'Date':<12} {'Days':<6} {'PNL':<10} {'Limit':<8} {'Threshold':<10} {'num_trades':<10}")
        print("-"*60)
        
        # Rows - Format date as YYYY-MM-DD
        for day_executed, data in summary.items():
            date_str = data['day_executed'].strftime('%Y-%m-%d')  # Format datetime
            print(f"{date_str:<12} {data['elapsed']:<6} {data['total_pnl']:<10.2f} {data['neutralLimit']:<8} {data['positiveThreshold']:<10} {data['num_trades']:<10}")
        
        print("-"*60)
        print(f"{'TOTAL:':<18} {overall_total_pnl:>10.2f}")
        print("="*60)