from modules import terminal
import schwabdev

from dotenv import load_dotenv
import time
from datetime import datetime, timedelta
import csv
import datetime
import os
import pytz
import json
import requests
import sys
import traceback
import numpy as np
from scipy.stats import norm
from scipy import optimize
import copy


# nohup /bin/python3 -u /var/www/html/vps_streaming_csV2/allchainIV-Original.py &
# tail -f nohup.out
# ps aux | grep python | grep "/var/www/html"

load_dotenv()  # Cargar variables de entorno desde el archivo .env
client = schwabdev.Client(os.getenv('appKey'), os.getenv('appSecret'), os.getenv('callbackUrl'), verbose=True)


# Cambiar al directorio
os.chdir('/var/www/html/vps_streaming_csV2')

PATH_UBUNTU = "/var/www/html/flask_project/"
MAX_RETRIES = 5
RETRY_DELAY_SECONDS = 30
SYMBOL = "$SPX"

# from_date = "2024-07-10"
# to_date = "2024-07-10"
from_date = datetime.datetime.now().strftime("%Y-%m-%d")
to_date = datetime.datetime.now().strftime("%Y-%m-%d")
totalStrikes = 120

timeOpen = "09:29:00"
timeClose = "16:02:00"

timeIB1 = "09:40:00"
timeIB1Close = "16:02:00"

timeIB2 = "10:30:00"
timeIB2Close = "16:02:00"

timeIB3 = "11:00:00"
timeIB3Close = "16:02:00"

timeIB4 = "11:30:00"
timeIB4Close = "16:02:00"

timeIB5 = "09:31:00"
timeIB5Close = "16:02:00"

timeIB6 = "09:31:00"
timeIB6Close = "16:02:00"

timeIB7 = "09:31:00"
timeIB7Close = "16:02:00"

timeIB8 = "10:30:00"
timeIB8Close = "16:02:00"

# timeOpen = timeIB1 = timeIB2 = timeIB3 =timeIB4=timeIB5=timeIB6=timeIB7=timeIB8= "00:20:00"

fechaActual = datetime.datetime.now().strftime("%Y-%m-%d")
archivo_csv1 = PATH_UBUNTU + "Intraday_IB0940_" + str(fechaActual) + ".csv"
archivo_csv2 = PATH_UBUNTU + "Intraday_IB1030_" + str(fechaActual) + ".csv"
archivo_csv3 = PATH_UBUNTU + "Intraday_IB1100_" + str(fechaActual) + ".csv"
archivo_csv4 = PATH_UBUNTU + "Intraday_IB1130_" + str(fechaActual) + ".csv"
archivo_csv5 = PATH_UBUNTU + "Intraday_IB0932_" + str(fechaActual) + ".csv"  # IB 9:31 30 PUNTOS
archivo_csv6 = PATH_UBUNTU + "Intraday_IB0933_" + str(fechaActual) + ".csv"  # IB 9:31 50 PUNTOS
archivo_csv7 = PATH_UBUNTU + "Intraday_IC0932_" + str(fechaActual) + ".csv"  # IC 9:31 DELTA .10 - 20 ANCHO DE ALAS
archivo_csv8 = PATH_UBUNTU + "Intraday_IC1030_" + str(fechaActual) + ".csv"  # IC 10:30 DELTA .10 - 20 ANCHO DE ALAS

archivo_IV_EM = PATH_UBUNTU + "Intraday_IV_EM_" + str(fechaActual) + ".csv"


# Función de Black-Scholes para opciones Call
def black_scholes_call(S, K, T, r, sigma):
    d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)
    call_price = S * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
    return call_price


# Función de Black-Scholes para opciones Put
def black_scholes_put(S, K, T, r, sigma):
    d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)
    put_price = K * np.exp(-r * T) * norm.cdf(-d2) - S * norm.cdf(-d1)
    return put_price


# Función para calcular la volatilidad implícita
def implied_volatility(option_price, S, K, T, r, option_type='call'):
    try:
        def objective(sigma): return (black_scholes_call(S, K, T, r, sigma) - option_price) if option_type == 'call' else (black_scholes_put(S, K, T, r, sigma) - option_price)
        implied_vol = optimize.newton(objective, 0.2, tol=1e-6)
        return implied_vol
    except RuntimeError:
        return np.nan  # Retorna NaN si no se puede encontrar la volatilidad implícita


# Funcion para obtener quotes con reintento
def obtener_quotes_con_reintento(instruments):
    for intento in range(MAX_RETRIES):
        try:
            quotes_response = client.quotes(instruments).json()
            return quotes_response
        except json.decoder.JSONDecodeError as json_error:
            print(
                f"Error al decodificar JSON en el intento {intento + 1}: {json_error}")
        except requests.RequestException as request_error:
            print(
                f"Error en la solicitud HTTP en el intento {intento + 1}: {request_error}")
        except Exception as e:
            print(
                f"Otro error inesperado en el intento {intento + 1}: {type(e).__name__}: {e}")
        time.sleep(RETRY_DELAY_SECONDS)
    else:
        terminal.colorPrint.error(f"Se ha producido un error en {MAX_RETRIES} intentos. Deteniendo el programa.")
        sys.exit(1)


# Funcion para obtener cadena de opciones con reintento
def obtener_cadena_con_reintento(SYMBOL, from_date, to_date, totalStrikes):
    for intento in range(MAX_RETRIES):
        try:
            chain_response = client.option_chains(SYMBOL, fromDate=from_date, toDate=to_date, strikeCount=totalStrikes)
            return chain_response
        except json.decoder.JSONDecodeError as json_error:
            print(f"Error al decodificar JSON en el intento {intento + 1}: {json_error}")
        except requests.RequestException as request_error:
            print(f"Error en la solicitud HTTP en el intento {intento + 1}: {request_error}")
        except Exception as e:
            print(f"Otro error inesperado en el intento {intento + 1}: {type(e).__name__}: {e}")
        time.sleep(RETRY_DELAY_SECONDS)
    else:
        terminal.colorPrint.error(f"Se ha producido un error en {MAX_RETRIES} intentos. Deteniendo el programa.")
        sys.exit(1)


# Función para extraer datos de opciones
def extract_data(option_data):
    extracted_data = {}
    timestamp = datetime.datetime.now(pytz.timezone('America/New_York')).strftime('%m/%d/%Y %H:%M:%S')
    underlying_price = option_data.get('underlyingPrice', '')
    for option_type in ['callExpDateMap', 'putExpDateMap']:
        if option_type in option_data:
            for date_key, strikes in option_data[option_type].items():
                for strike, details in strikes.items():
                    for detail in details:
                        strike_price = detail.get('strikePrice', '')
                        bid = detail.get('bid', '')
                        ask = detail.get('ask', '')
                        delta = detail.get('delta', '')
                        if strike_price not in extracted_data:
                            extracted_data[strike_price] = {'timestamp': timestamp, 'underlying_price': underlying_price, 'strike': strike_price}
                        if option_type == 'callExpDateMap':
                            extracted_data[strike_price]['bid_call'] = bid
                            extracted_data[strike_price]['ask_call'] = ask
                            extracted_data[strike_price]['delta_call'] = delta
                        else:
                            extracted_data[strike_price]['bid_put'] = bid
                            extracted_data[strike_price]['ask_put'] = ask
                            extracted_data[strike_price]['delta_put'] = delta
    return list(extracted_data.values())


# Función para guardar la cadena de opciones en un archivo CSV
def save_chain_to_cvs(data):
    fechaActual = datetime.datetime.now().strftime("%Y-%m-%d")
    filename = PATH_UBUNTU + "optionChain_" + str(fechaActual) + ".csv"
    header = ['timestamp', 'underlying_price', 'strike', 'bid_call', 'ask_call', 'bid_put', 'ask_put', 'delta_call', 'delta_put']
    file_exists = os.path.isfile(filename)
    try:
        with open(filename, mode='a', newline='') as file:
            writer = csv.DictWriter(file, fieldnames=header)
            if not file_exists:
                writer.writeheader()
                os.chmod(filename, 0o664)

            writer.writerows(data)

    except Exception as e:
        print(f"Error al guardar los datos en el archivo CSV: {e}")


# Función para calcular el precio del Iron Condor
def calculate_iron_condor_price(data_to_save, call_strike_sell, call_strike_buy, put_strike_sell, put_strike_buy):
    global totalStrikes
    global from_date
    global to_date

    attempt = 0

    while attempt < MAX_RETRIES:
        try:
            # Incrementar el contador de intentos
            attempt += 1

            # Filtrar los datos relevantes
            relevant_data = [row for row in data_to_save if str(row['strike']) in [call_strike_sell, call_strike_buy, put_strike_sell, put_strike_buy]]
            if not relevant_data:
                raise ValueError("No relevant data found for the given strike prices.")

            call_strike_sell_data = next((item for item in relevant_data if str(item['strike']) == call_strike_sell), None)
            call_strike_buy_data = next((item for item in relevant_data if str(item['strike']) == call_strike_buy), None)
            put_strike_sell_data = next((item for item in relevant_data if str(item['strike']) == put_strike_sell), None)
            put_strike_buy_data = next((item for item in relevant_data if str(item['strike']) == put_strike_buy), None)

            if call_strike_sell_data is None:
                raise ValueError(f"No data found for call strike sell price: {call_strike_sell}")
            if call_strike_buy_data is None:
                raise ValueError(f"No data found for call strike buy price: {call_strike_buy}")
            if put_strike_sell_data is None:
                raise ValueError(f"No data found for put strike sell price: {put_strike_sell}")
            if put_strike_buy_data is None:
                raise ValueError(f"No data found for put strike buy price: {put_strike_buy}")

            # Si todos los datos específicos se encuentran, salir del bucle
            break

        except Exception as e:
            terminal.colorPrint.error(f"Error en el intento {attempt}: {e}")

            # Incrementar totalStrikes en 5 para el siguiente intento
            totalStrikes += 5

            # Volver a obtener la cadena de opciones y extraer los datos
            try:
                options_chain = obtener_cadena_con_reintento(SYMBOL, from_date, to_date, totalStrikes).json()
                data_to_save = extract_data(options_chain)
            except Exception as e:
                terminal.colorPrint.error(f"Error al regenerar data_to_save: {e}")
                raise

            # Si el número de intentos excede el máximo permitido, lanzar una excepción
            if attempt >= MAX_RETRIES:
                terminal.colorPrint.error("Max retries exceeded. Could not find all relevant data.")
                # Volver a imprimir el error y la traza fuera del bucle
                raise

    # Calcular los precios del Iron Condor
    sellCall = (call_strike_sell_data['ask_call'] + call_strike_sell_data['bid_call']) / 2
    buyCall = (call_strike_buy_data['ask_call'] + call_strike_buy_data['bid_call']) / 2
    sellPut = (put_strike_sell_data['ask_put'] + put_strike_sell_data['bid_put']) / 2
    buyPut = (put_strike_buy_data['ask_put'] + put_strike_buy_data['bid_put']) / 2

    # Calcular el precio del Iron Condor
    iron_condor_price = (sellCall - buyCall + sellPut - buyPut) * 100
    iron_condor_price = "{:.2f}".format(iron_condor_price)

    return iron_condor_price


# Funcion para guardar los datos del IC en el archivo CSV
def save_iron_condor_to_csv(timestamp, iron_condor_price, underlying_price, elVIX, expectedMove, filename):
    # si expectedMove es Nan, no grabar nada en el archivo, solo salir
    if np.isnan(expectedMove):
        expectedMove=0

    attempts = 0
    while attempts < MAX_RETRIES:
        try:
            with open(filename, mode='a', newline='') as file:
                writer = csv.writer(file)
                writer.writerow([timestamp, iron_condor_price, underlying_price, elVIX, expectedMove])
            break  # Salir del bucle si se guarda exitosamente
        except Exception as e:
            terminal.colorPrint.error(f"Ocurrió un error al guardar en el archivo CSV: {e}")
            attempts += 1
            if attempts < MAX_RETRIES:
                terminal.colorPrint.info("Intentando de nuevo en 5 segundos...")
                time.sleep(5)
            else:
                terminal.colorPrint.error("No se pudo guardar en el archivo CSV después de 3 intentos. Deteniendo el programa.")
                raise  # Levantar la excepción para que el programa termine


# Funcion para obtener la figura del IC 
def obtenerFiguraIC(archivo, sellCall, sellPut, buyCall, buyPut):
    # Chequear si existe el archivo
    if os.path.isfile(archivo):
        terminal.colorPrint.info("The CSV file exists. Getting Data From File.")
        with open(archivo, 'r') as archivo_csv:
            lector_csv = csv.reader(archivo_csv)
            primera_fila = next(lector_csv)
            sellCall = primera_fila[0]
            sellPut = primera_fila[1]
            buyCall = primera_fila[2]
            buyPut = primera_fila[3]

        terminal.colorPrint.info(f'[BUY CALL:{buyCall}] [SELL CALL:{sellCall}] [SELL PUT:{sellPut}] [BUY PUT:{buyPut}]')
    # NO existe el archivo - Generar figura a partir de datos en real time
    else:
        terminal.colorPrint.info("The CSV file don't exists. Getting Data From Real Time.")
        spxStrike = 0
        elSPX = obtener_quotes_con_reintento(instruments=["$SPX"])
        if (elSPX is not None and "$SPX" in elSPX and "quote" in elSPX["$SPX"] and "lastPrice" in elSPX["$SPX"]["quote"]):
            SPXLastPrice = elSPX["$SPX"]["quote"]["lastPrice"]
            mod = SPXLastPrice % 5
            if mod > 2.5:
                spxStrike = int(SPXLastPrice - mod) + 5
            else:
                spxStrike = int(SPXLastPrice - mod)

            sellCall = spxStrike+sellCall
            sellPut = spxStrike+sellPut
            buyCall = spxStrike+buyCall
            buyPut = spxStrike-buyPut

            terminal.colorPrint.info(f'[BUY CALL:{buyCall}] [SELL CALL:{sellCall}] [SELL PUT:{sellPut}] [BUY PUT:{buyPut}]')

            with open(archivo, 'w', newline='') as archivo_csv:
                escritor_csv = csv.writer(archivo_csv)
                escritor_csv.writerow([sellCall, sellPut, buyCall, buyPut])

            # Dar permisos de lectura y escritura para todos los usuarios
            os.chmod(archivo, 0o664)

    return sellCall, sellPut, buyCall, buyPut


# Función para extraer los valores de delta y strike
def extract_delta_strike_pairs(option_data):
    delta_strike_pairs = []
    # Recorrer las claves del mapa de expiración de llamadas
    for exp_date, strikes in option_data.get('callExpDateMap', {}).items():
        for strike, options in strikes.items():
            for option in options:
                if 'delta' in option:
                    delta_strike_pairs.append((option['delta'], float(strike)))

    # Recorrer las claves del mapa de expiración de ventas
    for exp_date, strikes in option_data.get('putExpDateMap', {}).items():
        for strike, options in strikes.items():
            for option in options:
                if 'delta' in option:
                    delta_strike_pairs.append((option['delta'], float(strike)))

    return delta_strike_pairs


# Función para encontrar el strike más cercano a un delta objetivo
def find_closest_strike(delta_strike_pairs, target_delta):
    closest_pair = min(delta_strike_pairs, key=lambda x: abs(x[0] - target_delta))
    return closest_pair


# Funcion para armar la figura del IC con deltas 0.10 y anchos de 20 puntos
def obtenerFiguraICDelta(archivo, deltaCall, deltaPut, anchoCall, anchoPut):
    # Chequear si existe el archivo
    if os.path.isfile(archivo):
        terminal.colorPrint.info("The CSV file exists. Getting Data From File.")
        with open(archivo, 'r') as archivo_csv:
            lector_csv = csv.reader(archivo_csv)
            primera_fila = next(lector_csv)
            sellCall = primera_fila[0]
            sellPut = primera_fila[1]
            buyCall = primera_fila[2]
            buyPut = primera_fila[3]

        terminal.colorPrint.info(f'[BUY CALL:{buyCall}] [SELL CALL:{sellCall}] [SELL PUT:{sellPut}] [BUY PUT:{buyPut}]')
    else:
        terminal.colorPrint.info("The CSV file don't exists. Getting Data From Real Time.")
        options_chain = (client.option_chains(SYMBOL, fromDate=from_date, toDate=to_date, strikeCount=totalStrikes).json())

        # Llamar a la función y obtener los pares de delta y strike
        delta_strike_pairs = extract_delta_strike_pairs(options_chain)
        # Encontrar los strikes más cercanos
        strikeDeltaCall = find_closest_strike(delta_strike_pairs, deltaCall)
        strikeDeltaPut = find_closest_strike(delta_strike_pairs, deltaPut)

        # # Mostrar los resultados
        terminal.colorPrint.info(f"El strike mas cercano al delta 0.10 es : {strikeDeltaCall[1]} con delta {strikeDeltaCall[0]}")
        terminal.colorPrint.info(f"El strike mas cercano al delta -0.10 es: {strikeDeltaPut[1]} con delta {strikeDeltaPut[0]}")

        sellCall = int(float(strikeDeltaCall[1]))
        sellPut = int(float(strikeDeltaPut[1]))
        buyCall = sellCall + anchoCall
        buyPut = sellPut - anchoPut

        terminal.colorPrint.info(f'[BUY CALL:{buyCall}] [SELL CALL:{sellCall}] [SELL PUT:{sellPut}] [BUY PUT:{buyPut}]')

        with open(archivo, 'w', newline='') as archivo_csv:
            escritor_csv = csv.writer(archivo_csv)
            escritor_csv.writerow([sellCall, sellPut, buyCall, buyPut])

        # Dar permisos de lectura y escritura para todos los usuarios
        os.chmod(archivo, 0o664)

    return sellCall, sellPut, buyCall, buyPut


# Funcion para obtener el IV y la EM ademas grabar datos en archivo
def save_IV_EM(strike_atm):

    risk_free_rate = 0.054
    time_to_expiration = 1 / 252

    S = strike_atm['underlying_price']
    K = strike_atm['strike']
    option_price_call = (strike_atm['bid_call'] + strike_atm['ask_call']) / 2
    option_price_put = (strike_atm['bid_put'] + strike_atm['ask_put']) / 2

    iv_call = implied_volatility(option_price_call, S, K, time_to_expiration, risk_free_rate, option_type='call')
    iv_put = implied_volatility(option_price_put, S, K, time_to_expiration, risk_free_rate, option_type='put')

    if np.isnan(iv_call):
        iv_call = 0
    if np.isnan(iv_put):
        iv_put = 0

    iv_avg = round(((iv_call + iv_put) / 2), 3)
    expected_move = round(S * iv_avg * np.sqrt(time_to_expiration), 3)

    data1 = {
        'timestamp': strike_atm['timestamp'],
        'strike': K,
        'underlying_price': S,
        'iv_call': iv_call,
        'iv_put': iv_put,
        'iv_avg': iv_avg,
        'expected_move': expected_move,
        'VIX': strike_atm['VIX']
    }

    # Escribir los datos en un archivo CSV

    file_exists = os.path.isfile(archivo_IV_EM)
    with open(archivo_IV_EM, mode='a', newline='') as file:
        writer = csv.DictWriter(file, fieldnames=data1.keys())
        if not file_exists:
            writer.writeheader()
        writer.writerow(data1)

    return expected_move, iv_avg


def main():
    global totalStrikes
    terminal.colorPrint.info("***** Market opened!!! *****")

    IB1_activado = False
    IB2_activado = False
    IB3_activado = False
    IB4_activado = False
    IB5_activado = False
    IB6_activado = False
    IB7_activado = False
    IB8_activado = False

    while True:
        # Obtenemos la hora actual en formato hh:mm:ss
        hora_actual = time.strftime("%H:%M:%S")
        success = False  # Variable para determinar si el intento fue exitoso

        for intento in range(MAX_RETRIES):
            try:
                # Obtenemos la cadena de opciones
                options_chain = obtener_cadena_con_reintento(SYMBOL, from_date, to_date, totalStrikes).json()
                if not options_chain['callExpDateMap'] and not options_chain['putExpDateMap']:
                    raise ValueError("Empty callExpDateMap and putExpDateMap")

                success = True
                break  # Salir del bucle for si se procesa correctamente

            except ValueError as e:

                terminal.colorPrint.error(f"Error: {e}. Attempt {intento + 1} of {MAX_RETRIES}. Trying again...")
                time.sleep(10)

            except KeyError as e:
                terminal.colorPrint.error(f"Missing key: {e}. Attempt {intento + 1} of {MAX_RETRIES}. Trying again...")
                time.sleep(10)

            except Exception as e:
                terminal.colorPrint.error(f"An unexpected error occurred: {e}. Attempt {intento + 1} of {MAX_RETRIES}. Trying again...")
                time.sleep(10)

        if not success:
            terminal.colorPrint.error(f"Se ha producido un error en {MAX_RETRIES} intentos. Deteniendo el programa.")
            sys.exit(1)

        # Extraemos sola la info que se necesita
        data_to_save = extract_data(options_chain)
        underlying_price = data_to_save[0]['underlying_price']
        timestamp = data_to_save[0]['timestamp']

        # Obteniendo el VIX
        elVIX = obtener_quotes_con_reintento(instruments=["$VIX"])
        if (elVIX is not None and "$VIX" in elVIX and "quote" in elVIX["$VIX"] and "lastPrice" in elVIX["$VIX"]["quote"]):
            VIXLastPrice = elVIX["$VIX"]["quote"]["lastPrice"]

        # Obtenemos los datos del strike ATM
        strike_atm = min(data_to_save, key=lambda x: abs(x['strike'] - underlying_price))

        # Creamos una copia profunda de strike_atm antes de modificarlo
        strike_atm_copy = copy.deepcopy(strike_atm)
        strike_atm_copy['VIX'] = VIXLastPrice

        # Generamos y grabamos los datos del IV y el EM en el archivo correspondiente

        expectedMove, impliedVolatility = save_IV_EM(strike_atm_copy)

        terminal.colorPrint.spx(str(underlying_price))
        terminal.colorPrint.vix(str(VIXLastPrice))
        terminal.colorPrint.iv(str(impliedVolatility*100))

        terminal.colorPrint.em(str(expectedMove))

        # Comenzar el streaming para IB 9:30 con alas de 30
        if hora_actual >= timeIB5 and hora_actual <= timeIB5Close:
            if IB5_activado == False:
                terminal.colorPrint.info("Starting IB 9:30... Wings 30")

                # Chequear si archivo existe
                sellCall, sellPut, buyCall, buyPut = obtenerFiguraIC(archivo_csv5, 0, 0, 30, 30)

                call_strike_sell5 = str(sellCall) + ".0"
                call_strike_buy5 = str(buyCall) + ".0"
                put_strike_sell5 = str(sellPut) + ".0"
                put_strike_buy5 = str(buyPut) + ".0"

                IB5_activado = True

            iron_condor_price = calculate_iron_condor_price(data_to_save, call_strike_sell5, call_strike_buy5, put_strike_sell5, put_strike_buy5)

            save_iron_condor_to_csv(timestamp, iron_condor_price, underlying_price, VIXLastPrice, expectedMove, archivo_csv5)

            terminal.colorPrint.quote(f"{timestamp} {iron_condor_price} [IB 9:30 0DTE W30]")

        # Comenzar el streaming para IB 9:30 con alas de 50
        if hora_actual >= timeIB6 and hora_actual <= timeIB6Close:
            if IB6_activado == False:
                terminal.colorPrint.info("Starting IB 9:30... Wings 50")

                # Chequear si archivo existe
                sellCall, sellPut, buyCall, buyPut = obtenerFiguraIC(archivo_csv6, 0, 0, 50, 50)
                call_strike_sell6 = str(sellCall) + ".0"
                call_strike_buy6 = str(buyCall) + ".0"
                put_strike_sell6 = str(sellPut) + ".0"
                put_strike_buy6 = str(buyPut) + ".0"

                IB6_activado = True

            iron_condor_price = calculate_iron_condor_price(data_to_save, call_strike_sell6, call_strike_buy6, put_strike_sell6, put_strike_buy6)
            save_iron_condor_to_csv(timestamp, iron_condor_price, underlying_price, VIXLastPrice, expectedMove, archivo_csv6)
            terminal.colorPrint.quote(f"{timestamp} {iron_condor_price} [IB 9:30 0DTE W50]")

        # Comenzar el streaming para IC 9:30 con delta 10 y ancho de 20
        if hora_actual >= timeIB7 and hora_actual <= timeIB7Close:
            if IB7_activado == False:
                terminal.colorPrint.info("Starting IC 9:30... Delta 10 - Ancho alas de 20")

                # Chequear si archivo existe
                sellCall, sellPut, buyCall, buyPut = obtenerFiguraICDelta(archivo_csv7, 0.10, -0.10, 20, 20)

                call_strike_sell7 = str(sellCall) + ".0"
                call_strike_buy7 = str(buyCall) + ".0"
                put_strike_sell7 = str(sellPut) + ".0"
                put_strike_buy7 = str(buyPut) + ".0"

                IB7_activado = True

            iron_condor_price = calculate_iron_condor_price(data_to_save, call_strike_sell7, call_strike_buy7, put_strike_sell7, put_strike_buy7)
            save_iron_condor_to_csv(timestamp, iron_condor_price, underlying_price, VIXLastPrice, expectedMove, archivo_csv7)
            terminal.colorPrint.quote(f"{timestamp} {iron_condor_price} [IC  9:30 0DTE D10 W20]")

        # Comenzar el streaming para el 1er IB 09:40
        if hora_actual >= timeIB1 and hora_actual <= timeIB1Close:
            if IB1_activado == False:
                terminal.colorPrint.info("Starting IB 09:40... Wings 40")
                # Chequear si archivo existe
                sellCall, sellPut, buyCall, buyPut = obtenerFiguraIC(archivo_csv1, 0, 0, 40, 40)

                call_strike_sell1 = str(sellCall) + ".0"
                call_strike_buy1 = str(buyCall) + ".0"
                put_strike_sell1 = str(sellPut) + ".0"
                put_strike_buy1 = str(buyPut) + ".0"
                IB1_activado = True

            iron_condor_price = calculate_iron_condor_price(data_to_save, call_strike_sell1, call_strike_buy1, put_strike_sell1, put_strike_buy1)
            save_iron_condor_to_csv(timestamp, iron_condor_price, underlying_price, VIXLastPrice, expectedMove, archivo_csv1)
            terminal.colorPrint.quote(f"{timestamp} {iron_condor_price} [IB 9:40 0DTE W40]")

        # Comenzar el streaming para IC 10:30 con delta 10 y ancho de 20
        if hora_actual >= timeIB8 and hora_actual <= timeIB8Close:
            if IB8_activado == False:
                terminal.colorPrint.info("Starting IC 10:30... Delta 10 - Ancho alas de 20")

                # Chequear si archivo existe
                sellCall, sellPut, buyCall, buyPut = obtenerFiguraICDelta(archivo_csv8, 0.10, -0.10, 20, 20)

                call_strike_sell8 = str(sellCall) + ".0"
                call_strike_buy8 = str(buyCall) + ".0"
                put_strike_sell8 = str(sellPut) + ".0"
                put_strike_buy8 = str(buyPut) + ".0"

                IB8_activado = True

            iron_condor_price = calculate_iron_condor_price(data_to_save, call_strike_sell8, call_strike_buy8, put_strike_sell8, put_strike_buy8)
            save_iron_condor_to_csv(timestamp, iron_condor_price, underlying_price, VIXLastPrice, expectedMove, archivo_csv8)
            terminal.colorPrint.quote(f"{timestamp} {iron_condor_price} [IC 10:30 0DTE D10 W20]")

        # Comenzar el streaming para el IB 10:30
        if hora_actual >= timeIB2 and hora_actual <= timeIB2Close:
            if IB2_activado == False:
                terminal.colorPrint.info("Starting IB 10:30... Wings 40")

                # Chequear si archivo existe
                sellCall, sellPut, buyCall, buyPut = obtenerFiguraIC(archivo_csv2, 0, 0, 40, 40)

                call_strike_sell2 = str(sellCall) + ".0"
                call_strike_buy2 = str(buyCall) + ".0"
                put_strike_sell2 = str(sellPut) + ".0"
                put_strike_buy2 = str(buyPut) + ".0"

                IB2_activado = True

            iron_condor_price = calculate_iron_condor_price(data_to_save, call_strike_sell2, call_strike_buy2, put_strike_sell2, put_strike_buy2)
            save_iron_condor_to_csv(timestamp, iron_condor_price, underlying_price, VIXLastPrice, expectedMove, archivo_csv2)
            terminal.colorPrint.quote(f"{timestamp} {iron_condor_price} [IB 10:30 0DTE W40]")

        # Comenzar el streaming para IB 11:00
        if hora_actual >= timeIB3 and hora_actual <= timeIB3Close:
            if IB3_activado == False:
                terminal.colorPrint.info("Starting IB 11:00... Wings 40")

                # Chequear si archivo existe
                sellCall, sellPut, buyCall, buyPut = obtenerFiguraIC(
                    archivo_csv3, 0, 0, 40, 40)

                call_strike_sell3 = str(sellCall) + ".0"
                call_strike_buy3 = str(buyCall) + ".0"
                put_strike_sell3 = str(sellPut) + ".0"
                put_strike_buy3 = str(buyPut) + ".0"

                IB3_activado = True

            iron_condor_price = calculate_iron_condor_price(data_to_save, call_strike_sell3, call_strike_buy3, put_strike_sell3, put_strike_buy3)
            save_iron_condor_to_csv(timestamp, iron_condor_price, underlying_price, VIXLastPrice, expectedMove, archivo_csv3)
            terminal.colorPrint.quote(f"{timestamp} {iron_condor_price} [IB 11:00 0DTE W40]")

        # Comenzar el streaming para el IB 11:30
        if hora_actual >= timeIB4 and hora_actual <= timeIB4Close:
            if IB4_activado == False:
                terminal.colorPrint.info("Starting IB 11:30... Wings 40")
                # Chequear si archivo existe
                sellCall, sellPut, buyCall, buyPut = obtenerFiguraIC(archivo_csv4, 0, 0, 40, 40)

                call_strike_sell4 = str(sellCall) + ".0"
                call_strike_buy4 = str(buyCall) + ".0"
                put_strike_sell4 = str(sellPut) + ".0"
                put_strike_buy4 = str(buyPut) + ".0"

                IB4_activado = True

            iron_condor_price = calculate_iron_condor_price(data_to_save, call_strike_sell4, call_strike_buy4, put_strike_sell4, put_strike_buy4)
            save_iron_condor_to_csv(timestamp, iron_condor_price, underlying_price, VIXLastPrice, expectedMove, archivo_csv4)
            terminal.colorPrint.quote(f"{timestamp} {iron_condor_price} [IB 11:30 0DTE W40]")

        # Guarda la cadena de opciones

        save_chain_to_cvs(data_to_save)

        if hora_actual >= timeClose:
            return
        time.sleep(9)


def timeToMarketOpen(hora_actual, hora_apertura):
    diferencia_tiempo = hora_apertura - hora_actual
    # Imprimir la diferencia de tiempo en formato hh:mm:ss
    print(f"V6 - Time to begin process: {diferencia_tiempo // timedelta(hours=1):02}:{(diferencia_tiempo % timedelta(hours=1)) // timedelta(minutes=1):02}:{(diferencia_tiempo % timedelta(minutes=1)) // timedelta(seconds=1):02}", end="\r", flush=True)
    time.sleep(1)


def is_holiday(fecha, holidays):
    # Verificar si la fecha está en la lista de días feriados
    return fecha.strftime("%m/%d/%Y") in holidays


def calcular_hora_apertura(hora_actual, hora_primera_estrategia, holidays):
    dias_a_sumar = 1

    while True:
        # Calcular la nueva fecha sumando los días correspondientes
        nueva_fecha = hora_actual.date() + timedelta(days=dias_a_sumar)

        # Verificar si la nueva fecha es un día feriado o fin de semana (sábado o domingo)

        if not (is_holiday(nueva_fecha, holidays) or nueva_fecha.weekday() in [5, 6]):
            break  # Salir del bucle si no es un día feriado ni fin de semana

        # Si es un día feriado o fin de semana, sumar un día adicional y verificar nuevamente
        dias_a_sumar += 1

    # Calcular la nueva hora_apertura
    nueva_hora_apertura = datetime.datetime.combine(
        nueva_fecha, hora_primera_estrategia)
    print(nueva_hora_apertura)

    return nueva_hora_apertura


# Comienzo del progragma!!!
os.system("clear")  # Borra la pantalla!!!

marketIsOpen = False 
hora_actual_string = time.strftime("%H:%M:%S")
hora_actual = datetime.datetime.now()
hora_cierre = datetime.datetime.strptime(timeClose, "%H:%M:%S").time()
hora_market_open = datetime.datetime.strptime(timeOpen, "%H:%M:%S").time()
holidays = ["01/01/2024", "01/15/2024", "02/19/2024", "05/27/2024", "06/19/2024", "07/04/2024", "09/02/2024", "11/28/2024", "12/25/2024"]
# Lógica para determinar hora_apertura
if hora_actual.time() > hora_cierre and hora_actual.time() < datetime.time(23, 59, 59):
    # Si es de tarde, obtener la fecha del día siguiente
    hora_apertura = datetime.datetime.combine(hora_actual.date() + timedelta(days=1), hora_market_open)
else:
    # Si es mañana, obtener la fecha actual
    hora_apertura = datetime.datetime.combine(hora_actual.date(), hora_market_open)

try:
    while True:
        hora_actual = datetime.datetime.now()
        timeToMarketOpen(hora_actual, hora_apertura)

        if hora_actual >= hora_apertura:
            os.system("clear")  # Borra la pantalla!!!
            marketIsOpen = True  # Comenzar el streaming para el 1er estragegia!!!

        if marketIsOpen:
            # from_date = datetime.datetime.now().strftime("%Y-%m-%d")
            # to_date = datetime.datetime.now().strftime("%Y-%m-%d")
            # totalStrikes = 45

            # api.initialize()  # checks tokens & loads variables
            # api.updateTokensAutomatic()  # starts thread to update tokens automatically
            main()
            # Si salio de main, es porque el mercado cerró y volvera a iniciarse el proceso de espera!!!!

            print("\n***** The market is closed *****\n")
            marketIsOpen = False
            hora_apertura = calcular_hora_apertura(hora_actual, hora_market_open, holidays)


except Exception as e:
    error_message = f"Error occurred: {str(e)}\n\n{traceback.format_exc()}"
    # Envía un correo electrónico o notificación
