# -*- coding: utf-8 -*-
"""THE_APPROACH_Strategy2_OPTIONS_09_02_2026.ipynb

Automatically generated by Colab.

Original file is located at
    https://colab.research.google.com/drive/18PraERPwH6cIHJ9DuefRb75ED-MYxIZN

# **INITIALIZE**

### PYTHON
"""

# from google.colab import drive
# drive.mount('/content/drive')

# import sys
# display(sys.path)

import sys
# sys.path.append('/content/drive/MyDrive/Colab_Python_Packages/fyers_apiv3/')
# sys.path.append('/content/drive/MyDrive/Colab_Python_Packages/TA-Lib/')

# display(sys.path)

# !pip install fyers_apiv3 --target=/content/drive/MyDrive/Colab_Python_Packages/fyers_apiv3/ # RUN ONLY ONCE FOR NEW PACKAGE INSTALLATION

# !pip install TA-Lib --target=/content/drive/MyDrive/Colab_Python_Packages/TA-Lib/ # RUN ONLY ONCE FOR NEW PACKAGE INSTALLATION

# %pip install fyers_apiv3 # DONOT USE HENCEFORTH

from fyers_apiv3 import fyersModel

import talib

# memory_path = '/content/drive/MyDrive/Colab_Python_Memory/'
memory_path = '/var/www/trade/beta/'

"""### FYERS"""

# Path to save token file in Google Drive
client_id_file = f"{memory_path}fyers/client_id_file.txt"
secret_key_file = f"{memory_path}fyers/secret_key_file.txt"
auth_code_file = f"{memory_path}fyers/auth_code_file.txt"
access_token_file = f"{memory_path}fyers/access_token_file.txt"
refresh_token_file = f"{memory_path}fyers/refresh_token_file.txt"
appIdHash_file = f"{memory_path}fyers/appIdHash_file.txt"
pin_file = f"{memory_path}fyers/pin_file.txt"

# Path to save paper_trading_positions in Google Drive
paper_trading_positions_file = f"{memory_path}fyers/paper_trading_positions_file.txt"

# Path to save live_trading_positions in Google Drive
live_trading_positions_file = f"{memory_path}fyers/live_trading_positions_file.txt"

# Path to save profitable_symbols_with_strategy in Google Drive
profitable_symbols_with_strategy_file = f"{memory_path}fyers/profitable_symbols_with_strategy_file.txt"

# Path to save paper_trade_book in Google Drive
paper_trade_book_file = f"{memory_path}fyers/paper_trade_book_file.txt"

# Path to save best_strategy_per_stock in Google Drive
best_strategy_per_stock_file = f"{memory_path}fyers/best_strategy_per_stock_file.txt"

"""**TRADE**"""

# current_path = '/content/drive/MyDrive/Colab Notebooks/The Approach/Options/Strategy2 - Short Straddle - Daily/'
current_path = '/var/www/trade/beta/Strategy2_Short_Straddle-Daily/'

# run_stocks = 'NIFTY50'
# run_stocks = 'NIFTYMidCap50'
# run_stocks = 'Lenin_Priya'
# run_stocks = 'NIFTY500'
run_stocks = 'NIFTY-EQ-ALL'

"""### TELEGRAM"""

telegram_bot_token_file = f"{memory_path}telegram/telegram_bot_token_file.txt"
telegram_chat_id_file = f"{memory_path}telegram/telegram_chat_id_file.txt"

"""# **FUNCTIONS**

### LOGIN FUNCTIONS
"""

import pandas as pd
import os

from fyers_apiv3 import fyersModel
import requests # Import requests for fetching auth code from URL

def fresh_login():

  # --- Setup credentials and redirect URI ---
  client_id = open(client_id_file, "r").read()  # Client_id is your APP_ID
  secret_key = open(secret_key_file, "r").read()  # App secret key
  redirect_uri = 'https://stock.work.gd/beta/fyers/fyers-calback.php'  # The redirect URI you entered while creating the app
  response_type = "code"
  grant_type = "authorization_code"
  state = "mmkhan_app" # A unique identifier for your session

  # --- Create session and generate auth URL ---
  session = fyersModel.SessionModel(
      client_id=client_id,
      secret_key=secret_key,
      redirect_uri=redirect_uri,
      response_type=response_type,
      grant_type=grant_type,
      state=state
  )

  # Generate the auth code using the session model
  response = session.generate_authcode()

  display(response)
  display("****************** Auth URL received ******************")

  sleep(1) # Wait for the auth code to be generated and saved

  # Get the authorization code from the user
  auth_code = open(auth_code_file, "r").read().strip()
  # display(f'You entered: {auth_code}')

  # --- Exchange code for access token ---
  session.set_token(auth_code)
  response = session.generate_token()
  # display(response)
  display("\n****************** Token response received ******************\n")

  access_token = response.get("access_token")

  # display(f"Access Token: {access_token}")
  # display(f"Response Status Code: {response.get("code")}\n")

  message=""

  print(f"Response Code: {response}")

  if response.get("code") == 200:

    display("************** Login Successful **************\n")

    try:
      access_token = response["access_token"]
      refresh_token = response["refresh_token"]
      # display("\nAccess Token:", access_token)
      # display("\nRefresh Token:", refresh_token)
    except Exception as e:
        display(e, response)

    # with open(client_id_file, "w") as file:
    #   file.write(client_id)

    with open(access_token_file, "w") as file:
        file.write(access_token)

    with open(refresh_token_file, "w") as file:
        file.write(refresh_token)

  else:

    display("************** Login Failed **************\n")
    message = "Kindly Check Login Code\n"
    # display("Kindly Check Login Code\n")

  return message

# Create Fyers Model

def create_fyers_model():

    # --- Use Fyers API ---
    fyers = fyersModel.FyersModel(
        token=open(access_token_file, "r").read(),
        is_async=False,
        client_id=open(client_id_file, "r").read(),
        log_path=""
    )

    return fyers

def get_profile_info(fyers):

    # Make a request to get the User information
    profile = fyers.get_profile()
    display("\nProfile data:", profile)

def get_funds_info(fyers):

    # Make a request to get the funds information
    response = fyers.funds()
    response = response['fund_limit']
    # display(response)
    # display("Funds data:", response)
    df_funds=pd.DataFrame(response)

    display(f"\nFunds Report:\n{df_funds}")

    display('\nAvailable Equity Funds:',df_funds[df_funds['title']=='Total Balance']['equityAmount'].iloc[0])

import requests
from fyers_apiv3 import fyersModel

def login_fyers():

    client_id = open(client_id_file, "r").read()
    appIdHash = open(appIdHash_file, "r").read()
    pin = open(pin_file, "r").read()

    message = ""

    try:
      refresh_token = open(refresh_token_file, "r").read()
      access_token = open(access_token_file, "r").read()
      # client_id = open(client_id_file, "r").read()

      url = 'https://api-t1.fyers.in/api/v3/validate-refresh-token'
      headers = {
          'Content-Type': 'application/json',
      }

      '''
      You need to generate appIdHash of your fyers api.
      SHA-256 of api_id + app_secret. Eg: SHA-256 of app_id:app_secret is 73a13acc79651212f62d9aded027452215782e2e2c38fff612**************
      You can use this online tool for reference (https://emn178.github.io/online-tools/sha256.html)

      For eg: app_id = NSOCB*****-100
              app_secret = 4NDAG*****
              Then open given website and enter NSOCB*****-100:4NDAG***** to generate a appIdHash value. Use it below.

      '''

      data = {
          "grant_type": "refresh_token",
          "appIdHash": appIdHash,
          "refresh_token": refresh_token,
          "pin": pin
      }


      response = requests.post(url, headers=headers, json=data)

      # display(f"Response Status Code: {response.status_code}\n")


      if response.status_code == 200:

        display("************** Login Successful **************\n")

        try:
            access_token = response.json()["access_token"]
            # display("\nAccess Token:", access_token)
        except Exception as e:
            display(e, response)

        # Successful Response
        # {
        #     "s": "ok",
        #     "code": 200,
        #     "message": "",
        #     "access_token": "eyJ0eXAiOiJK***.eyJpc3MiOiJhcGkuZnllcnM***.IzcuRxg4tnXiULCx3***"
        # }

        # In this way access token is generated which can be used further to login, we can save this in a file.
        # Save client_id and access_token to a file

        with open(access_token_file, "w") as file:
            file.write(access_token)

      else:

        display("************** Login Failed **************\n")
        display("Possible Refresh Token Expired..!!\n")
        display("Executing Fresh Login\n")

        message = fresh_login()

    except Exception as e:
      # display(f"{e} !!!\n")
      display("************** Login Failed **************\n")
      display("Necessary Tokens BackUp Not Found..!!\n")
      display("Executing Fresh Login\n")
      message = fresh_login()
      refresh_token = open(refresh_token_file, "r").read()

    if message != 'Kindly Check Login Code\n':
      # Create Fyers Model
      fyers = create_fyers_model()
      get_profile_info(fyers)
      get_funds_info(fyers)
    else:
      display(message)

# import requests
# from fyers_apiv3 import fyersModel

# def login_fyers():

#     client_id = open(client_id_file, "r").read()
#     appIdHash = open(appIdHash_file, "r").read()
#     pin = open(pin_file, "r").read()

#     try:
#       refresh_token = open(refresh_token_file, "r").read()
#       access_token = open(access_token_file, "r").read()
#       # client_id = open(client_id_file, "r").read()


#     except Exception as e:
#       # display(f"{e} !!!\n")
#       display("************** Login Failed **************\n")
#       display("Executing Fresh Login\n")
#       fresh_login()
#       refresh_token = open(refresh_token_file, "r").read()
#       # client_id = open(client_id_file, "r").read()

#     url = 'https://api-t1.fyers.in/api/v3/validate-refresh-token'
#     headers = {
#         'Content-Type': 'application/json',
#     }

#     '''
#     You need to generate appIdHash of your fyers api.
#     SHA-256 of api_id + app_secret. Eg: SHA-256 of app_id:app_secret is 73a13acc79651212f62d9aded027452215782e2e2c38fff612**************
#     You can use this online tool for reference (https://emn178.github.io/online-tools/sha256.html)

#     For eg: app_id = NSOCB*****-100
#             app_secret = 4NDAG*****
#             Then open given website and enter NSOCB*****-100:4NDAG***** to generate a appIdHash value. Use it below.

#     '''

#     data = {
#         "grant_type": "refresh_token",
#         "appIdHash": appIdHash,
#         "refresh_token": refresh_token,
#         "pin": pin
#     }


#     response = requests.post(url, headers=headers, json=data)

#     # display(f"Response Status Code: {response.status_code}\n")


#     if response.status_code == 200:

#       display("************** Login Successful **************\n")

#       try:
#           access_token = response.json()["access_token"]
#           # display("\nAccess Token:", access_token)
#       except Exception as e:
#           display(e, response)

#       # Successful Response
#       # {
#       #     "s": "ok",
#       #     "code": 200,
#       #     "message": "",
#       #     "access_token": "eyJ0eXAiOiJK***.eyJpc3MiOiJhcGkuZnllcnM***.Izc�Rxg4tnXiULCx3***"
#       # }

#       # In this way access token is generated which can be used further to login, we can save this in a file.
#       # Save client_id and access_token to a file

#       # with open(access_token_file, "w") as file:
#       #     file.write(access_token)

#     else:

#       display("************** Login Failed **************\n")
#       display("Executing Fresh Login\n")

#       fresh_login()

#     # # Create Fyers Model

#     # # --- Use Fyers API ---
#     # fyers = fyersModel.FyersModel(
#     #     token=open(access_token_file, "r").read(),
#     #     is_async=False,
#     #     client_id=open(client_id_file, "r").read(),
#     #     log_path=""
#     # )

#     fyers = create_fyers_model()
#     get_profile_info(fyers)
#     get_funds_info(fyers)

"""### HELPER FUNCTIONS"""

import pandas as pd
import sys

def display(*args, sep=" ", end="\n"):
    """
    Drop-in replacement for print() with:
    - DataFrame full table rendering
    - Proper newline handling
    - Logging support
    """

    # Handle single DataFrame specially
    if len(args) == 1 and isinstance(args[0], pd.DataFrame):
        with pd.option_context(
            'display.max_rows', None,
            'display.max_columns', None,
            'display.width', None,
            'display.max_colwidth', None
        ):
            output = args[0].to_string()
    else:
        output = sep.join(str(arg) for arg in args)

    # Print EXACTLY like print()
    print(output, end=end)

    # Log EXACT same output including newline behavior
    with open(f"{current_path}report.log", "a", encoding="utf-8") as log_file:
        log_file.write(output + end)

# Optional: Set pandas display options for better terminal viewing
pd.set_option('display.max_columns', None)
pd.set_option('display.width', 1000)

# Function: Fetch Historic Data using yfinance

import yfinance as yf
import pandas as pd
from datetime import datetime, timedelta
# import datetime as dt
import pytz

INTERVAL = "15"         # 15 Minute candles
# CHECK_FREQUENCY = 60*60 # Every 1 hour (3600 sec)

def fetch_historic_data_yf(ticker, days):

    # Define the Indian timezone
    # IST is represented as 'Asia/Kolkata' in pytz
    ist_tz = pytz.timezone('Asia/Kolkata')

    # Define the stock ticker for an Indian company (example: State Bank of India)
    ticker_symbol = ticker + '.NS'

    # Define the time period
    # It's best practice to use timezone-aware datetime objects for start and end
    # when passing them to yfinance. This is already done correctly.
    end_date = datetime.now(ist_tz)
    # end_date = dt.date.today()
    start_date = end_date - timedelta(days=days+1) # Fetching data for the last n days
    # start_date = dt.date.today()-dt.timedelta(days=days)

    # Download the data with a 15-minute interval
    data = yf.download(
        ticker_symbol,
        start=start_date,
        end=end_date,
        interval=INTERVAL + 'm',
        auto_adjust=True,
        progress=False # Suppress the progress bar
    )

    # --- UPDATE: Convert the index timezone to IST (Asia/Kolkata) ---
    # 1. The downloaded index is usually in UTC.
    # 2. Use .tz_convert() to change the timezone to 'Asia/Kolkata'.
    #    If the index is timezone-naive (which sometimes happens), you might need
    #    to localize it first using .tz_localize('UTC') before converting.
    if data.index.tz is None:
        # Localize a naive index assuming it's UTC (yfinance standard)
        data.index = data.index.tz_localize('UTC').tz_convert(ist_tz)
    else:
        # Convert an already timezone-aware index
        data.index = data.index.tz_convert(ist_tz)
    # -----------------------------------------------------------------

    # # Print the data and check the index (which contains the timestamps)
    # display(f"Downloaded data for {ticker_symbol} with 15m intervals:")
    # display(data.head())
    # display("\nTimezone of the data index:", data.index.tz)
    # display("\nFirst few timestamps in IST:")
    # display(data.index[:5])

    df=pd.DataFrame(data)

    df.columns = df.columns.droplevel(level='Ticker')
    df.columns.name = None

    df.index = df.index.tz_localize(None)

    df.index.rename('date', inplace=True)
    df.rename(columns={'Volume': 'volume'}, inplace=True)
    df = df[['Open', 'High', 'Low', 'Close', 'volume']]

    df = df.round({
        'Open': 2,
        'High': 2,
        'Low': 2,
        'Close': 2,
        'volume': 0  # Optional: Round Volume to 0 decimals (integer)
    })

    return df

# Function: Fetch Historic Data

import datetime as dt

INTERVAL = "15"         # 15 Minute candles
# CHECK_FREQUENCY = 60*60 # Every 1 hour (3600 sec)

def fetch_historic_data(ticker, days):

  ticker='NSE:' + ticker + '-EQ'
  # ticker='NSE:ADANIENT-EQ'
  # display(f"Processing stock: {ticker}")
  data = {
    "symbol":ticker,
    # "symbol":"NSE:ADANIENT-EQ",
    # "symbol":"MCX:GOLD25SEPFUT",
    "resolution":INTERVAL,
    "date_format":"1",
    # "range_from":"2025-06-08",
    "range_from":dt.date.today()-dt.timedelta(days=days), # 1-minute to 1-hour resolution: You can fetch up to 100 days of data per request. Daily, Weekly, or Monthly resolution: You can fetch up to 1000 days of data per request.

    # "range_to":"2025-09-08",
    "range_to":dt.date.today(),
    "cont_flag":"1"
  }

  fyers = create_fyers_model()

  response = fyers.history(data=data)

  data=response['candles']
  df=pd.DataFrame(data)
  # display(df.tail())

  df.columns=['date','Open','High','Low','Close','volume']
  df['date']=pd.to_datetime(df['date'],unit='s')
  df.date=(df.date.dt.tz_localize('UTC').dt.tz_convert('Asia/Kolkata'))
  df['date']=df['date'].dt.tz_localize(None)

  df.to_csv(f'{ticker}.csv',index=False)

  df=df.set_index('date')

  return df

import datetime as dt
from time import sleep
import time
import sys
import pytz

def get_now_ist():
  # Get the current time in UTC and convert to IST
  now_utc = dt.datetime.now(dt.timezone.utc)
  now_ist = now_utc.astimezone(pytz.timezone('Asia/Kolkata'))
  return now_ist

# Function: Capture and save output

# List to store output messages and timestamps
output_messages = []

def capture_output(message, output_file_path):
    timestamp = get_now_ist()
    output_messages.append({'time': timestamp, 'statement': message})
    display(message) # Still print to console for real-time monitoring

    # Save all output messages to a CSV file
    if output_messages:
        df_output = pd.DataFrame(output_messages)
        df_output.to_csv(output_file_path, index=False)
        # display(f"\nOutput messages saved to: {output_file_path}")

# Function: Save trades

def save_tradebook(trade_list, tradebook_file_path):
    """
    Creates a DataFrame from a list of paper trades and saves it to a CSV file.

    Args:
        trade_list (list): A list of dictionaries, each representing a trade.
        tradebook_file_path (str): The file path to save the tradebook CSV.
    """
    if trade_list:
        df_tradebook = pd.DataFrame(trade_list)
        df_tradebook.to_csv(tradebook_file_path, index=False)
        # display(f"\nPaper tradebook saved to: {tradebook_file_path}")
        # display(df_tradebook) # Display the tradebook
    else:
        display("\nNo trades recorded for the paper trading session.")

# Function: Fetch current price

def lp(ticker):
  ticker='NSE:' + ticker + '-EQ'
  data = {
    "symbols":ticker
  }

  fyers = create_fyers_model()

  response = fyers.quotes(data=data)

  return response['d'][0]['v']['lp']

# Function: Telegram Bot

import requests

# Replace with your details
BOT_TOKEN = open(telegram_bot_token_file, "r").read()  # Client_id is your APP_ID   # Your BotFather token
CHAT_ID = open(telegram_chat_id_file, "r").read()                                # Your chat_id from @userinfobot

def send_telegram_message(message: str):
    url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage"
    payload = {
        "chat_id": CHAT_ID,
        "text": message
    }
    try:
        response = requests.post(url, data=payload)
        if response.status_code == 200:
            display("✅ Message sent successfully!")
        else:
            display("❌ Failed to send:", response.text)
    except Exception as e:
        display("Error:", e)

"""### STRATEGIES"""

# Strategy INDEX

# s1 = 'Strategy1 - Price & Volume Change'
# s2 = 'Strategy2 - EMA 10|20'
# s3 = 'Strategy3 - MACD 12|26|9'
s4 = 'Strategy4 - RSI 14'
s5 = 'Strategy5 - Advanced MACD 12|26|9'

# Function: Strategy1 - Price & Volume Change

import pandas as pd

def strategy1(df):
    """
    Generates trading signals based on price and volume relationships.

    Args:
        df (pd.DataFrame): A DataFrame with 'Close' and 'volume' columns.

    Returns:
        pd.DataFrame: The input DataFrame with a new 'signal' column.
    """

    display(f"\n**** Running {s1} ****")

    df['price_change'] = df['Close'].pct_change()
    # display(f"\nPrice Change: {df['price_change']}")
    # display(f"\nLatest Price % Change: {df['price_change'].iloc[-1]*100:.2f}%")

    df['volume_change'] = df['volume'].pct_change()
    # display(f"\nVolume Change: {df['volume_change']}")
    # display(f"\nLatest Volume % Change: {df['volume_change'].iloc[-1]*100:.2f}%")

    df['signal'] = 'HOLD'

    # # Bullish confirmation: Rising price and rising volume
    # df.loc[(df['price_change'] > 0) & (df['volume_change'] > 0), ['analysis', 'signal']] = 'Bullish confirmation', 'BUY'

    # # Bearish confirmation: Falling price and rising volume
    # df.loc[(df['price_change'] < 0) & (df['volume_change'] > 0), ['analysis', 'signal']] = 'Bearish confirmation', 'SELL'

    # # Potential bearish reversal: Rising price and falling volume
    # df.loc[(df['price_change'] > 0) & (df['volume_change'] < 0), ['analysis', 'signal']] = 'Potential bearish reversal', 'SELL'

    # # Potential bullish reversal: Falling price and falling volume
    # df.loc[(df['price_change'] < 0) & (df['volume_change'] < 0), ['analysis', 'signal']] = 'Potential bullish reversal', 'HOLD'

    # Bullish confirmation: Rising price and rising volume
    df.loc[(df['price_change'] > 0) & (df['volume_change'] > 0), 'signal'] = 'BUY'

    # Bearish confirmation: Falling price and rising volume
    df.loc[(df['price_change'] < 0) & (df['volume_change'] > 0), 'signal'] = 'SELL'

    # Potential bearish reversal: Rising price and falling volume
    df.loc[(df['price_change'] > 0) & (df['volume_change'] < 0), 'signal'] = 'SELL'

    # Potential bullish reversal: Falling price and falling volume
    df.loc[(df['price_change'] < 0) & (df['volume_change'] < 0), 'signal'] = 'HOLD'

    # return df.drop(columns=['price_change', 'volume_change'])
    return df

# Example Usage:
# # Create a sample DataFrame
# data = {'Close': [10, 11, 12, 11.5, 10, 10.5, 12, 11, 11.5, 12],
#         'volume': [1000, 1500, 2000, 1800, 2200, 2100, 2500, 2300, 1900, 1600]}


# df = pd.DataFrame(data)

# # Generate signals
# df_with_signals = strategy1(df)
# display(df_with_signals.to_string())

# Function: Strategy2 - EMA Crossover

import pandas as pd

def strategy2(df, short_period=10, long_period=20):
    """
    Generates trading signals based on the EMA Crossover strategy.

    Args:
        df: pandas DataFrame with OHLCV data. Must contain a 'Close' column.
        short_period: The period for the short EMA.
        long_period: The period for the long EMA.

    Returns:
        pandas DataFrame: The input DataFrame with short_ema, long_ema, and
                          signal columns added.
    """
    display(f"\n**** Running {s2} ****")

    # Calculate the short and long EMAs
    df['short_ema'] = df['Close'].ewm(span=short_period, adjust=False).mean()
    df['long_ema'] = df['Close'].ewm(span=long_period, adjust=False).mean()

    # Generate trading signals
    signals = pd.Series('HOLD', index=df.index)

    # Buy signal: Short EMA crosses above Long EMA
    buy_condition = (df['short_ema'].shift(1) < df['long_ema'].shift(1)) & (df['short_ema'] > df['long_ema'])
    signals[buy_condition] = 'BUY'

    # Sell signal: Short EMA crosses below Long EMA
    sell_condition = (df['short_ema'].shift(1) > df['long_ema'].shift(1)) & (df['short_ema'] < df['long_ema'])
    signals[sell_condition] = 'SELL'

    # Add signals to the DataFrame
    df['signal'] = signals

    # Return the DataFrame with signals
    return df

# Example usage (assuming you have a DataFrame named 'ohlcv_df' with 'Close' column)
# df_with_signals = strategy2(ohlcv_df)
# display(df_with_signals.head())

# Function: Strategy3 - MACD

import pandas as pd

def strategy3(df, fast_period=12, slow_period=26, signal_period=9):
    """
    Generates trading signals based on the MACD strategy.

    Args:
        df: pandas DataFrame with OHLCV data. Must contain a 'Close' column.
        fast_period: The period for the fast EMA.
        slow_period: The period for the slow EMA.
        signal_period: The period for the signal line EMA.

    Returns:
        pandas DataFrame: The input DataFrame with MACD, Signal_Line, Histogram,
                          and signal columns added.
    """

    display(f"\n**** Running {s3} ****")

    # # Calculate the MACD line
    # exp1 = df['Close'].ewm(span=fast_period, adjust=False).mean()
    # exp2 = df['Close'].ewm(span=slow_period, adjust=False).mean()
    # macd = exp1 - exp2

    # # Calculate the signal line
    # signal = macd.ewm(span=signal_period, adjust=False).mean()

    # # Calculate the histogram
    # histogram = macd - signal

    macd, signal, histogram = talib.MACD(
                                            df['Close'],
                                            fastperiod=fast_period,
                                            slowperiod=slow_period,
                                            signalperiod=signal_period
                                        )

    # Calculate MACD Slope
    macd_slope = (macd - macd.shift(1))/(1-0)

    # Generate trading signals
    signals = pd.Series('HOLD', index=df.index)

    # # BUY signal: MACD crosses above Signal Line AND MACD histogram is less than or equal to 1
    # # buy_condition = (macd > signal) # & (histogram <= 1)
    # buy_condition = (macd > signal) & (macd_slope > 0)
    # signals[buy_condition] = 'BUY'

    # # sell_condition = (macd < signal)
    # sell_condition = (macd_slope <= 0)
    # signals[sell_condition] = 'SELL'


    # buy_condition1 = (macd<0) & (signal<0) & (macd<signal) & (macd_slope.shift(1)<=0) & (macd_slope>0)
    # buy_condition2 = (macd>0) & (signal>0) & (macd>signal) & (macd_slope.shift(1)<=0) & (macd_slope>0)
    # buy_condition3 = (macd>signal) & (macd_slope>0)
    # buy_condition4 = (macd.shift(1) < signal.shift(1)) & (macd > signal) & (macd < 0) & (signal < 0)

    # buy_condition = buy_condition1 | buy_condition2 | buy_condition3 | buy_condition4

    # signals[buy_condition] = 'BUY'

    # sell_condition1 = (macd>0) & (signal>0) & (macd>signal) & (macd_slope.shift(1)>0) & (macd_slope<=0)
    # sell_condition2 = (macd<0) & (signal<0) & (macd<signal) & (macd_slope.shift(1)>0) & (macd_slope<=0)
    # sell_condition3 = (macd.shift(1) > signal.shift(1)) & (macd < signal)

    # sell_condition = sell_condition1 | sell_condition2 | sell_condition3
    # signals[sell_condition] = 'SELL'

    buy_condition1 = (macd<0) & (signal<0) & (macd<signal) & (macd_slope.shift(1)<=0) & (macd_slope>0)  # Pre potential crossover below zero line
    buy_condition2 = (macd < 0) & (signal < 0) & (macd.shift(1) < signal.shift(1)) & (macd > signal)  # Confirmed MACD CrossOver above Signal line while both are below zero line
    buy_condition3 = (macd>signal) & (macd_slope>0) # MACD and Signal are in diverging trend
    buy_condition4 = (macd>signal) & (macd_slope.shift(1)<=0) & (macd_slope>0)  # MACD drops towards signal line and bounces back

    buy_condition = buy_condition1 | buy_condition2 | buy_condition3 | buy_condition4

    signals[buy_condition] = 'BUY'



    sell_condition1 = (macd<signal) & (macd_slope.shift(1)>0) & (macd_slope<=0) # MACD turns away from Signal while it is below Signal
    sell_condition2 = (macd>signal) & (macd_slope.shift(1)>0) & (macd_slope<=0) # MACD turns towards Signal while it is above Signal
    sell_condition3 = (macd.shift(1) > signal.shift(1)) & (macd < signal) # Confirmed MACD CrossOver below signal line

    sell_condition = sell_condition1 | sell_condition2 | sell_condition3

    signals[sell_condition] = 'SELL'


    # Add MACD, Signal, and Histogram to the DataFrame for potential plotting/analysis
    df['MACD'] = macd # Uncomment if you still need these in the original DataFrame
    df['Signal_Line'] = signal
    df['Histogram'] = histogram
    df['MACD_Slope'] = macd_slope
    df['signal'] = signals # Add the signal column to the DataFrame

    # Return the DataFrame with signals
    return df

# Example usage (assuming you have a DataFrame named 'ohlcv_df' with 'Close' column)
# latest_signal = strategy2(ohlcv_df)
# display(latest_signal)

# # Function: Strategy4 - RSI with SL/TP

# import pandas as pd
# import yfinance as yf
# import numpy as np
# import talib

# # -----------------------------
# # Function to calculate RSI and generate signals with SL/TP
# # -----------------------------
# def strategy4(df, period=14, lower=30, upper=70, stop_loss_percent=0.01, profit_ratio=4.0, bounce_period=10):

#     """
#     Generates trading signals based on the RSI strategy with conditions for
#     oversold/overbought levels, trend confirmation, and divergence. Returns
#     the DataFrame with final signals, including Stop Loss and Take Profit levels.

#     Args:
#         df: pandas DataFrame with OHLCV data. Must contain 'Close', 'Low', and 'High' columns.
#         period: The period for the RSI calculation (default = 14).
#         lower: The lower RSI threshold (default = 30). Values below this are
#                considered oversold.
#         upper: The upper RSI threshold (default = 70). Values above this are
#                considered overbought.
#         stop_loss_percent: The percentage below the entry price for the stop loss (default = 1%).
#         profit_ratio: The risk-reward ratio for calculating the take profit (default = 1.5).
#         bounce_period: The lookback period for finding a recent low for stop loss reference (default = 10).

#     Returns:
#         A pandas DataFrame with additional columns:
#             'RSI': The calculated RSI values.
#             'signal': Trading signals ('BUY', 'SELL', or 'HOLD').
#             'Entry_Price': The price at which a BUY signal was generated.
#             'Stop_Loss': The calculated stop loss price for a BUY signal.
#             'Take_Profit': The calculated take profit price for a BUY signal.
#             'Comments': Explanation for the generated signal.
#     """

#     # display(f"\n**** Running {s4} ****")

#     # Calculate RSI
#     df['RSI'] = talib.RSI(df['Close'], timeperiod=period)

#     # Calculate recent low for potential stop loss reference
#     recent_low = df['Low'].rolling(window=bounce_period).min().shift(1)
#     df['Recent_Low'] = recent_low


#     # Initialize columns
#     signals = pd.Series('HOLD', index=df.index, dtype=object)
#     comments = pd.Series('', index=df.index, dtype=object)
#     entry_prices = pd.Series(pd.NA, index=df.index, dtype=object)
#     stop_losses = pd.Series(pd.NA, index=df.index, dtype=object)
#     take_profits = pd.Series(pd.NA, index=df.index, dtype=object)

#     # Trade State Variables
#     in_trade = False
#     entry_price = 0
#     stop_loss = 0
#     take_profit = 0


#     for i in range(1, len(df)):
#         current_index = df.index[i]
#         previous_index = df.index[i-1]

#         rsi = df['RSI'].iloc[i]
#         price = df['Close'].iloc[i]
#         low_price = df['Low'].iloc[i]
#         high_price = df['High'].iloc[i]
#         current_recent_low = df['Recent_Low'].iloc[i]


#         if np.isnan(rsi):
#             signals.iloc[i] = "HOLD"
#             comments.iloc[i] = "Insufficient data for RSI."
#             continue

#         # --- DIVERGENCE CHECK (simplified) ---
#         price_change = df['Close'].iloc[i] - df['Close'].iloc[i-1]
#         rsi_change = df['RSI'].iloc[i] - df['RSI'].iloc[i-1]


#         # A. Check for NEW BUY signal
#         buy_condition_oversold = rsi < lower and rsi_change > 0  # Oversold and turning up
#         buy_condition_divergence = price_change < 0 and rsi_change > 0  # Bullish divergence

#         # Combine BUY conditions (you can refine this logic)
#         buy_signal_generated = (buy_condition_oversold or buy_condition_divergence) and not in_trade


#         if buy_signal_generated:
#             in_trade = True
#             entry_price = price
#             entry_prices.iloc[i] = entry_price

#             # Calculate SL based on recent low or a percentage below entry
#             if not np.isnan(current_recent_low):
#                 stop_loss = current_recent_low * (1 - 0.001)  # Small buffer below recent low
#             else:
#                  stop_loss = entry_price * (1 - stop_loss_percent) # Fallback to percentage based SL

#             stop_losses.iloc[i] = stop_loss

#             # Calculate TP based on risk-reward ratio
#             risk = entry_price - stop_loss
#             take_profit = entry_price + (risk * profit_ratio)
#             take_profits.iloc[i] = take_profit

#             signals.iloc[i] = 'BUY'
#             comments.iloc[i] = (f"ENTRY: RSI {'Oversold and rising' if buy_condition_oversold else 'Bullish divergence'}. SL: {stop_loss:.4f}, TP: {take_profit:.4f}")

#         # B. Check for EXIT (SL or TP or original SELL condition) if in a trade
#         elif in_trade:
#             # SL Trigger: Low price hit or passed the Stop Loss
#             sl_hit = low_price <= stop_loss

#             # TP Trigger: High price hit or passed the Take Profit
#             tp_hit = high_price >= take_profit

#             # Original SELL condition: RSI > upper and falling, or bearish divergence
#             sell_condition_overbought = rsi > upper and rsi_change < 0
#             sell_condition_divergence = price_change > 0 and rsi_change < 0
#             original_sell_signal = sell_condition_overbought or sell_condition_divergence


#             if sl_hit and tp_hit:
#                 # Ambiguous case: If both are hit in the same bar, assume TP hit for aggressive strategy
#                 signals.iloc[i] = 'SELL'
#                 in_trade = False
#                 comments.iloc[i] = f"EXIT: Take Profit (TP) hit. Price hit {take_profit:.4f} in same candle as SL. Profit Ratio: {profit_ratio}."

#             elif sl_hit:
#                 signals.iloc[i] = 'SELL'
#                 in_trade = False
#                 comments.iloc[i] = f"EXIT: Stop Loss (SL) hit. Low reached {stop_loss:.4f}."

#             elif tp_hit:
#                 signals.iloc[i] = 'SELL'
#                 in_trade = False
#                 comments.iloc[i] = f"EXIT: Take Profit (TP) hit. High reached {take_profit:.4f}. Profit Ratio: {profit_ratio}."

#             elif original_sell_signal:
#                  signals.iloc[i] = 'SELL'
#                  in_trade = False
#                  comments.iloc[i] = (f"EXIT: Original SELL signal triggered: RSI {'Overbought and falling' if sell_condition_overbought else 'Bearish divergence'}.")

#             # C. If no exit, keep the signal as 'HOLD'
#             else:
#                 signals.iloc[i] = 'HOLD'
#                 comments.iloc[i] = f"HOLD: Position open. SL: {stop_loss:.4f}, TP: {take_profit:.4f}"

#         # D. If not in a trade and no BUY signal, keep as HOLD
#         else:
#             signals.iloc[i] = 'HOLD'
#             comments.iloc[i] = "HOLD: No active signal or in neutral zone."


#         # E. Propagate SL/TP and Entry Price for HOLD periods within a trade
#         if in_trade and signals.iloc[i] == 'HOLD':
#              entry_prices.iloc[i] = entry_price
#              stop_losses.iloc[i] = stop_loss
#              take_profits.iloc[i] = take_profit



#     # --- Finalize DataFrame ---

#     df['signal'] = signals
#     df['Comments'] = comments
#     df['Entry_Price'] = entry_prices
#     df['Stop_Loss'] = stop_losses
#     df['Take_Profit'] = take_profits

#     return df

# # # Example usage (assuming you have a DataFrame named 'ohlcv_df' with 'Close' column)
# # # latest_signal = strategy4(ohlcv_df)
# # # display(latest_signal)

# Function: Strategy4 - RSI with SL/TP

import pandas as pd
import yfinance as yf
import numpy as np
import talib

# -----------------------------
# Function to calculate RSI and generate signals with SL/TP
# -----------------------------
def strategy4(df, period=14, lower=30, upper=70, atr_period=50, atr_multiplier=3, profit_ratio=4.0):  # , bounce_period=10, trailing_profit_ratio=2.0

    """
    Generates trading signals based on the RSI strategy with conditions for
    oversold/overbought levels, trend confirmation, and divergence. Returns
    the DataFrame with final signals, including Stop Loss and Take Profit levels.

    Args:
        df: pandas DataFrame with OHLCV data. Must contain 'Close', 'Low', and 'High' columns.
        period: The period for the RSI calculation (default = 14).
        lower: The lower RSI threshold (default = 30). Values below this are
               considered oversold.
        upper: The upper RSI threshold (default = 70). Values above this are
               considered overbought.
        atr_period: The period for the ATR calculation (default = 14).
        atr_multiplier: The multiplier for the ATR to calculate the stop loss distance (default = 2).
        profit_ratio: The risk-reward ratio for calculating the initial take profit (default = 1.5).
        bounce_period: The lookback period for finding a recent low for stop loss reference (default = 10).
        trailing_profit_ratio: The risk-reward ratio for the trailing stop loss and take profit (default = 2.0).


    Returns:
        A pandas DataFrame with additional columns:
            'RSI': The calculated RSI values.
            'ATR': The calculated ATR values.
            'signal': Trading signals ('BUY', 'SELL', or 'HOLD').
            'Entry_Price': The price at which a BUY signal was generated.
            'Stop_Loss': The calculated stop loss price for a BUY signal.
            'Take_Profit': The calculated take profit price for a BUY signal.
            'Comments': Explanation for the generated signal.
    """

    display(f"\n**** Running {s4} - NEW - TRAILING SL AND TP USING ATR ****")
    # display(f"\n**** Running RSI - NEW1 - TRAILING SL AND TP USING ATR ****")


    # Calculate RSI and ATR
    df['RSI'] = talib.RSI(df['Close'], timeperiod=period)
    df['ATR'] = talib.ATR(df['High'], df['Low'], df['Close'], timeperiod=atr_period)


    # Initialize columns
    signals = pd.Series('HOLD', index=df.index, dtype=object)
    comments = pd.Series('', index=df.index, dtype=object)
    entry_prices = pd.Series(pd.NA, index=df.index, dtype=object)
    stop_losses = pd.Series(pd.NA, index=df.index, dtype=object)
    take_profits = pd.Series(pd.NA, index=df.index, dtype=object)
    # ep_for_dynamic_sl_tps = pd.Series(pd.NA, index=df.index, dtype=object)


    # Trade State Variables
    in_trade = False
    entry_price = 0
    stop_loss = 0
    take_profit = 0
    # ep_for_dynamic_sl_tp = 0


    for i in range(1, len(df)):
        current_index = df.index[i]
        previous_index = df.index[i-1]

        rsi = df['RSI'].iloc[i]
        atr = df['ATR'].iloc[i]
        price = df['Close'].iloc[i]
        low_price = df['Low'].iloc[i]
        high_price = df['High'].iloc[i]


        if np.isnan(rsi) or np.isnan(atr):
            signals.iloc[i] = "HOLD"
            comments.iloc[i] = "Insufficient data for RSI or ATR."
            continue

        # --- DIVERGENCE CHECK (simplified) ---
        price_change = df['Close'].iloc[i] - df['Close'].iloc[i-1]
        rsi_change = df['RSI'].iloc[i] - df['RSI'].iloc[i-1]


        # A. Check for NEW BUY signal
        buy_condition_oversold = rsi < lower and rsi_change > 0  # Oversold and turning up
        buy_condition_divergence = price_change < 0 and rsi_change > 0  # Bullish divergence

        # Combine BUY conditions (you can refine this logic)
        buy_signal_generated = (buy_condition_oversold or buy_condition_divergence) and not in_trade


        if buy_signal_generated:
            in_trade = True
            entry_price = price
            entry_prices.iloc[i] = entry_price
            # ep_for_dynamic_sl_tp = entry_price # Initialize for dynamic SL/TP

            # Calculate initial SL based on ATR below entry price
            stop_loss = entry_price - (atr * atr_multiplier)
            stop_losses.iloc[i] = stop_loss

            # Calculate initial TP based on risk-reward ratio
            risk = entry_price - stop_loss
            take_profit = entry_price + (risk * profit_ratio)
            take_profits.iloc[i] = take_profit

            signals.iloc[i] = 'BUY'
            comments.iloc[i] = (f"ENTRY: RSI {'Oversold and rising' if buy_condition_oversold else 'Bullish divergence'}. Initial SL: {stop_loss:.4f}, Initial TP: {take_profit:.4f}")

        # B. Check for EXIT (SL or TP or original SELL condition) if in a trade
        elif in_trade:

        #######################################################################
            # Trailing Stop Loss and Take Profit Update using ATR
            trailing_stop_loss = entry_price - (atr * atr_multiplier)
            stop_loss = max(stop_loss, trailing_stop_loss)

            # Recalculate Take Profit based on the updated Stop Loss
            risk = entry_price - stop_loss
            take_profit = entry_price + (risk * profit_ratio)

            comments.iloc[i] = f"HOLD: Trailing SL/TP updated. New SL: {stop_loss:.4f}, New TP: {take_profit:.4f}"
        #######################################################################

            # SL Trigger: Low price hit or passed the Stop Loss
            sl_hit = low_price <= stop_loss

            # TP Trigger: High price hit or passed the Take Profit
            tp_hit = high_price >= take_profit

            # Original SELL condition: RSI > upper and falling, or bearish divergence
            sell_condition_overbought = rsi > upper and rsi_change < 0
            sell_condition_divergence = price_change > 0 and rsi_change < 0
            original_sell_signal = sell_condition_overbought or sell_condition_divergence


            if sl_hit and tp_hit:
                # Ambiguous case: If both are hit in the same bar, assume TP hit for aggressive strategy
                signals.iloc[i] = 'SELL'
                in_trade = False
                comments.iloc[i] = f"EXIT: Take Profit (TP) hit. Price hit {take_profit:.4f} in same candle as SL. Profit Ratio: {profit_ratio}."

            elif sl_hit:
                signals.iloc[i] = 'SELL'
                in_trade = False
                comments.iloc[i] = f"EXIT: Stop Loss (SL) hit. Low reached {stop_loss:.4f}."

            elif tp_hit:
                signals.iloc[i] = 'SELL'
                in_trade = False
                comments.iloc[i] = f"EXIT: Take Profit (TP) hit. High reached {take_profit:.4f}. Profit Ratio: {profit_ratio}."

            elif original_sell_signal:
                 signals.iloc[i] = 'SELL'
                 in_trade = False
                 comments.iloc[i] = (f"EXIT: Original SELL signal triggered: RSI {'Overbought and falling' if sell_condition_overbought else 'Bearish divergence'}.")

            # C. If no exit, keep the signal as 'HOLD' and propagate SL/TP
            else:
                signals.iloc[i] = 'HOLD'
                # if comments.iloc[i] != "":
                #     comments.iloc[i] = f"HOLD: Position open. SL: {stop_loss:.4f}, TP: {take_profit:.4f}"
                # comments.iloc[i] will be updated by trailing SL/TP logic or remain as previous HOLD comment


        # D. If not in a trade and no BUY signal, keep as HOLD
        else:
            signals.iloc[i] = 'HOLD'
            comments.iloc[i] = "HOLD: No active signal or in neutral zone."


        # E. Propagate SL/TP and Entry Price for HOLD periods within a trade
        # if in_trade and signals.iloc[i] == 'HOLD':
        if in_trade or signals.iloc[i] == 'SELL':
             entry_prices.iloc[i] = entry_price
             stop_losses.iloc[i] = stop_loss
             take_profits.iloc[i] = take_profit
            #  ep_for_dynamic_sl_tps.iloc[i] = ep_for_dynamic_sl_tp


    # --- Finalize DataFrame ---

    df['signal'] = signals
    df['Comments'] = comments
    df['Entry_Price'] = entry_prices
    df['Stop_Loss'] = stop_losses
    df['Take_Profit'] = take_profits
    # df['ep_for_dynamic_sl_tp'] = ep_for_dynamic_sl_tps

    # Add a 'P/L' column
    df['P/L'] = pd.Series(dtype=float) # Initialize with explicit float dtype

    df['P/L'] = float(0)

    # Calculate P/L for SELL signals
    sell_signals_index = df[df['signal'] == 'SELL'].index
    # Cast the result to float to ensure compatible dtype
    df.loc[sell_signals_index, 'P/L'] = (df.loc[sell_signals_index, 'Close'] - df.loc[sell_signals_index, 'Entry_Price']).astype(float)

    # Display the updated DataFrame
    # display(df.to_string())

    return df

# # Example usage (assuming you have a DataFrame named 'ohlcv_df' with 'Close' column)
# # latest_signal = strategy4(ohlcv_df)
# # display(latest_signal)

# # **************************************** ADVANCED MACD FINAL WITH COMMENTS *********************************************************

# import pandas as pd
# # import talib

# def strategy5(df, fast_period=12, slow_period=26, signal_period=9, ema_period=200, bounce_period=10, low_bounce_percent=0.005):
#     """
#     Generates trading signals based on the MACD strategy with SL/TP exit and includes
#     a 'Comments' column explaining the reason for each signal.

#     Args:
#         df: pandas DataFrame with OHLCV data. Must contain 'Close', 'Low', and 'High' columns.
#         # ... (other arguments are the same)

#     Returns:
#         pandas DataFrame: The input DataFrame with indicators, entry/exit parameters,
#                           final 'signal' column, and 'Comments'.
#     """

#     # s3 = "Strategy3 - MACD"

#     # display(f"\n**** Running {s5} ****")

#     # --- 1. Calculate Indicators ---
#     macd, signal_line, histogram = talib.MACD(
#                                                 df['Close'], fastperiod=fast_period,
#                                                 slowperiod=slow_period, signalperiod=signal_period
#                                              )
#     ema_200 = df['Close'].ewm(span=ema_period, adjust=False).mean()
#     recent_low = df['Low'].rolling(window=bounce_period).min().shift(1)

#     # Define Bounce Condition
#     bounce_condition = (df['Close'] > recent_low * (1 + low_bounce_percent))

#     # --- 2. Define BUY Conditions ---
#     crossover_up = (macd.shift(1) < signal_line.shift(1)) & (macd > signal_line)
#     below_zero = (macd < 0) & (signal_line < 0)
#     above_ema_200 = (df['Close'] > ema_200)
#     bounced_from_support = bounce_condition

#     buy_condition = crossover_up & below_zero & above_ema_200 & bounced_from_support

#     # --- 3. Initialize Signals, Comments, and Track Trade State ---

#     df['Temp_Buy_Signal'] = buy_condition.apply(lambda x: 'BUY' if x else 'HOLD')
#     df['Entry_Price'] = df['Close'].where(df['Temp_Buy_Signal'] == 'BUY')
#     df['SL_Ref'] = recent_low.where(df['Temp_Buy_Signal'] == 'BUY')

#     # Initialize dynamic columns for tracking the open position's parameters
#     df['Current_Entry_Price'] = pd.NA
#     df['Current_Stop_Loss'] = pd.NA
#     df['Current_Take_Profit'] = pd.NA

#     # --- 4. Iterative SL/TP Tracking and Comments Generation ---

#     # Trade State Variables
#     in_trade = False
#     entry_price = 0
#     stop_loss = 0
#     take_profit = 0

#     signals = pd.Series('HOLD', index=df.index, dtype=object)
#     comments = pd.Series('', index=df.index, dtype=object)

#     SL_BUFFER_PERCENT = 0.001
#     PROFIT_RATIO = 4.0

#     for i in range(1, len(df)):
#         current_index = df.index[i]

#         # A. Check for NEW BUY signal
#         temp_buy_signal_val = df.loc[current_index, 'Temp_Buy_Signal']
#         # Handle potential Series return from .loc when slicing
#         if isinstance(temp_buy_signal_val, pd.Series):
#             temp_buy_signal_val = temp_buy_signal_val.iloc[0]

#         if not in_trade and temp_buy_signal_val == 'BUY':
#             in_trade = True
#             entry_price = df.loc[current_index, 'Entry_Price']
#             # Handle potential Series return from .loc when slicing
#             if isinstance(entry_price, pd.Series):
#                 entry_price = entry_price.iloc[0]


#             # Calculate SL/TP at entry
#             sl_ref = df.loc[current_index, 'SL_Ref']
#             # Handle potential Series return from .loc when slicing
#             if isinstance(sl_ref, pd.Series):
#                 sl_ref = sl_ref.iloc[0]

#             stop_loss = sl_ref * (1 - SL_BUFFER_PERCENT)
#             risk = entry_price - stop_loss
#             take_profit = entry_price + (risk * PROFIT_RATIO)

#             signals.iloc[i] = 'BUY'
#             comments.iloc[i] = (f"ENTRY: All BUY conditions met. (MACD Cross < 0, Price > EMA{ema_period}, Bounced from Support). SL: {stop_loss:.4f}, TP: {take_profit:.4f}")

#         # B. Check for EXIT (SL or TP) if in a trade
#         elif in_trade:
#             # SL Trigger: Low price hit or passed the Stop Loss
#             sl_hit = df.loc[current_index, 'Low'] <= stop_loss
#             # Handle potential Series return from .loc when slicing
#             if isinstance(sl_hit, pd.Series):
#                 sl_hit = sl_hit.iloc[0]

#             # TP Trigger: High price hit or passed the Take Profit
#             tp_hit = df.loc[current_index, 'High'] >= take_profit
#             # Handle potential Series return from .loc when slicing
#             if isinstance(tp_hit, pd.Series):
#                 tp_hit = tp_hit.iloc[0]


#             if sl_hit and tp_hit:
#                 # Ambiguous case: If both are hit in the same bar, assume TP hit for aggressive strategy
#                 signals.iloc[i] = 'SELL'
#                 in_trade = False
#                 comments.iloc[i] = f"EXIT: Take Profit (TP) hit. Price hit {take_profit:.4f} in same candle as SL. Profit Ratio: {PROFIT_RATIO}."

#             elif sl_hit:
#                 signals.iloc[i] = 'SELL'
#                 in_trade = False
#                 comments.iloc[i] = f"EXIT: Stop Loss (SL) hit. Low reached {stop_loss:.4f}."

#             elif tp_hit:
#                 signals.iloc[i] = 'SELL'
#                 in_trade = False
#                 comments.iloc[i] = f"EXIT: Take Profit (TP) hit. High reached {take_profit:.4f}. Profit Ratio: {PROFIT_RATIO}."

#             # C. If no exit, keep the signal as 'HOLD'
#             else:
#                 signals.iloc[i] = 'HOLD'
#                 comments.iloc[i] = f"HOLD: Position open. SL: {stop_loss:.4f}, TP: {take_profit:.4f}"

#         # D. Propagate SL/TP and Entry Price for HOLD periods within a trade
#         if in_trade or signals.iloc[i] == 'SELL':
#             df.loc[current_index, 'Current_Entry_Price'] = entry_price
#             df.loc[current_index, 'Current_Stop_Loss'] = stop_loss
#             df.loc[current_index, 'Current_Take_Profit'] = take_profit

#     # --- 5. Finalize DataFrame ---

#     # Apply the dynamic signal and comments
#     df['signal'] = signals
#     df['Comments'] = comments # The new comments column

#     # Add indicators and map the current parameters back
#     df['MACD'] = macd
#     df['Signal_Line'] = signal_line
#     df['Histogram'] = histogram
#     df['EMA_200'] = ema_200
#     df['Recent_Low'] = recent_low

#     df['Stop_Loss'] = df['Current_Stop_Loss']
#     df['Take_Profit'] = df['Current_Take_Profit']
#     df['Entry_Price'] = df['Current_Entry_Price']

#     # Clean up auxiliary columns
#     df = df.drop(columns=['Temp_Buy_Signal', 'SL_Ref', 'Current_Entry_Price', 'Current_Stop_Loss', 'Current_Take_Profit'], errors='ignore')

#     return df

# **************************************** ADVANCED MACD FINAL WITH COMMENTS *********************************************************

import pandas as pd
import talib

def strategy5(df, fast_period=12, slow_period=26, signal_period=9, ema_period=200, bounce_period=10, low_bounce_percent=0.005, atr_period=50, atr_multiplier=3, profit_ratio=4.0):
    """
    Generates trading signals based on the MACD strategy with SL/TP exit and includes
    a 'Comments' column explaining the reason for each signal. This version includes
    trailing Stop Loss and Take Profit based on ATR.

    Args:
        df: pandas DataFrame with OHLCV data. Must contain 'Close', 'Low', and 'High' columns.
        fast_period: The period for the fast MACD EMA (default = 12).
        slow_period: The period for the slow MACD EMA (default = 26).
        signal_period: The period for the MACD signal line EMA (default = 9).
        ema_period: The period for the long-term EMA for trend filtering (default = 200).
        bounce_period: The lookback period for finding a recent low for stop loss reference (default = 10).
        low_bounce_percent: The percentage increase from the recent low for the bounce condition (default = 0.005).
        atr_period: The period for the ATR calculation (default = 14).
        atr_multiplier: The multiplier for the ATR to calculate the stop loss distance (default = 2).
        profit_ratio: The risk-reward ratio for calculating the initial take profit (default = 4.0).


    Returns:
        pandas DataFrame: The input DataFrame with indicators, entry/exit parameters,
                          final 'signal' column, and 'Comments'.
    """

    display(f"\n**** Running {s5} - NEW - TRAILING SL AND TP USING ATR ****")
    # display(f"\n**** Running MACD NEW2 - Trailing SL and TP using ATR ****")

    # --- 1. Calculate Indicators ---
    macd, signal_line, histogram = talib.MACD(
                                                df['Close'], fastperiod=fast_period,
                                                slowperiod=slow_period, signalperiod=signal_period
                                             )
    ema_200 = df['Close'].ewm(span=ema_period, adjust=False).mean()
    recent_low = df['Low'].rolling(window=bounce_period).min().shift(1)
    df['ATR'] = talib.ATR(df['High'], df['Low'], df['Close'], timeperiod=atr_period)


    # Define Bounce Condition
    bounce_condition = (df['Close'] > recent_low * (1 + low_bounce_percent))

    # --- 2. Define BUY Conditions ---
    crossover_up = (macd.shift(1) < signal_line.shift(1)) & (macd > signal_line)
    below_zero = (macd < 0) & (signal_line < 0)
    above_ema_200 = (df['Close'] > ema_200)
    bounced_from_support = bounce_condition

    buy_condition = crossover_up & below_zero & above_ema_200 & bounced_from_support

    # --- 3. Initialize Signals, Comments, and Track Trade State ---

    df['Temp_Buy_Signal'] = buy_condition.apply(lambda x: 'BUY' if x else 'HOLD')
    df['Entry_Price'] = df['Close'].where(df['Temp_Buy_Signal'] == 'BUY')
    df['SL_Ref'] = recent_low.where(df['Temp_Buy_Signal'] == 'BUY')

    # Initialize dynamic columns for tracking the open position's parameters
    df['Current_Entry_Price'] = pd.NA
    df['Current_Stop_Loss'] = pd.NA
    df['Current_Take_Profit'] = pd.NA

    # --- 4. Iterative SL/TP Tracking and Comments Generation ---

    # Trade State Variables
    in_trade = False
    entry_price = 0
    stop_loss = 0
    take_profit = 0

    signals = pd.Series('HOLD', index=df.index, dtype=object)
    comments = pd.Series('', index=df.index, dtype=object)

    # SL_BUFFER_PERCENT = 0.001


    for i in range(1, len(df)):
        current_index = df.index[i]

        # A. Check for NEW BUY signal
        temp_buy_signal_val = df.loc[current_index, 'Temp_Buy_Signal']
        # Handle potential Series return from .loc when slicing
        if isinstance(temp_buy_signal_val, pd.Series):
            temp_buy_signal_val = temp_buy_signal_val.iloc[0]

        if not in_trade and temp_buy_signal_val == 'BUY':
            in_trade = True
            entry_price = df.loc[current_index, 'Entry_Price']
            # Handle potential Series return from .loc when slicing
            if isinstance(entry_price, pd.Series):
                entry_price = entry_price.iloc[0]

            # Calculate initial SL based on ATR below entry price
            current_atr = df['ATR'].iloc[i]
            stop_loss = entry_price - (current_atr * atr_multiplier)

            # Calculate initial TP based on risk-reward ratio
            risk = entry_price - stop_loss
            take_profit = entry_price + (risk * profit_ratio)

            signals.iloc[i] = 'BUY'
            comments.iloc[i] = (f"ENTRY: All BUY conditions met. (MACD Cross < 0, Price > EMA{ema_period}, Bounced from Support). Initial SL: {stop_loss:.4f}, Initial TP: {take_profit:.4f}")

        # B. Check for EXIT (SL or TP or original SELL condition) if in a trade
        elif in_trade:
            # Trailing Stop Loss and Take Profit Update using ATR
            current_atr = df['ATR'].iloc[i]
            trailing_stop_loss = entry_price - (current_atr * atr_multiplier)
            stop_loss = max(stop_loss, trailing_stop_loss)

            # Recalculate Take Profit based on the updated Stop Loss
            risk = entry_price - stop_loss
            take_profit = entry_price + (risk * profit_ratio)

            comments.iloc[i] = f"HOLD: Trailing SL/TP updated. New SL: {stop_loss:.4f}, New TP: {take_profit:.4f}"


            # SL Trigger: Low price hit or passed the Stop Loss
            sl_hit = df.loc[current_index, 'Low'] <= stop_loss
            # Handle potential Series return from .loc when slicing
            if isinstance(sl_hit, pd.Series):
                sl_hit = sl_hit.iloc[0]

            # TP Trigger: High price hit or passed the Take Profit
            tp_hit = df.loc[current_index, 'High'] >= take_profit
            # Handle potential Series return from .loc when slicing
            if isinstance(tp_hit, pd.Series):
                tp_hit = tp_hit.iloc[0]

            # Original SELL condition: MACD crosses below Signal, Price below EMA200, Price breaks below Recent Low
            sell_condition_macd_cross = (macd.iloc[i-1] > signal_line.iloc[i-1]) and (macd.iloc[i] < signal_line.iloc[i])
            sell_condition_below_ema = df.loc[current_index, 'Close'] < ema_200.loc[current_index]
            # sell_condition_break_support = df.loc[current_index, 'Low'] < recent_low.loc[current_index]


            if sl_hit and tp_hit:
                # Ambiguous case: If both are hit in the same bar, assume TP hit for aggressive strategy
                signals.iloc[i] = 'SELL'
                in_trade = False
                comments.iloc[i] = f"EXIT: Take Profit (TP) hit. Price hit {take_profit:.4f} in same candle as SL. Profit Ratio: {profit_ratio}."

            elif sl_hit:
                signals.iloc[i] = 'SELL'
                in_trade = False
                comments.iloc[i] = f"EXIT: Stop Loss (SL) hit. Low reached {stop_loss:.4f}."

            elif tp_hit:
                signals.iloc[i] = 'SELL'
                in_trade = False
                comments.iloc[i] = f"EXIT: Take Profit (TP) hit. High reached {take_profit:.4f}. Profit Ratio: {profit_ratio}."

            elif sell_condition_macd_cross:
                 signals.iloc[i] = 'SELL'
                 in_trade = False
                 comments.iloc[i] = (f"EXIT: Original SELL signal triggered: MACD cross below Signal.")

            elif sell_condition_below_ema:
                 signals.iloc[i] = 'SELL'
                 in_trade = False
                 comments.iloc[i] = (f"EXIT: Original SELL signal triggered: Price below EMA{ema_period}.")

            # elif sell_condition_break_support:
            #      signals.iloc[i] = 'SELL'
            #      in_trade = False
            #      comments.iloc[i] = (f"EXIT: Original SELL signal triggered: Price broke below recent low of ({recent_low.loc[current_index]:.4f}).")

            # C. If no exit, keep the signal as 'HOLD'
            else:
                signals.iloc[i] = 'HOLD'
                # comments.iloc[i] will be updated by trailing SL/TP logic or remain as previous HOLD comment

        # D. If not in a trade and no BUY signal, keep as HOLD
        else:
            signals.iloc[i] = 'HOLD'
            comments.iloc[i] = "HOLD: No active signal or in neutral zone."


        # E. Propagate SL/TP and Entry Price for HOLD periods within a trade
        if in_trade or signals.iloc[i] == 'SELL':
            df.loc[current_index, 'Current_Entry_Price'] = entry_price
            df.loc[current_index, 'Current_Stop_Loss'] = stop_loss
            df.loc[current_index, 'Current_Take_Profit'] = take_profit

    # --- 5. Finalize DataFrame ---

    # Apply the dynamic signal and comments
    df['signal'] = signals
    df['Comments'] = comments # The new comments column

    # Add indicators and map the current parameters back
    df['MACD'] = macd
    df['Signal_Line'] = signal_line
    df['Histogram'] = histogram
    df['EMA_200'] = ema_200
    df['Recent_Low'] = recent_low

    df['Stop_Loss'] = df['Current_Stop_Loss']
    df['Take_Profit'] = df['Current_Take_Profit']
    df['Entry_Price'] = df['Current_Entry_Price']


    # Add a 'P/L' column
    df['P/L'] = pd.Series(dtype=float) # Initialize with explicit float dtype

    df['P/L'] = float(0)

    # Calculate P/L for SELL signals
    sell_signals_index = df[df['signal'] == 'SELL'].index
    # Cast the result to float to ensure compatible dtype
    df.loc[sell_signals_index, 'P/L'] = (df.loc[sell_signals_index, 'Close'] - df.loc[sell_signals_index, 'Entry_Price']).astype(float)

    # Clean up auxiliary columns
    df = df.drop(columns=['Temp_Buy_Signal', 'SL_Ref', 'Current_Entry_Price', 'Current_Stop_Loss', 'Current_Take_Profit'], errors='ignore')

    return df

"""# LOGIN"""

login_fyers()

"""# PAPER - OP"""

import datetime
import pandas as pd
import pytz
from time import sleep

def get_option_chain(NIFTY_OPTION_SYMBOL, expiry_date, strike_count):

    expiry_date = datetime.datetime.strptime(expiry_date, '%Y-%m-%d %H:%M:%S')
    
    india_tz = pytz.timezone("Asia/Kolkata")
    expiry_date = india_tz.localize(expiry_date)

    display(expiry_date)
    display(expiry_date.timestamp())

    expiry_date = int(expiry_date.timestamp())

    # display(f"\nFetching option chain for {NIFTY_OPTION_SYMBOL} with expiry {expiry_date} and strike count {strike_count}...")

    data = {
        "symbol" : NIFTY_OPTION_SYMBOL,
        "strikecount" : strike_count,
        "timestamp" : expiry_date
    }

    fyers = create_fyers_model()
    response = fyers.optionchain(data=data);

    # display(f"\nOption Chain Response:")
    # display(response)

    while response.get('s') != 'ok':
        display('\n NOK Response Received while fetching Option Chain !!!!!')
        display(f'\nResponse: \n{response}')
        display(f'\n Retrying...')
        sleep(2)
        response = fyers.optionchain(data=data);

    df_options_chain = pd.DataFrame(response['data']['optionsChain'])

    display(f"\ndf_options_chain:")
    display(df_options_chain)

    return df_options_chain

def split_options_chain(df_options_chain):

    df_strike = df_options_chain[df_options_chain['strike_price'] == -1]
    df_puts = df_options_chain[df_options_chain['option_type'] == 'PE']
    df_puts.reset_index(drop=True, inplace=True)
    df_calls = df_options_chain[df_options_chain['option_type'] == 'CE']
    df_calls.reset_index(drop=True, inplace=True)

    # display("df_strike:")
    # display(df_strike)

    # display("\ndf_calls:")
    # display(df_calls)

    # display("\ndf_puts:")
    # display(df_puts)

    return df_strike, df_calls, df_puts

def get_current_strike_atm_price(df_strike, df_options_chain):

    strike = df_strike.iloc[0]['ltp']
    display(f'\nCurrent Strike Price: {strike}')

    all_strike_prices = df_options_chain['strike_price'].dropna().unique()
    # Filter out -1 which represents the index itself
    filtered_strike_prices = all_strike_prices[all_strike_prices != -1]

    # Find the strike price closest to the current 'strike' (underlying price)
    atm = min(filtered_strike_prices, key=lambda x: abs(x - strike))

    display(f'\nCalculated ATM Strike Price: {atm}')

    return strike, atm

def catergorise_options(df_strike, df_calls, df_puts, atm):

    # Add 'category' column to df_strike
    df_strike = df_strike.copy()
    df_strike['category'] = 'STRIKE'

    # Add 'category' column to df_calls
    df_calls = df_calls.copy()
    df_calls['category'] = None
    df_calls.loc[df_calls['strike_price'] == atm, 'category'] = 'ATM'
    df_calls.loc[df_calls['strike_price'] > atm, 'category'] = 'OTM'
    df_calls.loc[df_calls['strike_price'] < atm, 'category'] = 'ITM'

    # Add 'category' column to df_puts
    df_puts = df_puts.copy()
    df_puts['category'] = None
    df_puts.loc[df_puts['strike_price'] == atm, 'category'] = 'ATM'
    df_puts.loc[df_puts['strike_price'] < atm, 'category'] = 'OTM'
    df_puts.loc[df_puts['strike_price'] > atm, 'category'] = 'ITM'

    # display("df_strike with category:")
    # display(df_strike)

    # display("\ndf_calls with category:")
    # display(df_calls)

    # display("\ndf_puts with category:")
    # display(df_puts)

    df_calls['sequence'] = None
    df_puts['sequence'] = None

    # Assign sequence for df_calls
    df_calls.loc[df_calls['category'] == 'ATM', 'sequence'] = 0

    # For OTM calls (strike_price > atm), sort ascending by strike_price (closest to ATM first)
    otm_calls = df_calls[df_calls['category'] == 'OTM'].sort_values(by='strike_price', ascending=True)
    df_calls.loc[otm_calls.index, 'sequence'] = range(1, len(otm_calls) + 1)

    # For ITM calls (strike_price < atm), sort descending by strike_price (closest to ATM first)
    itm_calls = df_calls[df_calls['category'] == 'ITM'].sort_values(by='strike_price', ascending=False)
    df_calls.loc[itm_calls.index, 'sequence'] = range(1, len(itm_calls) + 1)

    # Assign sequence for df_puts
    df_puts.loc[df_puts['category'] == 'ATM', 'sequence'] = 0

    # For OTM puts (strike_price < atm), sort descending by strike_price (closest to ATM first)
    otm_puts = df_puts[df_puts['category'] == 'OTM'].sort_values(by='strike_price', ascending=False)
    df_puts.loc[otm_puts.index, 'sequence'] = range(1, len(otm_puts) + 1)

    # For ITM puts (strike_price > atm), sort ascending by strike_price (closest to ATM first)
    itm_puts = df_puts[df_puts['category'] == 'ITM'].sort_values(by='strike_price', ascending=True)
    df_puts.loc[itm_puts.index, 'sequence'] = range(1, len(itm_puts) + 1)


    df_calls.loc[(df_calls['category'] == 'OTM') | (df_calls['category'] == 'ITM'), 'category'] = df_calls['category'] + df_calls['sequence'].astype(str)
    df_puts.loc[(df_puts['category'] == 'OTM') | (df_puts['category'] == 'ITM'), 'category'] = df_puts['category'] + df_puts['sequence'].astype(str)

    df_calls = df_calls.drop(columns=['sequence'])
    df_puts = df_puts.drop(columns=['sequence'])

    # display("df_strike with category:")
    # display(df_strike)

    # display("df_calls with updated category:")
    # display(df_calls)

    # display("\ndf_puts with updated category:")
    # display(df_puts)

    display("\ndf_strike:")
    display(df_strike)

    display("\ndf_calls:")
    display(df_calls)

    display("\ndf_puts:")
    display(df_puts)

    return df_strike, df_calls, df_puts

import pandas as pd
import requests
import io
import json

def get_lot_size(symbol):

    # --- 1. Configuration ---
    # URL for NSE F&O Symbol Master (contains NIFTY, BANKNIFTY, and Stock F&O lot sizes)
    CSV_URL = "https://public.fyers.in/sym_details/NSE_FO.csv"
    TARGET_SYMBOL = symbol # Example F&O symbol
    # SYMBOL_TO_LOOKUP = f"NSE:{symbol}" # Fyers-formatted symbol
    SYMBOL_TO_LOOKUP = symbol # Fyers-formatted symbol

    # --- 2. Download and Load the CSV Data ---
    # display(f"Downloading Symbol Master file from: {CSV_URL}...")
    try:
        response = requests.get(CSV_URL)
        response.raise_for_status() # Check for request errors

        # Use StringIO to treat the string content as a file
        csv_data = io.StringIO(response.content.decode('utf-8'))

        # Load into a Pandas DataFrame (assuming header=None as the file has no official header)
        # The Lot Size is typically in Column Index 4 (the 5th column)
        # The Fyers Symbol Format is typically in Column Index 10 (the 11th column)
        df = pd.read_csv(csv_data, header=None)

        # --- 3. Create a Lookup Dictionary ---
        # We map the Fyers Symbol (Column 9) to the Lot Size (Column 3)
        # Note: Column indices might shift if the Fyers format changes.
        df_filtered = df[[9, 3]].dropna()
        lot_size_map = pd.Series(df_filtered[3].values, index=df_filtered[9]).to_dict()

        # display("Successfully loaded symbol data into a lookup map.")

        # --- 4. Fetch the Lot Size for the Target Symbol ---
        lot_size = lot_size_map.get(SYMBOL_TO_LOOKUP)

        if lot_size is not None:
            # display(f"\n✅ Lot Size for {SYMBOL_TO_LOOKUP}: **{int(lot_size)}**")
            return int(lot_size)
        else:
            display(f"\n❌ Symbol {SYMBOL_TO_LOOKUP} not found in the master file.")

    except requests.exceptions.RequestException as e:
        display(f"\n❌ Error downloading the file: {e}")
    except Exception as e:
        display(f"\n❌ An error occurred during processing: {e}")

def prepare_order(df_strike, df_calls, df_puts):

    df_order = pd.concat([df_puts[df_puts['category']=='OTM9'], df_puts[df_puts['category']=='ATM'], df_calls[df_calls['category']=='ATM'], df_calls[df_calls['category']=='OTM9']], ignore_index=True)

    df_order.sort_values(by=['strike_price','option_type'], ascending=[True, False], inplace=True)

    df_order['lot_size'] = df_order['symbol'].apply(get_lot_size)

    df_order['action'] = ['BUY', 'SELL', 'SELL', 'BUY']

    # df_order['cost'] = np.where(df_order['action'] == 'BUY', df_order['lot_size'] * df_order['ltp'], df_order['lot_size'] * df_order['ltp'])
    df_order['cost'] = df_order['lot_size'] * df_order['ltp']

    df_order = df_order.reset_index(drop=True)

    display("\ndf_order:")
    display(df_order)

    return df_order

import requests
import json

# Replace these with your actual details
APP_ID = open(client_id_file, "r").read()
APP_TYPE = '100'
ACCESS_TOKEN = open(access_token_file, "r").read()
AUTH_HEADER = f"{APP_ID}-{APP_TYPE}:{ACCESS_TOKEN}"

# 1. Funds API details
FUNDS_URL = 'https://api-t1.fyers.in/api/v3/funds'

headers = {
    'Authorization': AUTH_HEADER,
    'Content-Type': 'application/json'
}

def fetch_live_funds(avl_bal):
    """
    Fetches live funds
    """

    # --- Fetch Live Funds ---
    funds_response = requests.get(FUNDS_URL, headers=headers)
    funds_response.raise_for_status()
    funds_data_response = funds_response.json()

    # display(funds_data_response)

    if funds_data_response.get("s") != "ok":
        display(f"Error fetching funds: {funds_data_response.get('message')}")
        # return False, None

    # Find the 'availableBalance' within the 'fund_limit' list in the response
    available_balance = 0.0
    for fund_item in funds_data_response.get('data', {}).get('fund_limit', []):
        if fund_item.get('title') == 'Available Balance': # Corrected to use 'title' which holds 'Available Balance'
            available_balance = float(fund_item.get('equityAmount', 0)) # Corrected to use 'equityAmount'
            break

    available_balance = avl_bal # Delete for LIVE Trade

    # display(f"\n✅ Successfully fetched live account balance.")


    return available_balance

import requests
import json

# Replace these with your actual details
APP_ID = open(client_id_file, "r").read()
APP_TYPE = '100'
ACCESS_TOKEN = open(access_token_file, "r").read()
AUTH_HEADER = f"{APP_ID}-{APP_TYPE}:{ACCESS_TOKEN}"

# 1. Margin API details
MARGIN_URL = 'https://api-t1.fyers.in/api/v3/multiorder/margin'

headers = {
    'Authorization': AUTH_HEADER,
    'Content-Type': 'application/json'
}

def fetch_margin(df_order):
    """
    Fetches margin for provided trade combination
    """

    # --- Calculate Margin for New Trade ---

    payload_data = []
    for index, row in df_order.iterrows():
        side_value = 1 if row['action'] == 'BUY' else -1
        payload_data.append({
            "symbol": row['symbol'],
            "qty": int(row['lot_size']),
            "side": side_value,
            "type": 2, # MARKET ORDER
            "productType": "MARGIN",
            "limitPrice": 0.0,
            "stopLoss": 0.0,
            "stopPrice": 0.0,
            "takeProfit": 0.0
        })

    payload = {"data": payload_data}
    # payload

    margin_payload = payload # Use the payload generated in the previous step

    margin_response = requests.post(MARGIN_URL, headers=headers, json=margin_payload)
    margin_response.raise_for_status()
    margin_data_response = margin_response.json()

    if margin_data_response.get("s") != "ok":
        display(f"Error calculating margin: {margin_data_response.get('message')}")
        # return False, None

    # Get required margin from the *nested* data key
    # Based on successful response of previous cell, margin_total is directly under 'data' key
    required_margin = margin_data_response.get('data', {}).get('margin_total', 0)


    return required_margin

import requests
import json

def fetch_funds_margin_and_compare(df_order, avl_bal):
    """
    Fetches live funds, then compares against the margin needed for the potential trade.
    """
    try:
        # --- Fetch Live Funds ---

        available_balance = fetch_live_funds(avl_bal)
        display(f"Live Available Balance: ₹{available_balance:,.2f}\n")

        # --- Calculate Margin for New Trade ---

        required_margin = fetch_margin(df_order)
        display(f"Margin required for new order: ₹{required_margin:,.2f}")

        # --- Compare ---
        if available_balance >= required_margin:
            display("\n✅ **GO signal**: You have sufficient funds to place this trade.")
            return True, required_margin
        else:
            deficit = required_margin - available_balance
            display(f"\n❌ **NO-GO signal**: Insufficient funds.")
            display(f"You need an additional ₹{deficit:,.2f}.")
            return False, None

    except requests.exceptions.RequestException as e:
        display(f"An API request error occurred: {e}")
        return False, None
    except Exception as e:
        display(f"An unexpected error occurred: {e}")
        return False, None

import datetime

def get_next_nth_tuesday(n, start_datetime=None):
    """
    Calculates the datetime of the next nth Tuesday at 10:00:00 from a given start datetime.

    Args:
        n (int): Which upcoming Tuesday to find (e.g., 1 for the first, 2 for the second).
                 Must be a positive integer.
        start_datetime (datetime.datetime, optional): The datetime from which to start counting.
                                            Defaults to today's date at current time if None.

    Returns:
        datetime.datetime: The datetime of the next nth Tuesday at 10:00:00.

    Raises:
        ValueError: If n is not a positive integer.
    """
    if not isinstance(n, int) or n <= 0:
        raise ValueError("n must be a positive integer")

    if start_datetime is None:
        start_datetime = get_now_ist() # Changed to use get_now_ist()

    # 0 = Monday, 1 = Tuesday, ..., 6 = Sunday
    # We want to find the next Tuesday, which is day 1.
    # Calculate days until next Tuesday
    days_until_tuesday = (1 - start_datetime.weekday() + 7) % 7

    # # If today is Tuesday or Monday or Sunday or Saturdat, and we are looking for the 1st upcoming Tuesday,
    # # we should move to the next week's Tuesday. If n=1 and today is Tuesday,
    # # and days_until_tuesday is 0 or 1 or 2 or 3, we treat it as 7 days from now.
    # if days_until_tuesday == 0 or days_until_tuesday == 1 or days_until_tuesday == 2 or days_until_tuesday == 3:
    #     days_until_tuesday = days_until_tuesday + 7

    # Add (n-1) full weeks to get to the nth Tuesday
    target_date = start_datetime + datetime.timedelta(days=days_until_tuesday + (n - 1) * 7)

    # Set the time to 10:00:00
    # target_datetime = target_date.replace(hour=10, minute=0, second=0, microsecond=0)

    # Set the time to expiry time of 15:30:00
    target_datetime = target_date.replace(hour=15, minute=30, second=0, microsecond=0)

    return target_datetime

import datetime as dt
import pandas as pd
import pytz
from time import sleep

def is_expiry_date_valid(NIFTY_OPTION_SYMBOL, expiry_date_str):
    """
    Checks if the given expiry date is valid for the NIFTY_OPTION_SYMBOL
    by querying the Fyers API's option chain.

    Args:
        NIFTY_OPTION_SYMBOL (str): The Fyers symbol for the Nifty index options (e.g., "NSE:NIFTY50-INDEX").
        expiry_date_str (str): The expiry date string in 'YYYY-MM-DD HH:MM:SS' format.

    Returns:
        bool: True if the expiry date is found in the option chain, False otherwise.
    """
    display(f"\nChecking validity of expiry date: {expiry_date_str} for symbol: {NIFTY_OPTION_SYMBOL}")

    try:
        # Convert expiry_date_str to a timezone-aware datetime object
        expiry_datetime_naive = dt.datetime.strptime(expiry_date_str, '%Y-%m-%d %H:%M:%S')
        india_tz = pytz.timezone("Asia/Kolkata")
        expiry_datetime_aware = india_tz.localize(expiry_datetime_naive)

        # Convert to Unix timestamp (integer seconds since epoch)
        expiry_timestamp = int(expiry_datetime_aware.timestamp())

        data = {
            "symbol": NIFTY_OPTION_SYMBOL,
            "strikecount": 10,  # Request some strikes to get a non-empty chain if valid
            "timestamp": expiry_timestamp
        }

        fyers = create_fyers_model()
        response = fyers.optionchain(data=data)

        while response.get('s') != 'ok' and response.get('message') == 'request limit reached':
            display('\n NOK Response Received while fetching Expiry Dates !!!!!')
            display(f'\nResponse: \n{response}')
            display(f'\n Retrying...')
            sleep(2)
            response = fyers.optionchain(data=data)

        # display(f"  Response: {response}")

        # Check if the API call was successful and returned data
        if response.get('s') == 'ok' and 'optionsChain' in response['data'] and response['data']['optionsChain']:
            df_options_chain = pd.DataFrame(response['data']['optionsChain'])
            # The Fyers API usually returns options for the requested date if it's valid.
            # We can check if any entry in the DataFrame matches the expiry_date_str, or simply if the chain is not empty.
            # A more robust check might involve comparing the `expiry` field in the dataframe if available.
            # For now, if optionsChain is not empty, we assume the date is valid for the symbol.
            display(f"\n  Expiry date {expiry_date_str} is valid.")
            return True
        else:
            display(f"\n  Expiry date {expiry_date_str} is NOT valid or no option chain found for this date. Response: {response.get('message', 'No specific error message')}")
            return False

    except Exception as e:
        display(f"\n  An error occurred while checking expiry date validity: {e}")
        return False

# next_tuesday = get_next_nth_tuesday(1)
# display(f"Next 1st Tuesday: {next_tuesday}")

# expiry_date = next_tuesday.strftime('%Y-%m-%d %H:%M:%S')
# display(expiry_date)

# next_tuesday = get_next_nth_tuesday(2)
# display(f"Next 2nd Tuesday: {next_tuesday}")

# expiry_date = next_tuesday.strftime('%Y-%m-%d %H:%M:%S')
# display(expiry_date)

# '''
# If today is Tuesday
# '''

# import datetime
# import pytz

# start_dt_str = "2026-02-03 10:00:00"
# naive_dt = datetime.datetime.strptime(start_dt_str, '%Y-%m-%d %H:%M:%S')
# ist_tz = pytz.timezone('Asia/Kolkata')
# start_dt_ist = ist_tz.localize(naive_dt)

# next_tuesday = get_next_nth_tuesday(2, start_dt_ist)
# display(f"Next 1st Tuesday: {next_tuesday}")

# expiry_date = next_tuesday.strftime('%Y-%m-%d %H:%M:%S')
# display(expiry_date)

# # TEST MAIN

# NIFTY_OPTION_SYMBOL = "NSE:NIFTY50-INDEX" # Replace with your target option symbol

# expiry_date = "2026-01-06 10:00:00"

# strike_count = 4

# df_options_chain = get_option_chain(NIFTY_OPTION_SYMBOL, expiry_date, strike_count)

# df_strike, df_calls, df_puts = split_options_chain(df_options_chain)

# current_strike_price, current_atm = get_current_strike_atm_price(df_strike, df_options_chain)

# df_strike, df_calls, df_puts = catergorise_options(df_strike, df_calls, df_puts, current_atm)

# df_order = prepare_order(df_strike, df_calls, df_puts)

# # df_order = calculate_cost(df_order)

# # Run the live check
# trade_approved, margin = fetch_funds_margin_and_compare(df_order, 100000)

# if trade_approved:
#     display('TRADE APPROVED')
#     # Place Order API Call goes here
#     pass

from time import sleep

def get_market_status(exchange, market_type, segment):

    # Create Fyers Model
    fyers = create_fyers_model()

    response = fyers.market_status()

    while response.get('s') != 'ok':
        display('\n NOK Response Received while fetching Market Status !!!!!')
        display(f'\nResponse: \n{response}')
        display(f'\n Retrying...')
        sleep(2)
        response = fyers.market_status()

    # display(response)

    market_status = response['marketStatus']

    # Filter for Exchange NSE | NORMAL | EQUITY
    filtered_status = [status for status in market_status if status['exchange'] == exchange and status['market_type'] == market_type and status['segment'] == segment]

    # for status in filtered_status:

    #     display(f"Market Status for Exchange NSE | NORMAL | EQUITY: {status.get('status')}")

    if filtered_status:
        return filtered_status[0].get('status')
        # return 'OPEN'
    else:
        return None

import datetime
from datetime import timedelta
import pytz # Import the pytz library for timezone handling
import time # Import the time module for the sleep function

def wait(INTERVAL, output_file_path):
    # Calculate time until the next INTERVAL
    now_ist = get_now_ist()
    next_run_time = now_ist + datetime.timedelta(minutes=int(INTERVAL)) + datetime.timedelta(seconds=0) # Use the INTERVAL variable
    # Align to the next INTERVAL minute mark if not already aligned
    next_run_time = next_run_time.replace(minute=(next_run_time.minute // int(INTERVAL)) * int(INTERVAL), second=0, microsecond=0)
    if next_run_time <= now_ist: # Handle cases where it's exactly on the INTERVAL min mark
        next_run_time += datetime.timedelta(minutes=int(INTERVAL))

    message = f"\n Next Run @ {next_run_time.strftime('%Y-%m-%d %H:%M:%S')}"
    capture_output(message, output_file_path)
    # send_telegram_message(message)

    time_to_wait = (next_run_time - now_ist).total_seconds()
    if time_to_wait > 0:
        capture_output(f"  Waiting for {time_to_wait:.2f} seconds...", output_file_path)
        capture_output("\n================================================================================================================================================================================================================", output_file_path)
        sleep(time_to_wait)
    else:
        # If time_to_wait is not positive (shouldn't happen with alignment), just wait a short time
        capture_output("  Waiting for a short interval before the next run.", output_file_path)
        sleep(60) # Wait for 1 minute if calculation is off

# Load available balance at the start

import pandas as pd

def load_avl_bal(today_date_str, output_file_path):

    # Define the avl_bal file path
    avl_bal_file_path = current_path + 'paper_available_balance.csv'

    try:
        df_avl_bal = pd.read_csv(avl_bal_file_path)

        if today_date_str not in df_avl_bal['date'].values:

            # Fetch available balance from last row
            avl_bal = df_avl_bal['available_balance'].iloc[-1]

            # capture_output(f"\nAvailable balance loaded: {avl_bal:.2f}", output_file_path)

            # Add the new row for today's date using .loc and the next index. Save to file_path.
            df_avl_bal.loc[len(df_avl_bal)] = [today_date_str, avl_bal, 0, '']
            df_avl_bal.to_csv(avl_bal_file_path, index=False)

        else:
            avl_bal = df_avl_bal.loc[df_avl_bal['date'] == today_date_str, 'available_balance'].values[0]

    except (FileNotFoundError, KeyError, Exception) as e:
        capture_output(f"\nError loading available balance: {e}. Starting with default 100000.", output_file_path)
        avl_bal = 100000.0

    return avl_bal

# # 2. Create the FyersModel instance
# fyers = create_fyers_model()

# # 3. Call the Fyers API quotes method
# data = {"symbols": "NSE:NIFTY50-INDEX"}
# response = fyers.quotes(data)

# if response.get('s') == 'ok':
#   display(response)
#   display(response['d'])
#   for item in response['d']:
#     display(item['n'])
#     display(item['v']['lp'])

import datetime as dt
from datetime import date
import pandas as pd
from time import sleep
import pytz
import json
import math

CHECK_INTERVAL = '5'  # Minutes

def paper_trade(today_trade_execute_ist, today_intraday_buy_cutoff_ist, today_intraday_squareoff_ist, today_market_close_ist):

    # strategies = {
    #     # s1 : strategy1,
    #     # s2 : strategy2,
    #     # s3 : strategy3,
    #     s4 : strategy4,
    #     s5 : strategy5,
    # }

    # Generate tradebook filename with today's date
    today_date_str = dt.date.today().strftime('%Y-%m-%d')

    # Generate output filename with today's date
    output_file_path = f'{current_path}output_{today_date_str}.csv'

    # List to store output messages and timestamps
    output_messages = []

    # tradebook_file_path = f'{current_path}paper_trade_{today_date_str}.csv'
    tradebook_file_path = f'{current_path}paper_trade_book.csv'

    # Define the avl_bal file path
    avl_bal_file_path = current_path + 'paper_available_balance.csv'

    # Read available balance csv as dataframe for runtime updates
    df_avl_bal = pd.read_csv(avl_bal_file_path)

    if get_market_status(10, 'NORMAL', 10) == 'OPEN':  # Get Market Status for NSE | NORMAL | EQUITY
    # if get_market_status(10, 'NORMAL', 10) == get_market_status(10, 'NORMAL', 10):  # Get Market Status for NSE | NORMAL | EQUITY ------> Activate for TEST

        message = "\n******* MARKET OPEN *******"
        capture_output(message, output_file_path)
        # send_telegram_message(message)

        message = f"\n************ PAPER TRADING STARTED @ {get_now_ist().strftime('%Y-%m-%d %H:%M:%S')} ************"
        capture_output(message, output_file_path)
        # send_telegram_message(message)

        # Load available balance at the start
        avl_bal = load_avl_bal(today_date_str, output_file_path)

        message = f"\nAvailable balance loaded: {avl_bal:.2f}"
        capture_output(message, output_file_path)
        # send_telegram_message(message)

        start_capital = avl_bal # Save for calculation of P/L after market close

        capture_output("\n================================================================================================================================================================================================================", output_file_path)

        # Read available balance csv as dataframe for runtime updates
        df_avl_bal = pd.read_csv(avl_bal_file_path)

        # profitable_symbols_with_strategy = read_profitable_symbols_with_strategy()

        # # Define the file path
        # pl_file_path = current_path + 'pl.csv'

        now = get_now_ist()

        # Determine the time to wait till 09:20 HRS
        if now < today_trade_execute_ist:
            # If current time is before market open today, wait until market open today
            time_to_wait = (today_trade_execute_ist - now).total_seconds()
            message = f"Current time is before defined trade execute time. Waiting until {today_trade_execute_ist} IST."
            display(message)
            # send_telegram_message(message)
            sleep(time_to_wait)


        while get_now_ist() < today_intraday_squareoff_ist: # and get_market_status(10, 'NORMAL', 10) == 'OPEN':
        # if get_now_ist() < today_intraday_squareoff_ist: # and get_market_status(10, 'NORMAL', 10) == get_market_status(10, 'NORMAL', 10):   # ------> Activate for TEST

            capture_output(f"\nNOW: {get_now_ist().strftime('%Y-%m-%d %H:%M:%S')}", output_file_path)

            # stocks_in_position, paper_trading_positions = read_paper_trade_positions()

            NIFTY_OPTION_SYMBOL = "NSE:NIFTY50-INDEX" # Replace with your target option symbol

            # expiry_date = "2026-01-13 10:00:00"

            # Check if the day name is "Wednesday"
            if dt.datetime.now().strftime('%A') == "Wednesday" or dt.datetime.now().strftime('%A') == "Thursday" or dt.datetime.now().strftime('%A') == "Friday" or dt.datetime.now().strftime('%A') == "Saturday" or dt.datetime.now().strftime('%A') == "Sunday" or dt.datetime.now().strftime('%A') == "Monday":

                next_tuesday = get_next_nth_tuesday(1)
                display(f"\n  Next 1st Tuesday: {next_tuesday}")

            elif dt.datetime.now().strftime('%A') == "Tuesday":

                next_tuesday = get_next_nth_tuesday(2)
                display(f"\n  Next 2nd Tuesday: {next_tuesday}")

            expiry_date = next_tuesday
            
            while not is_expiry_date_valid(NIFTY_OPTION_SYMBOL, expiry_date.strftime('%Y-%m-%d %H:%M:%S')):
                # display(f"Result: {expiry_date.strftime('%Y-%m-%d %H:%M:%S')} is not a valid expiry date. Trying previous date in 2 seconds...")
                expiry_date = expiry_date - dt.timedelta(days=1)
                display(f"\n  Checking previous date {expiry_date.strftime('%Y-%m-%d %H:%M:%S')} after 1 second...")
                sleep(1)

            expiry_date = expiry_date.strftime('%Y-%m-%d %H:%M:%S')
            display(f'\n Selected Expiry Date: {expiry_date}')

            strike_count = 9

            df_options_chain = get_option_chain(NIFTY_OPTION_SYMBOL, expiry_date, strike_count)

            df_strike, df_calls, df_puts = split_options_chain(df_options_chain)

            current_strike_price, current_atm = get_current_strike_atm_price(df_strike, df_options_chain)

            df_strike, df_calls, df_puts = catergorise_options(df_strike, df_calls, df_puts, current_atm)

            df_order = prepare_order(df_strike, df_calls, df_puts)

            # df_order_cost = calculate_cost(df_order)

            #************** DONOT DELETE ************** REQUIRED FOR LIVE TRADING

            # # Run the live check
            # trade_approved, margin = fetch_funds_and_compare(df_order)

            #**********************************************************************

            # Run the paper check with paper available balance
            trade_approved, margin = fetch_funds_margin_and_compare(df_order, avl_bal)

            if trade_approved:

                display('\n ********* TRADE APPROVED. Executing ENTRY *********')
                # Place Order API Call goes here

                # 1. Create the FyersModel instance
                fyers = create_fyers_model()

                # 2. Call the Fyers API quotes method and fetch current value of INDEX
                data = {"symbols": NIFTY_OPTION_SYMBOL}
                response = fyers.quotes(data)

                if response.get('s') == 'ok':
                    for item in response['d']:
                        # display(f'\n Current Value of {item['n']} is {item['v']['lp']}')
                        display(f'\n Current Value of {item["n"]} is {item["v"]["lp"]}')
                else:
                    display('\n NOK Response Received while Executing Entry !!!!!')
                    display(f'\nResponse: \n{response}')

                df_entry = df_order.copy().reset_index(drop=True)

                df_entry.rename(columns={'cost': 'entry_cost'}, inplace=True)

                display('\n df_entry:')
                display(df_entry)

                display(f'\n Entry Margin: {margin}')

                avl_bal = avl_bal - margin

                # Find the row with today's date and update the available balance
                df_avl_bal['available_balance'] = df_avl_bal['available_balance'].astype(float)
                df_avl_bal.loc[df_avl_bal['date'] == today_date_str, 'available_balance'] = round(avl_bal, 2)
                df_avl_bal.to_csv(avl_bal_file_path, index=False)
                capture_output(f"\n  Updated available balance saved: {avl_bal:.2f}", output_file_path)

                # df_current_pl = pd.DataFrame(columns=['TIME', 'INDEX', 'INDEX_VALUE', 'P/L'])

                # Initialize with specific data types (dtypes)
                # Now pd.concat won't trigger the warning

                df_current_pl = pd.DataFrame({
                    'TIME': pd.Series(dtype='str'), # Explicitly tell Pandas to expect strings
                    'INDEX': pd.Series(dtype='str'),
                    'INDEX_VALUE': pd.Series(dtype='float64'),
                    'P/L': pd.Series(dtype='float64')
                })

                display('\n df_current_pl:')
                # display(df_current_pl)
                display(df_current_pl.to_string())

                wait(CHECK_INTERVAL, output_file_path)

                while get_market_status(10, 'NORMAL', 10) == 'OPEN': # get_now_ist() < today_intraday_squareoff_ist and
                # while get_now_ist() < today_intraday_squareoff_ist and get_market_status(10, 'NORMAL', 10) == get_market_status(10, 'NORMAL', 10): # ------> Activate for TEST

                    capture_output(f"\nNOW: {get_now_ist().strftime('%Y-%m-%d %H:%M:%S')}", output_file_path)

                    # 1. Create the FyersModel instance
                    fyers = create_fyers_model()

                    # 2. Call the Fyers API quotes method and fetch current value of INDEX
                    data = {"symbols": NIFTY_OPTION_SYMBOL}
                    response = fyers.quotes(data)

                    if response.get('s') == 'ok':
                        for item in response['d']:
                            index = item['n']
                            index_value = item['v']['lp']
                            display(f'\n Current Value of {index} is {index_value}')
                    else:
                        display('\n NOK Response Received while fetching NIFTY_OPTION_SYMBOL value !!!!!')
                        display(f'\nResponse: \n{response}')

                    # 3. Extract unique symbols from df_order and join them with commas
                    # This creates a string like: "NSE:NIFTY2610625850PE,NSE:SBIN-EQ,..."
                    symbol_list = df_entry['symbol'].tolist()
                    symbol_string = ",".join(symbol_list)

                    # 4. Call the Fyers API quotes method
                    data = {"symbols": symbol_string}
                    response = fyers.quotes(data)

                    while response.get('s') != 'ok':
                        display('\n NOK Response Received while fetching Basket Items LTP !!!!!')
                        display(f'\nResponse: \n{response}')
                        display(f'\n Retrying...')
                        sleep(2)
                        response = fyers.quotes(data)

                    # 5. Process the response into a list of dictionaries
                    current_data = []

                    # if response.get('s') == 'ok':
                    for item in response['d']:
                        # Extract the symbol name (n) and the Last Price (lp)
                        current_data.append({
                            'symbol': item['n'],
                            'current_price': item['v']['lp']
                        })

                    # 6. Create the new DataFrame df_current
                    df_current = pd.DataFrame(current_data)

                    df_current['entry_price']=df_entry['ltp']
                    df_current['lot_size']=df_entry['lot_size']
                    df_current['category']=df_entry['category']
                    df_current['action']=df_entry['action']
                    df_current['entry_cost']=df_entry['entry_cost']
                    df_current['current_cost']=df_current['current_price']*df_current['lot_size']

                    df_current['p/l'] = np.where(df_current['action'] == 'BUY', (df_current['current_cost']-df_current['entry_cost']), (df_current['entry_cost']-df_current['current_cost']))

                    display('\n df_current:')
                    display(df_current)

                    current_pl = df_current['p/l'].sum()

                    display(f'\n Current P/L: {current_pl}')

                    display(f'\n Entry Margin: {margin}')

                    # trade_approved, current_margin = fetch_funds_and_compare(df_order, avl_bal)
                    # current_margin = fetch_margin(df_current)

                    # display(f'\n Current Margin: {current_margin}')

                    # current_pl = current_pl + (current_margin - margin)

                    # display(f'\n  Adjusted Current P/L for Margin Delta: {current_pl}')

                    display(f'\n Available Balance: {avl_bal:.2f}')

                    new_current_pl_row = {
                        'TIME': get_now_ist().strftime('%Y-%m-%d %H:%M:%S'),
                        'INDEX': index,
                        'INDEX_VALUE': index_value,
                        'P/L': current_pl
                    }

                    # Method A: Using pd.concat (Recommended for modern pandas)
                    # Convert the dictionary to a Series, then wrap it in a DataFrame for proper concatenation
                    new_current_pl_row_df = pd.DataFrame([new_current_pl_row])
                    df_current_pl = pd.concat([df_current_pl, new_current_pl_row_df], ignore_index=True)

                    display('\n df_current_pl:')
                    # display(df_current_pl)
                    display(df_current_pl.to_string())

                    if current_pl > 900:  # Ratio 1:1.2
                        exit_flag = True
                        display(f'\n  TP Reached. Exiting with Profit of {current_pl}')
                        # break
                    elif current_pl <= -750:
                        exit_flag = True
                        display(f'\n  SL Reached. Exiting with Loss of {current_pl}')
                        # break
                    elif get_now_ist() >= today_intraday_squareoff_ist:
                        exit_flag = True
                        message = f"\n************ INTRADAY SQUARE-OFF TIME CROSSED ************"
                        capture_output(message, output_file_path)
                        # send_telegram_message(message)
                        display(f'\n  Closing remaining open positions after 15:20:00...')
                        # break
                    else:
                        exit_flag = False

                    if exit_flag:

                        # display(f'\n  TP Reached. Exiting with Profit of {current_pl}')

                        # # trade_approved, current_margin = fetch_funds_and_compare(df_order, avl_bal)
                        # current_margin = fetch_margin(df_current)

                        # display(f'\n Current Margin: {current_margin}')

                        avl_bal = avl_bal + margin + current_pl

                        # Find the row with today's date and update the available balance
                        df_avl_bal['available_balance'] = df_avl_bal['available_balance'].astype(float)
                        df_avl_bal.loc[df_avl_bal['date'] == today_date_str, 'available_balance'] = round(avl_bal, 2)
                        df_avl_bal.to_csv(avl_bal_file_path, index=False)
                        capture_output(f"\n  Updated available balance saved: {avl_bal:.2f}", output_file_path)

                        today_p_l = df_avl_bal.loc[df_avl_bal['date'] == today_date_str, 'P/L'].values[0]
                        today_p_l = today_p_l + current_pl
                        # today_p_l = avl_bal - start_capital

                        # Find the row with today's date and update today's p_l
                        df_avl_bal['P/L'] = df_avl_bal['P/L'].astype(float)
                        df_avl_bal.loc[df_avl_bal['date'] == today_date_str, 'P/L'] = today_p_l
                        df_avl_bal.to_csv(avl_bal_file_path, index=False)
                        capture_output(f"\n  Updated today's P/L saved: {today_p_l:.2f}", output_file_path)

                        break

                    # if current_pl <= -750:

                    #     display(f'\n  SL Reached. Exiting with Loss of {current_pl}')

                    #     # # trade_approved, current_margin = fetch_funds_and_compare(df_order, avl_bal)
                    #     # current_margin = fetch_margin(df_current)

                    #     # display(f'\n Current Margin: {current_margin}')

                    #     avl_bal = avl_bal + margin + current_pl

                    #     # Find the row with today's date and update the available balance
                    #     df_avl_bal['available_balance'] = df_avl_bal['available_balance'].astype(float)
                    #     df_avl_bal.loc[df_avl_bal['date'] == today_date_str, 'available_balance'] = round(avl_bal, 2)
                    #     df_avl_bal.to_csv(avl_bal_file_path, index=False)
                    #     capture_output(f"\n  Updated available balance saved: {avl_bal:.2f}", output_file_path)

                    #     today_p_l = df_avl_bal.loc[df_avl_bal['date'] == today_date_str, 'P/L'].values[0]
                    #     today_p_l = today_p_l + current_pl
                    #     # today_p_l = avl_bal - start_capital

                    #     # Find the row with today's date and update today's p_l
                    #     df_avl_bal['P/L'] = df_avl_bal['P/L'].astype(float)
                    #     df_avl_bal.loc[df_avl_bal['date'] == today_date_str, 'P/L'] = today_p_l
                    #     df_avl_bal.to_csv(avl_bal_file_path, index=False)
                    #     capture_output(f"\n  Updated today's P/L saved: {today_p_l:.2f}", output_file_path)

                    #     break

                    else:

                        wait(CHECK_INTERVAL, output_file_path)
                    # else:

                    #     display('\n NOK Response Received while fetching Basket Items LTP !!!!!')
                    #     display(f'\nResponse: \n{response}')
                    #     wait(CHECK_INTERVAL, output_file_path)
                # ##############################################################################################################################################################################  Close Remaining Positions After 15:15 - for IntraDay

                # now = get_now_ist()

                # if now >= today_intraday_squareoff_ist:

                #     # Close any remaining open positions at the last available price after market close
                #     capture_output("\nClosing remaining open positions after 15:20:00...", output_file_path)

                #     capture_output(f"\nNOW: {get_now_ist().strftime('%Y-%m-%d %H:%M:%S')}", output_file_path)

                #     # 1. Extract unique symbols from df_order and join them with commas
                #     # This creates a string like: "NSE:NIFTY2610625850PE,NSE:SBIN-EQ,..."
                #     symbol_list = df_entry['symbol'].tolist()
                #     symbol_string = ",".join(symbol_list)

                #     # 2. Call the Fyers API quotes method
                #     data = {"symbols": symbol_string}
                #     response = fyers.quotes(data)

                #     # 3. Process the response into a list of dictionaries
                #     current_data = []

                #     if response.get('s') == 'ok':
                #         for item in response['d']:
                #             # Extract the symbol name (n) and the Last Price (lp)
                #             current_data.append({
                #                 'symbol': item['n'],
                #                 'current_price': item['v']['lp']
                #             })

                #         # 4. Create the new DataFrame df_current
                #         df_current = pd.DataFrame(current_data)

                #         df_current['entry_price']=df_entry['ltp']
                #         df_current['lot_size']=df_entry['lot_size']
                #         df_current['category']=df_entry['category']
                #         df_current['action']=df_entry['action']
                #         df_current['entry_cost']=df_entry['entry_cost']
                #         df_current['current_cost']=df_current['current_price']*df_current['lot_size']

                #         df_current['p/l'] = np.where(df_current['action'] == 'BUY', (df_current['current_cost']-df_current['entry_cost']), (df_current['entry_cost']-df_current['current_cost']))

                #         display('\n df_current:')
                #         display(df_current)

                #         current_pl = df_current['p/l'].sum()

                #         display(f'\n Current P/L: {current_pl}')

                #         display(f'\n Entry Margin: {margin}')

                #         # # trade_approved, current_margin = fetch_funds_and_compare(df_order, avl_bal)
                #         # current_margin = fetch_margin(df_current)

                #         # display(f'\n Current Margin: {current_margin}')

                #         display(f'\n Available Balance: {avl_bal:.2f}')

                #         new_current_pl_row = {
                #             'TIME': get_now_ist().strftime('%Y-%m-%d %H:%M:%S'),
                #             'INDEX': index,
                #             'INDEX_VALUE': index_value,
                #             'P/L': current_pl
                #         }

                #         # Method A: Using pd.concat (Recommended for modern pandas)
                #         # Convert the dictionary to a Series, then wrap it in a DataFrame for proper concatenation
                #         new_current_pl_row_df = pd.DataFrame([new_current_pl_row])
                #         df_current_pl = pd.concat([df_current_pl, new_current_pl_row_df], ignore_index=True)

                #         display('\n df_current_pl:')
                #         # display(df_current_pl)
                #         display(df_current_pl.to_string())

                #         avl_bal = avl_bal + margin + current_pl

                #         # Find the row with today's date and update the available balance
                #         df_avl_bal['available_balance'] = df_avl_bal['available_balance'].astype(float)
                #         df_avl_bal.loc[df_avl_bal['date'] == today_date_str, 'available_balance'] = round(avl_bal, 2)
                #         df_avl_bal.to_csv(avl_bal_file_path, index=False)
                #         capture_output(f"\n  Updated available balance saved: {avl_bal:.2f}", output_file_path)

                #         today_p_l = df_avl_bal.loc[df_avl_bal['date'] == today_date_str, 'P/L'].values[0]
                #         today_p_l = today_p_l + current_pl
                #         # today_p_l = avl_bal - start_capital

                #         # Find the row with today's date and update today's p_l
                #         df_avl_bal['P/L'] = df_avl_bal['P/L'].astype(float)
                #         df_avl_bal.loc[df_avl_bal['date'] == today_date_str, 'P/L'] = today_p_l
                #         df_avl_bal.to_csv(avl_bal_file_path, index=False)
                #         capture_output(f"\n  Updated today's P/L saved: {today_p_l:.2f}", output_file_path)

                #         message = f"\n************ PAPER TRADING STOPPED @ {get_now_ist().strftime('%Y-%m-%d %H:%M:%S')} ************"
                #         capture_output(message, output_file_path)
                #         # send_telegram_message(message)

                # ##############################################################################################################################################################################

                if get_market_status(10, 'NORMAL', 10) != 'OPEN' or get_now_ist() >= today_market_close_ist:
                    message = "\n******* MARKET CLOSED FOR TODAY *******"
                    capture_output(message, output_file_path)
                    # send_telegram_message(message)

                    message = f"\n************ PAPER TRADING STOPPED @ {get_now_ist().strftime('%Y-%m-%d %H:%M:%S')} ************"
                    capture_output(message, output_file_path)
                    # send_telegram_message(message)

                elif get_now_ist() >= today_intraday_squareoff_ist:

                    message = f"\n************ PAPER TRADING STOPPED @ {get_now_ist().strftime('%Y-%m-%d %H:%M:%S')} ************"
                    capture_output(message, output_file_path)
                    # send_telegram_message(message)

                elif get_now_ist() >= today_intraday_buy_cutoff_ist:

                    message = f"\n************ INTRADAY BUY CUT-OFF TIME CROSSED ************"
                    capture_output(message, output_file_path)
                    # send_telegram_message(message)

                    message = f"\n************ PAPER TRADING STOPPED @ {get_now_ist().strftime('%Y-%m-%d %H:%M:%S')} ************"
                    capture_output(message, output_file_path)
                    # send_telegram_message(message)

                    break

                else:

                    message = f"\n************ MARKET OPEN. EXECUTING RE-ENTRY ************"
                    capture_output(message, output_file_path)
                    # send_telegram_message(message)

            else:

                display('\n ********* TRADE NOT APPROVED. EXECUTING RE-ENTRY *********')

                # # capture_output("\n************ MARKET CLOSED ************", output_file_path)
                # message = f"\n************ PAPER TRADING STOPPED @ {get_now_ist().strftime('%Y-%m-%d %H:%M:%S')} ************"
                # capture_output(message, output_file_path)
                # # send_telegram_message(message)

                wait(CHECK_INTERVAL, output_file_path)


        # message = f"\n************ INTRADAY SQUARE-OFF TIME CROSSED ************"
        # capture_output(message, output_file_path)
        # # send_telegram_message(message)

        # message = f"\n************ PAPER TRADING STOPPED @ {get_now_ist().strftime('%Y-%m-%d %H:%M:%S')} ************"
        # capture_output(message, output_file_path)
        # # send_telegram_message(message)

    else:

        message = "\n******* MARKET CLOSED FOR TODAY *******"
        capture_output(message, output_file_path)
        # send_telegram_message(message)

"""# TRADE"""

# Function: Market Time

def trade(trade_type):

  now=get_now_ist()

  # Define market open and close times in IST

  market_open_time_ist = datetime.time(9, 15, 0)
  trade_execute_time_ist = datetime.time(9, 20, 0)
  intraday_buy_cutoff_time_ist = datetime.time(14, 30, 0)
  intraday_squareoff_time_ist = datetime.time(15, 20, 0)
  market_close_time_ist = datetime.time(15, 30, 0)

#   market_open_time_ist = datetime.time(0, 0, 0)
#   trade_execute_time_ist = datetime.time(0, 0, 0)
#   intraday_buy_cutoff_time_ist = datetime.time(23, 59, 0)
#   intraday_squareoff_time_ist = datetime.time(23, 59, 0)
#   market_close_time_ist = datetime.time(23, 59, 0)

  # Create datetime objects for market open and close today in IST
  today_market_open_ist = now.replace(hour=market_open_time_ist.hour, minute=market_open_time_ist.minute, second=market_open_time_ist.second, microsecond=0)
  today_trade_execute_ist = now.replace(hour=trade_execute_time_ist.hour, minute=trade_execute_time_ist.minute, second=trade_execute_time_ist.second, microsecond=0)
  today_intraday_buy_cutoff_ist = now.replace(hour=intraday_buy_cutoff_time_ist.hour, minute=intraday_buy_cutoff_time_ist.minute, second=intraday_buy_cutoff_time_ist.second, microsecond=0)
  today_intraday_squareoff_ist = now.replace(hour=intraday_squareoff_time_ist.hour, minute=intraday_squareoff_time_ist.minute, second=intraday_squareoff_time_ist.second, microsecond=0)
  today_market_close_ist = now.replace(hour=market_close_time_ist.hour, minute=market_close_time_ist.minute, second=market_close_time_ist.second, microsecond=0)


  # Determine the time to wait
  if now < today_market_open_ist:
      # If current time is before market open today, wait until market open today
      time_to_wait = (today_market_open_ist - now).total_seconds()
      message = f"Current time is before market open. Waiting until {market_open_time_ist} IST."
      display(message)
      # send_telegram_message(message)
      sleep(time_to_wait)
      # display("Market is open. Start processing immediately.")
      # display("\nRUNNIN CODE")
      if trade_type == 'paper':
        paper_trade(today_trade_execute_ist, today_intraday_buy_cutoff_ist, today_intraday_squareoff_ist, today_market_close_ist)
      elif trade_type == 'live':
        live_trade()

  elif today_market_open_ist < now < today_market_close_ist:
      # If current time is between market open and close, start immediately
      # display("Market is open. Start processing immediately.")
      # display("\nRUNNIN CODE")
      if trade_type == 'paper':
        paper_trade(today_trade_execute_ist, today_intraday_buy_cutoff_ist, today_intraday_squareoff_ist, today_market_close_ist)
      elif trade_type == 'live':
        live_trade()

  else:
      # If current time is after market close today, wait until market open tomorrow
      message = "Current time is after market close. Restart on next trading day."
      display(message)
      # send_telegram_message(message)

trade('paper')