import pandas as pd
import matplotlib.pyplot as plt
import time
import math

# Configuración
PATH_UBUNTU = "/var/www/html/flask_project/"
pd.set_option('display.max_rows', None)


# Variables de rango de fechas
desde = "2025-04-10"  # Fecha inicial en formato 'YYYY-MM-DD'
hasta = "2025-04-30"  # Fecha final en formato 'YYYY-MM-DD'
symbol = 'SPX'
timeHour= '1310'
estrategia = "Sonar"
DESPLAZAMIENTO = -5 # Ajusta este valor según sea necesario (positivo o negativo múltiplo de 5)

# Archivo a cargar
elArchivo = 'bola8/makekos/' + symbol + '/' + symbol + '_' + estrategia+ '_strikes_' + timeHour + '.csv'


# Definir códigos ANSI para colores
RED = "\033[91m"
GREEN = "\033[92m"
RESET = "\033[0m"

# Función para aplicar color
def colorize(value):
    if isinstance(value, (int, float)):  # Asegurarse de que es un número
        color = GREEN if value >= 0 else RED
        return f"{color}{value}{RESET}"
    return value

def obtener_ultimo_underlying_price(archivo_option_chain):
    try:
        # Leer el archivo CSV
        option_chain_df = pd.read_csv(archivo_option_chain)

        # Asegurarse de que 'timestamp' sea de tipo datetime
        option_chain_df['timestamp'] = pd.to_datetime(option_chain_df['timestamp'])

        # Ordenar por timestamp para garantizar el último registro
        option_chain_df = option_chain_df.sort_values(by='timestamp')

        # Obtener el último precio del subyacente
        if 'underlying_price' in option_chain_df.columns:
            ultimo_underlying_price = option_chain_df['underlying_price'].iloc[-1]
            return ultimo_underlying_price
        else:
            return None

    except Exception as e:
        return None
    
# Función para calcular el crédito recibido y el precio subyacente desde un archivo CSV
def calcular_credito_y_underlying_iron_condor(archivo_option_chain, strikes, timeHour):
    """
    Calcula el crédito recibido para un iron condor y obtiene el precio subyacente.
    strikes: Diccionario con los 4 strikes (Call_Strike1, Call_Strike2, Put_Strike1, Put_Strike2).
    timeHour: Tiempo de la operación en formato datetime.
    """
    option_chain_df = pd.read_csv(archivo_option_chain)
    option_chain_df['timestamp'] = pd.to_datetime(option_chain_df['timestamp'])
    filtered_chain = option_chain_df[option_chain_df['timestamp'] >= timeHour]

    if filtered_chain.empty:
        return "No data found after the specified timeHour", None, None, None
    
    # Filtrar datos para los strikes
    strikes_data = filtered_chain[
        filtered_chain['strike'].isin([strikes['Call_Strike1'], strikes['Call_Strike2'], strikes['Put_Strike1'], strikes['Put_Strike2']])
    ]

    if strikes_data.empty:
        return "Strikes not found in the filtered data", None, None, None
    
    # Calcular los valores promedio (bid + ask) / 2 para cada strike
    strike_values = {}
    for key, strike in strikes.items():
        strike_row = strikes_data[strikes_data['strike'] == strike]
        if key.startswith('Call'):
            strike_values[key] = ((strike_row['bid_call'].iloc[0] + strike_row['ask_call'].iloc[0]) / 2) if not strike_row.empty else None
        elif key.startswith('Put'):
            strike_values[key] = ((strike_row['bid_put'].iloc[0] + strike_row['ask_put'].iloc[0]) / 2) if not strike_row.empty else None
    
    # 🚨 VALIDACIÓN:
    if any(v is None for v in strike_values.values()):
        return "Missing data for one or more strikes", None, None, None
    
    # Calcular el crédito recibido por calls y puts
    credit_calls = strike_values['Call_Strike1'] - strike_values['Call_Strike2']
    credit_puts = strike_values['Put_Strike1'] - strike_values['Put_Strike2']

    
    # Crédito total recibido
    credit_received = credit_calls + credit_puts
    
    # Obtener el precio subyacente
    underlying_price = strikes_data['underlying_price'].iloc[0]
    
    return credit_received, underlying_price, credit_calls, credit_puts

# Función para calcular ganancia o pérdida del día
def calcular_ganancia_perdida_dia(credit_result, underlying_price_16, strikes):
    """
    Calcula la ganancia o pérdida del día para un Iron Condor.
    """
    if underlying_price_16 is None or credit_result is None:
        return 'No calculable'

    # Calcular diferencias entre strikes
    diferencia_strikes_put = strikes['Put_Strike1'] - strikes['Put_Strike2']
    diferencia_strikes_call = strikes['Call_Strike2'] - strikes['Call_Strike1']

    # Caso 1: Precio completamente fuera del rango (pérdida máxima)
    if underlying_price_16 < strikes['Put_Strike2'] or underlying_price_16 > strikes['Call_Strike2']:
        return (credit_result - max(diferencia_strikes_put, diferencia_strikes_call)) * 100

    # Caso 2: Precio completamente dentro del rango (ganancia máxima)
    if strikes['Put_Strike1'] <= underlying_price_16 <= strikes['Call_Strike1']:
        return credit_result * 100

    # Caso 3: Precio parcialmente ITM en la pierna PUT
    if strikes['Put_Strike2'] <= underlying_price_16 < strikes['Put_Strike1']:
        perdida_put = strikes['Put_Strike1'] - underlying_price_16
        return (credit_result - min(perdida_put, diferencia_strikes_put)) * 100

    # Caso 4: Precio parcialmente ITM en la pierna CALL
    if strikes['Call_Strike1'] < underlying_price_16 <= strikes['Call_Strike2']:
        perdida_call = underlying_price_16 - strikes['Call_Strike1']
        return (credit_result - min(perdida_call, diferencia_strikes_call)) * 100

    return 'No calculable'

# Función principal para manejar iron condor con desplazamiento
def calcular_precios_iron_condor(vertical_strikes_df, path_csv_base, desplazamiento):

    resultados = []

    symbol= vertical_strikes_df['Symbol'].iloc[0]

    for index, row in vertical_strikes_df.iterrows():
        fecha = pd.to_datetime(row['Day']).strftime('%Y-%m-%d')
        hora = row['Hour']
        
        # Ajustar los strikes con el desplazamiento
        strikes = {
            'Call_Strike1': row['Call_Strike1'] + desplazamiento,
            'Call_Strike2': row['Call_Strike2'] + desplazamiento,
            'Put_Strike1': row['Put_Strike1'] - desplazamiento,
            'Put_Strike2': row['Put_Strike2'] - desplazamiento
        }
        
        # Formatear los strikes en el formato XXXX/XXXX XXXX/XXXX
        strikes_formateados = f"{strikes['Call_Strike1']}/{strikes['Call_Strike2']} {strikes['Put_Strike1']}/{strikes['Put_Strike2']}"
        
        specific_time = pd.to_datetime(f"{row['Day']} {hora}")

        if symbol in ["RUT", "XSP", "SPX"]:
            archivo_option_chain = path_csv_base + "chains/optionChain_$" + symbol + '_' + fecha + ".csv"
        else:
            archivo_option_chain = path_csv_base + "chains/optionChain_" + symbol + '_' + fecha + ".csv"

        try:
            credit_result, underlying_price, credit_calls, credit_puts = calcular_credito_y_underlying_iron_condor(archivo_option_chain, strikes, specific_time)
        except FileNotFoundError:
            credit_result = 'File not found'
            credit_calls, credit_puts = None, None
 
        # specific_time_16 = pd.to_datetime(f"{row['Day']} 16:00:00")

        try:
            # credit_result_16, underlying_price_16, _, _ = calcular_credito_y_underlying_iron_condor(archivo_option_chain, strikes, specific_time_16)
            underlying_price_16 = obtener_ultimo_underlying_price(archivo_option_chain)

        except FileNotFoundError:
            underlying_price_16 = None

    #    # 📌 FILTRO: Verificar si los valores son válidos antes de hacer `append`
    #     if (
    #         underlying_price is None or math.isnan(underlying_price) or
    #         credit_result is None or credit_result == "File not found" or math.isnan(credit_result) or
    #         credit_calls is None or credit_puts is None
    #     ):
    #         continue  # No agregar este día

        # Calcular ganancia o pérdida del día
        ganancia_dia = calcular_ganancia_perdida_dia(credit_result, underlying_price_16, strikes)

        resultados.append({
            'Day': fecha,
            'Time': hora,
            'Strikes': strikes_formateados,
            'Price': underlying_price,
            'Close': underlying_price_16,
            'Credit': f"$ {0.00:.2f}" if not isinstance(credit_result, (int, float)) or math.isnan(credit_result) else f"$ {credit_result * 100:.2f}",
            'Credit_Calls': credit_calls,
            'Credit_Puts': credit_puts,
            'P/L': ganancia_dia,
            'Dif': f"{abs((underlying_price or 0) - strikes['Call_Strike1']):.2f} / {abs((underlying_price or 0) - strikes['Put_Strike1']):.2f}"
        })

    
    resultados_df = pd.DataFrame(resultados)
    resultados_df['P/L'] = pd.to_numeric(resultados_df['P/L'], errors='coerce')
    resultados_df['P/L'] = resultados_df['P/L'].map(lambda x: f"{x:.2f}" if pd.notnull(x) else x)
    profit_total = resultados_df['P/L'].astype(float).sum()

    dias_profit_positivo = resultados_df[resultados_df['P/L'].astype(float) > 0].shape[0]
    dias_profit_negativo = resultados_df[resultados_df['P/L'].astype(float) < 0].shape[0]

    return resultados_df, profit_total, dias_profit_positivo, dias_profit_negativo

# Cargar archivo con strikes
vertical_strikes_df = pd.read_csv(PATH_UBUNTU + elArchivo)

# Convertir a datetime y filtrar por rango de fechas
vertical_strikes_df['Day'] = pd.to_datetime(vertical_strikes_df['Day'])

vertical_strikes_df = vertical_strikes_df[(vertical_strikes_df['Day'] >= desde) & (vertical_strikes_df['Day'] <= hasta)]

# Llamar a la función con el desplazamiento
resultados_iron_condor, profit_total, dias_profit_positivo, dias_profit_negativo = calcular_precios_iron_condor(
    vertical_strikes_df, PATH_UBUNTU, desplazamiento=DESPLAZAMIENTO
)


# *** Crear una copia para imprimir en consola con color ***
resultados_consola = resultados_iron_condor.copy()
resultados_consola['P/L'] = pd.to_numeric(resultados_consola['P/L'], errors='coerce')  # Convertir a numérico
resultados_consola['P/L'] = resultados_consola['P/L'].apply(lambda x: colorize(x) if pd.notnull(x) else "N/A")

# Calcular el número total de días
total_dias = dias_profit_positivo + dias_profit_negativo

# Calcular el Win Rate
if total_dias > 0:
    win_rate = (dias_profit_positivo / total_dias) * 100
else:
    win_rate = 0.0

# Mostrar los resultados con el Win Rate agregado
# print(resultados_iron_condor[['Day', 'Time', 'Strikes', 'Underlying_Price', 'Underlying_Price_16', 'Credit_Received', 'Credit_Calls', 'Credit_Puts', 'Ganancia_Dia', 'Diferencia_Absoluta']])
print(resultados_consola.to_string(index=False))

print(f"\nSímbolo: {symbol}")
print(f"Profit Total: $ {profit_total:.2f}")
print(f"Días con profit positivo: {dias_profit_positivo}")
print(f"Días con profit negativo: {dias_profit_negativo}")
print(f"Win Rate: {win_rate:.2f}%")
print(f"Desplazamiento: {DESPLAZAMIENTO}")
print(f"Estrategia: {estrategia}")


# # Guardar resultados
# resultados_iron_condor.to_csv('resultados_iron_condor.csv', index=False)
resultados_iron_condor.to_csv(f"resultados/P&L_{estrategia}_{symbol}_{timeHour}_{desde.replace('-', '')}_{hasta.replace('-', '')}_D{DESPLAZAMIENTO}.csv", index=False)

# Asegurar que 'Day' sea datetime y manejar errores
resultados_iron_condor['Day'] = pd.to_datetime(resultados_iron_condor['Day'], errors='coerce')

# Verificar si hay valores nulos después de la conversión
if resultados_iron_condor['Day'].isnull().any():
    print("Advertencia: Hay valores no válidos en la columna 'Day'. Se eliminarán.")
    resultados_iron_condor = resultados_iron_condor.dropna(subset=['Day'])

# Gráfico de barras por ganancias mensuales
resultados_iron_condor['P/L'] = pd.to_numeric(resultados_iron_condor['P/L'], errors='coerce')
resultados_iron_condor['Month'] = resultados_iron_condor['Day'].dt.to_period('M')

# Sumar ganancias por mes
ganancias_mensuales = resultados_iron_condor.groupby('Month')['P/L'].sum()

# Crear el gráfico de barras
plt.figure(figsize=(10, 6))
plt.bar(ganancias_mensuales.index.astype(str), ganancias_mensuales.values)
titulo_grafico = f"Profit & Loss - {estrategia} {symbol}\n{desde} a {hasta}, Time {timeHour}, D{DESPLAZAMIENTO}"

plt.title(titulo_grafico)
plt.xlabel('Mes')
plt.ylabel('Ganancia Total ($)')
plt.xticks(rotation=45)
plt.grid(axis='y', linestyle='--', alpha=0.7)

# Agregar leyenda con el profit total
profit_legend = f"Profit Total: ${profit_total:.2f}"
plt.legend([profit_legend], loc='upper left', fontsize=10, frameon=True)

plt.tight_layout()

# Guardar el gráfico como archivo
output_filename = f"graficos/P&L Mensual_{estrategia}_{symbol}_{timeHour}_{desde.replace('-', '')}_{hasta.replace('-', '')}_D{DESPLAZAMIENTO}.png"

output_path = output_filename
plt.savefig(output_path)
print(f"Gráfico guardado en: {output_path}")

# Crear gráfico de líneas para ganancias acumuladas
resultados_iron_condor['P/L'] = pd.to_numeric(resultados_iron_condor['P/L'], errors='coerce')
resultados_iron_condor['Ganancia_Acumulada'] = resultados_iron_condor['P/L'].cumsum()

plt.figure(figsize=(10, 6))
plt.plot(resultados_iron_condor['Day'], resultados_iron_condor['Ganancia_Acumulada'], linestyle='-')
titulo_linea = f"Profit & Loss - {estrategia} {symbol}\n{desde} a {hasta}, Time {timeHour}, D{DESPLAZAMIENTO}"

plt.title(titulo_linea)
plt.xlabel('Fecha')
plt.ylabel('Ganancia Acumulada ($)')
plt.xticks(rotation=45)
plt.grid(axis='y', linestyle='--', alpha=0.7)

# Agregar leyenda con el profit total
profit_legend_line = f"Profit Total: ${profit_total:.2f}"
plt.legend([profit_legend_line], loc='upper left', fontsize=10, frameon=True)

# Guardar el gráfico de líneas como archivo
output_filename_line = f"graficos/P&L Acumulado_{estrategia}_{symbol}_{timeHour}_{desde.replace('-', '')}_{hasta.replace('-', '')}_D{DESPLAZAMIENTO}.png"
output_path_line = output_filename_line
plt.savefig(output_path_line)
print(f"Gráfico de ganancias acumuladas guardado en: {output_path_line}")