# ==============================================
# GENERATE PREDICTION FAST FROM SNAPSHOTS
# ==============================================
from datetime import datetime
import os
import time
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import scipy.stats as si
import pytz
# Constantes
SMA_WINDOW = 5
MINUTES_TENDENCIA = 30


# Función principal para procesar snapshots rápidos
def generate_prediction_fast_from_snapshots(INPUT_CSV, OUTPUT_CSV):
    print(f"\n🚀 Cargando {INPUT_CSV}...")
    df_chain = pd.read_csv(INPUT_CSV, parse_dates=['timestamp'])
    df_chain.sort_values('timestamp', inplace=True)

    if df_chain.empty:
        print("⚠️ El archivo está vacío.")
        return

    # 1. Filtrar segundos entre 10 y 40
    df_chain['second'] = df_chain['timestamp'].dt.second
    df_filtered = df_chain[(df_chain['second'] >= 10) & (df_chain['second'] <= 50)].copy()
    df_filtered.drop(columns=['second'], inplace=True)

    if df_filtered.empty:
        print("⚠️ No se encontraron snapshots en segundos 10-40.")
        return

    # 2. Filtrar desde 9:35 NY en adelante
    df_filtered = df_filtered[df_filtered['timestamp'].dt.time >= datetime.strptime("09:35:00", "%H:%M:%S").time()]
    if df_filtered.empty:
        print("⚠️ No hay datos después de 09:35:00.")
        return

    # 3. Agrupar por minuto y quedarnos con el último snapshot de cada minuto
    df_filtered["minute"] = df_filtered["timestamp"].dt.floor("min")
    df_final = df_filtered.sort_values("timestamp").groupby("minute").tail(1).copy()

    unique_timestamps = df_final['timestamp'].drop_duplicates().sort_values()
    print(f"✅ Minutos únicos encontrados: {len(unique_timestamps)}")

    # 4. Procesar y guardar
    if os.path.exists(OUTPUT_CSV):
        os.remove(OUTPUT_CSV)

    for ts in unique_timestamps:
        try:
            df_snapshot = df_filtered[df_filtered['timestamp'] <= ts].copy()
            if df_snapshot.empty:
                continue
            process_single_snapshot(df_snapshot, ts, OUTPUT_CSV)
        except Exception as e:
            print(f"❌ Error procesando timestamp {ts}: {e}")


# Función para procesar un único snapshot
def process_single_snapshot(df_snapshot, real_last_timestamp, OUTPUT_CSV):
    df_snapshot = df_snapshot.copy()
    df_snapshot.set_index('timestamp', inplace=True)

    if len(df_snapshot) < SMA_WINDOW:
        return

    df_prepared = prepare_features(df_snapshot)
    tendencia_30min, score_30min = calculate_tendencia_30min(df_prepared, window_minutes=30)
    predicted_closes = predict_closing_prices(df_prepared)
    expected_move = calculate_expected_move(df_prepared)

    short_trend, total_pos, total_neg = calculate_short_term_trend(
        output_csv_path=OUTPUT_CSV,
        columns=["prediccion_OI_GEX", "prediccion_Delta_Neutro", "prediccion_Volumen", "punto_de_gravedad"],
        minutes=60,
        threshold=0.95,
        debug=False
    )

    latest_price = df_prepared['underlying_price'].iloc[-1]
    dominant_strikes_dict = get_dominant_strikes(df_prepared, latest_price)
    OI_dominantes = dominant_strikes_dict['oi']['strike'].tolist()
    GEX_dominantes = dominant_strikes_dict['gex']['strike'].tolist()
    VOL_dominantes = dominant_strikes_dict['volume']['strike'].tolist()

    gamma_state = predicted_closes["Gamma State"]
    gex_value = predicted_closes["GEX"]
    gamma_flip_strike = predicted_closes["Gamma Flip Level"]

    df_latest = df_prepared.loc[[df_prepared.index.max()]].copy()

    vertical_idea = get_vertical_idea(df_latest, tendencia_30min, 1, 0)
    idea_adjust_5 = get_vertical_idea(df_latest, tendencia_30min, 1, 1)
    idea_adjust_10 = get_vertical_idea(df_latest, tendencia_30min, 1, 2)
    idea_adjust_15 = get_vertical_idea(df_latest, tendencia_30min, 1, 3)

    ironCondor_idea = get_ironCondor_idea(df_latest, tendencia_30min, 1, 0)
    ironCondor_idea_adjust_5 = get_ironCondor_idea(df_latest, tendencia_30min, 1, 1)
    ironCondor_idea_adjust_10 = get_ironCondor_idea(df_latest, tendencia_30min, 1, 2)
    ironCondor_idea_adjust_15 = get_ironCondor_idea(df_latest, tendencia_30min, 1, 3)

    data_to_save = {
        "timestamp": real_last_timestamp.strftime('%Y-%m-%d %H:%M:%S'),
        "precio_actual": latest_price,
        "tendencia_30min": tendencia_30min,
        "tendencia_corta": short_trend,
        "total_pos": total_pos,
        "total_neg": total_neg,
        "score_30min": score_30min,
        "strikes_OI": OI_dominantes,
        "strikes_GEX": GEX_dominantes,
        "strikes_Volumen": VOL_dominantes,
        "prediccion_OI_GEX": predicted_closes['OI_GEX'],
        "prediccion_Delta_Neutro": predicted_closes['Delta Neutro'],
        "prediccion_Volumen": predicted_closes['Volumen'],
        "punto_de_gravedad": predicted_closes['Punto de Gravedad'],
        "movimiento_esperado": expected_move,
        "Gamma_Exposure": gex_value,
        "Gamma_State": gamma_state,
        "Gamma_Flip_Level": gamma_flip_strike,
        "IDEA": vertical_idea,
        "IDEA_adj5": idea_adjust_5,
        "IDEA_adj10": idea_adjust_10,
        "IDEA_adj15": idea_adjust_15,
        "IDEA_IC": ironCondor_idea,
        "IDEA_IC_adj5": ironCondor_idea_adjust_5,
        "IDEA_IC_adj10": ironCondor_idea_adjust_10,
        "IDEA_IC_adj15": ironCondor_idea_adjust_15
    }

    save_to_csv(data_to_save, OUTPUT_CSV)


def save_to_csv(data, output_csv):
    """Guarda los datos en un archivo CSV de manera acumulativa."""
    df = pd.DataFrame([data])
    if not os.path.exists(output_csv):
        df.to_csv(output_csv, index=False)
    else:
        df.to_csv(output_csv, mode='a', header=False, index=False)


def get_vertical_idea(df, tendencia, distance=5, adjust=0):
    """
    Genera una idea de vertical credit spread basada en la tendencia detectada:
    - Si tendencia es UP => se arma un PUT credit spread (ventas de PUT)
    - Si tendencia es DOWN => se arma un CALL credit spread (ventas de CALL)

    Parámetros:
    - df: dataframe preparado
    - tendencia: "UP" o "DOWN"
    - distance: separación entre strikes
    - adjust: ajuste de strikes hacia arriba o abajo

    Retorna un string con la operación o "NO DATA" si no puede construirla.
    """

    if tendencia is None:
        return "NO DATA"
    tendencia = str(tendencia).lower()

    if df.empty:
        return "NO DATA"

    last_ts = df.index.max()  # ✅ primero obtenemos la fecha
    expiry_str = last_ts.strftime("%d %b %y")

    last_ts = df.index.max()
    df_last = df.loc[[last_ts]].copy()

    if tendencia == "up":
        df_last['put_diff'] = (df_last['delta_put'] + 0.20).abs()
        df_sorted = df_last.sort_values('put_diff')

        if df_sorted.empty:
            return "NO DATA"

        short_strike = round(df_sorted.iloc[0]['strike'] + adjust)
        long_strike = short_strike - distance

        df_short = df_last[df_last['strike'] == short_strike]
        df_long = df_last[df_last['strike'] == long_strike]

        if df_short.empty or df_long.empty:
            print(f"[DEBUG] short_strike={short_strike}, long_strike={long_strike}")
            print(f"[DEBUG] df_short.empty={df_short.empty}, df_long.empty={df_long.empty}")
            print(f"[DEBUG] Strikes disponibles: {df_last['strike'].unique()}")
            return "NO DATA"

        short_mid = (df_short.iloc[0]['bid_put'] + df_short.iloc[0]['ask_put']) / 2
        long_mid = (df_long.iloc[0]['bid_put'] + df_long.iloc[0]['ask_put']) / 2

        net_credit = round(short_mid - long_mid, 2)

        return (f"SELL -1 Vertical {symbol.lstrip('$')} {expiry_str} "
                f"{int(short_strike)}/{int(long_strike)} PUT @{net_credit} LMT")

    elif tendencia == "down":
        df_last['call_diff'] = (df_last['delta_call'] - 0.20).abs()
        df_sorted = df_last.sort_values('call_diff')

        if df_sorted.empty:
            return "NO DATA"

        short_strike = round(df_sorted.iloc[0]['strike'] - adjust)
        long_strike = short_strike + distance

        df_short = df_last[df_last['strike'] == short_strike]
        df_long = df_last[df_last['strike'] == long_strike]

        if df_short.empty or df_long.empty:
            return "NO DATA"

        short_mid = (df_short.iloc[0]['bid_call'] + df_short.iloc[0]['ask_call']) / 2
        long_mid = (df_long.iloc[0]['bid_call'] + df_long.iloc[0]['ask_call']) / 2

        net_credit = round(short_mid - long_mid, 2)

        return (f"SELL -1 Vertical {symbol.lstrip('$')} {expiry_str} "
                f"{int(short_strike)}/{int(long_strike)} CALL @{net_credit} LMT")

    else:
        return "NO DATA"


def get_ironCondor_idea(df, tendencia, distance=5, adjust=0):

    if tendencia is None:
        return "NO DATA"
    tendencia = str(tendencia).lower()

    if df.empty:
        return "NO DATA"

    last_ts = df.index.max()  # ✅ primero obtenemos la fecha
    expiry_str = last_ts.strftime("%d %b %y")

    if tendencia == "up":
        call_target = 0.10
        put_target = -0.12
    elif tendencia == "down":
        call_target = 0.12
        put_target = -0.10
    else:
        return "NO DATA"

    last_ts = df.index.max()
    df_last = df.loc[[last_ts]].copy()

    df_last['call_diff'] = (df_last['delta_call'] - call_target).abs()
    df_last['put_diff'] = (df_last['delta_put'] - put_target).abs()

    df_call_sorted = df_last.sort_values('call_diff')
    df_put_sorted = df_last.sort_values('put_diff')

    if df_call_sorted.empty or df_put_sorted.empty:
        return "NO DATA"


    # short_call_strike = df_call_sorted.iloc[0]['strike'] - adjust
    short_call_strike = round(df_call_sorted.iloc[0]['strike'] - adjust, 0)
    long_call_strike = short_call_strike + distance

    # short_put_strike = df_put_sorted.iloc[0]['strike'] + adjust
    short_put_strike = round(df_put_sorted.iloc[0]['strike'] + adjust, 0)
    long_put_strike = short_put_strike - distance

    df_short_call = df_last[df_last['strike'] == short_call_strike]
    df_long_call = df_last[df_last['strike'] == long_call_strike]
    df_short_put = df_last[df_last['strike'] == short_put_strike]
    df_long_put = df_last[df_last['strike'] == long_put_strike]

    if df_short_call.empty or df_long_call.empty or df_short_put.empty or df_long_put.empty:
        return "NO DATA"

    short_call_mid = (df_short_call.iloc[0]['bid_call'] + df_short_call.iloc[0]['ask_call']) / 2
    long_call_mid = (df_long_call.iloc[0]['bid_call'] + df_long_call.iloc[0]['ask_call']) / 2
    short_put_mid = (df_short_put.iloc[0]['bid_put'] + df_short_put.iloc[0]['ask_put']) / 2
    long_put_mid = (df_long_put.iloc[0]['bid_put'] + df_long_put.iloc[0]['ask_put']) / 2

    net_credit = round((short_call_mid - long_call_mid) + (short_put_mid - long_put_mid), 2)

    return (f"SELL -1 IRON CONDOR {symbol.lstrip('$')} {expiry_str} "
            f"[CALLS {int(short_call_strike)}/{int(long_call_strike)}] + "
            f"[PUTS {int(short_put_strike)}/{int(long_put_strike)}] @{net_credit} LMT")


def prepare_features(df):
    """
    Ejemplo de preparación de datos:
    - Cálculo de gex_change y rolling media.
    - Ajusta o amplía según tu necesidad real.
    """
    df['gex_change'] = df.groupby('strike')['gex_per_strike'].diff()
    df['gex_change_smooth'] = df['gex_change'].rolling(window=SMA_WINDOW).mean()
    df['price_sma'] = df['underlying_price'].rolling(window=SMA_WINDOW).mean()
    df.dropna(inplace=True)
    return df


def calculate_tendencia_30min(df, window_minutes=30, k=0.5):
    """
    Determina una tendencia (UP / DOWN) y su score en base a
    precio, OI, volumen y GEX en los últimos window_minutes.

    Además, ajusta el score usando un offset dinámico derivado del imbalance de OI (NOII),
    con un factor k calibrado a partir de datos históricos.

    Si no hay suficientes datos (ej. primeros 30 min del mercado), retorna "NO DATA".

    Retorna:
    --------
    tendencia : str  ("UP", "DOWN", o "NO DATA")
    score     : float (valor del score, o 0.0 si no hay datos)
    """
    if df.empty:
        return "NO DATA", 0.0

    # 1) Filtrar ventana de los últimos window_minutes
    last_timestamp = df.index.max()
    start_time = last_timestamp - pd.Timedelta(minutes=window_minutes)
    df_30 = df.loc[df.index >= start_time].copy()

    # 2) Si no hay suficientes datos en la ventana, retorna "NO DATA"
    if len(df_30) < 2:
        return "NO DATA", 0.0

    # 3) Cálculo de priceTrend (variación relativa del precio)
    first_price = df_30['underlying_price'].iloc[0]
    last_price = df_30['underlying_price'].iloc[-1]
    priceTrend = (last_price - first_price) / first_price if first_price != 0 else 0

    # 4) Open Interest Diff (CALL - PUT) normalizado
    sum_OI_call = df_30['open_interest_call'].sum()
    sum_OI_put = df_30['open_interest_put'].sum()
    total_OI = abs(sum_OI_call) + abs(sum_OI_put)
    OI_diff = (sum_OI_call - sum_OI_put) / total_OI if total_OI != 0 else 0

    # 5) Volumen Diff (CALL - PUT) normalizado
    sum_vol_call = df_30['volume_call'].sum()
    sum_vol_put = df_30['volume_put'].sum()
    total_vol = abs(sum_vol_call) + abs(sum_vol_put)
    Vol_diff = (sum_vol_call - sum_vol_put) / total_vol if total_vol != 0 else 0

    # 6) GEX Diff (CALL - PUT) normalizado
    df_30['call_gex'] = df_30['gamma_call'] * df_30['open_interest_call'] * 100
    df_30['put_gex'] = df_30['gamma_put'] * df_30['open_interest_put'] * 100
    sum_call_gex = df_30['call_gex'].sum()
    sum_put_gex = df_30['put_gex'].sum()
    total_gex = abs(sum_call_gex) + abs(sum_put_gex)
    GEX_diff = (sum_call_gex - sum_put_gex) / total_gex if total_gex != 0 else 0

    # 7) Calcular score ponderado sin offset
    w_price = 0.25
    w_oi = 0.25
    w_vol = 0.25
    w_gex = 0.25

    raw_score = (
        w_price * priceTrend +
        w_oi * OI_diff +
        w_vol * Vol_diff +
        w_gex * GEX_diff
    )

    # 8) Aplicar factor de amortiguamiento: si la ventana no está completa, se reduce el score
    actual_window = (df_30.index[-1] - df_30.index[0]).total_seconds() / 60.0
    damping_factor = actual_window / window_minutes if actual_window < window_minutes else 1.0
    adjusted_score = raw_score * damping_factor

    # 9) Calcular el offset dinámico basado en el imbalance de OI (NOII)
    # NOII ya es OI_diff; usamos k para calibrar
    offset = - k * OI_diff  # Si OI_diff es positivo (más calls), offset negativo; en días bajistas OI_diff podría ser negativo, ajustando según sea necesario.

    # Nota: Si históricamente en días bajistas OI_diff tiende a estar en un rango específico,
    # puedes calibrar k para que offset ≈ -0.1 cuando se requiera.
    final_score = adjusted_score + offset

    # 10) Determinar la tendencia final
    if final_score > 0:
        return "UP", final_score
    else:
        return "DOWN", final_score


def predict_closing_prices(df):
    """Calcula varias predicciones de cierre, punto de gravedad y Gamma Exposure."""
    gex_value, gamma_state, gamma_flip_strike = calculate_gex(df)  # Solo pasamos df

    return {
        "OI_GEX": predict_close_oi_gex(df),
        "Delta Neutro": predict_close_delta_neutral(df),
        "Volumen": predict_close_volume(df),
        "Punto de Gravedad": calculate_market_gravity(df),
        "GEX": gex_value,
        "Gamma State": gamma_state,
        "Gamma Flip Level": gamma_flip_strike
    }


def get_ny_time():
    ny_tz = pytz.timezone('America/New_York')
    return datetime.now(ny_tz).strftime('%Y-%m-%d %H:%M:%S')


def is_market_open():
    """Verifica si el mercado está abierto entre 9:35 AM y 16:01 PM (hora NY)."""
    ny_time_str = get_ny_time()
    ny_time = datetime.strptime(ny_time_str, '%Y-%m-%d %H:%M:%S')
    market_open = ny_time.replace(hour=9, minute=35, second=0, microsecond=0)
    market_close = ny_time.replace(hour=16, minute=1, second=0, microsecond=0)
    return market_open <= ny_time <= market_close


def load_data(file_path):
    """Carga los datos del archivo CSV en un DataFrame."""
    df = pd.read_csv(file_path, parse_dates=['timestamp'])
    df.sort_values(by=['timestamp'], inplace=True)
    df.set_index('timestamp', inplace=True)
    return df


def save_to_csv(data, output_csv):
    """Guarda los datos en un archivo CSV de manera acumulativa."""
    df = pd.DataFrame([data])
    if not os.path.exists(output_csv):
        df.to_csv(output_csv, index=False)
    else:
        df.to_csv(output_csv, mode='a', header=False, index=False)


def calculate_gex(df):
    """Calcula la Gamma Exposure total y el Gamma Flip Level con detección precisa del cambio de signo."""

    # 🔍 Restaurar 'timestamp' si está como índice
    if 'timestamp' not in df.columns and df.index.name == 'timestamp':
        df = df.reset_index()

    if 'timestamp' not in df.columns:
        raise ValueError("❌ ERROR: La columna 'timestamp' no está en el DataFrame.")

    # 1️⃣ Convertir 'timestamp' a datetime
    df["timestamp"] = pd.to_datetime(df["timestamp"], format="%m/%d/%Y %I:%M:%S %p", errors="coerce")

    # 2️⃣ Filtrar solo el último timestamp disponible
    latest_timestamp = df["timestamp"].max()
    df_latest = df[df["timestamp"] == latest_timestamp].copy()

    # print(f"✅ Último timestamp disponible: {latest_timestamp}")  # DEBUG

    # 3️⃣ Obtener el último precio subyacente
    spot_price = df_latest["underlying_price"].iloc[0]

    # 4️⃣ Filtrar solo los strikes dentro de un rango ±20 puntos del SPX para mayor precisión
    df_latest = df_latest[(df_latest["strike"] >= spot_price - 20) & (df_latest["strike"] <= spot_price + 20)].copy()

    # 5️⃣ Calcular GEX por strike
    df_latest["gex_call"] = df_latest["gamma_call"] * df_latest["open_interest_call"] * df_latest["underlying_price"]
    df_latest["gex_put"] = df_latest["gamma_put"] * df_latest["open_interest_put"] * df_latest["underlying_price"]

    df_latest["gex_total"] = df_latest["gex_call"] + df_latest["gex_put"]

    # 6️⃣ Ordenar por strike y calcular GEX acumulado
    df_sorted = df_latest.sort_values(by="strike")
    df_sorted["gex_cumsum"] = df_sorted["gex_total"].cumsum()

    # 7️⃣ Aplicar media móvil de 3 strikes para suavizar `gex_cumsum`
    df_sorted["gex_smooth"] = df_sorted["gex_cumsum"].rolling(window=3, center=True, min_periods=1).mean()

    # 8️⃣ Encontrar el Gamma Flip Level como el primer strike donde `gex_smooth` cambia de signo
    gamma_flip_strike = None
    prev_gex = None

    for idx, row in df_sorted.iterrows():
        current_gex = row["gex_smooth"]

        if prev_gex is not None:
            if prev_gex * current_gex < 0:  # Cambio de signo detectado
                gamma_flip_strike = row["strike"]
                break
        prev_gex = current_gex

    # 🔍 Si no encontramos un cambio de signo, tomar el strike más cercano a `gex_smooth = 0`
    if gamma_flip_strike is None:
        min_gex_index = df_sorted["gex_smooth"].abs().idxmin()
        gamma_flip_strike = df_sorted.loc[min_gex_index, "strike"]

    # 🔟 Calcular el Gamma Exposure total
    gex_total = df_latest["gex_total"].sum()

    # 🔟 Determinar si la Gamma es Positiva o Negativa
    gamma_state = "Positiva" if gex_total > 0 else "Negativa"

    # print(f"📊 🎯 Gamma Flip Level Detectado: {gamma_flip_strike}")  # Log importante para validar

    return round(gex_total, 2), gamma_state, gamma_flip_strike


# ------------------------------------------------
# BLACK-SCHOLES PARA MOVIMIENTO ESPERADO
# ------------------------------------------------
def black_scholes(S, K, T, r, sigma, option_type="call"):
    d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)
    if option_type == "call":
        price = S * si.norm.cdf(d1) - K * np.exp(-r * T) * si.norm.cdf(d2)
    else:
        price = K * np.exp(-r * T) * si.norm.cdf(-d2) - S * si.norm.cdf(-d1)
    return price


def calculate_expected_move(df):

    if 'timestamp' not in df.columns and df.index.name == 'timestamp':
        df = df.reset_index()
    if 'timestamp' not in df.columns:
        print("Error: Falta columna 'timestamp'.")
        return None

    df['timestamp'] = pd.to_datetime(df['timestamp'], errors='coerce')
    df = df.dropna(subset=['timestamp'])
    if df.empty:
        return None

    latest_timestamp = df['timestamp'].max()
    df_latest = df[df['timestamp'] == latest_timestamp]
    if df_latest.empty:
        return None

    underlying_price = df_latest['underlying_price'].iloc[0]
    # Strike más cercano al precio actual (ATM)
    atm_strike = df_latest.iloc[(df_latest['strike'] - underlying_price).abs().argsort()[:1]]
    if atm_strike.empty:
        return None

    bid_call = atm_strike['bid_call'].values[0]
    ask_call = atm_strike['ask_call'].values[0]
    bid_put = atm_strike['bid_put'].values[0]
    ask_put = atm_strike['ask_put'].values[0]

    call_price = (bid_call + ask_call) / 2
    put_price = (bid_put + ask_put) / 2
    straddle_price = call_price + put_price

    T = 1 / 252  # un día
    r = 0.05

    if 'iv_call' in df_latest.columns and 'iv_put' in df_latest.columns:
        iv_atm = (df_latest['iv_call'].values[0] + df_latest['iv_put'].values[0]) / 2
    else:
        iv_atm = straddle_price / (underlying_price * np.sqrt(T))
    iv_atm = max(0.05, min(iv_atm, 0.5))
    expected_move = straddle_price * (1 + iv_atm)
    return round(expected_move, 2)


def calculate_short_term_trend(
        output_csv_path,
        columns=["prediccion_OI_GEX", "prediccion_Delta_Neutro",
                 "prediccion_Volumen", "punto_de_gravedad"],
        minutes=60,
        threshold=0.95,
        debug=False):
    """
    Lee el CSV acumulado (OUTPUT_ONDEMAND_CSV) con una fila por minuto
    y las columnas de predicción. Retorna (trend, total_pos, total_neg)
    donde trend es "UP", "DOWN", "FLAT" o "NO DATA".
    """

    import pandas as pd

    # 1) Cargar CSV
    try:
        df = pd.read_csv(output_csv_path, parse_dates=["timestamp"])
    except FileNotFoundError:
        if debug:
            print(f"[DEBUG] No se encontró el archivo: {output_csv_path}")
        return ("NO DATA", 0, 0)  # <-- 3 elementos

    if df.empty:
        if debug:
            print("[DEBUG] El CSV está vacío => NO DATA")
        return ("NO DATA", 0, 0)  # <-- 3 elementos

    df.sort_values(by="timestamp", inplace=True)
    df.set_index("timestamp", inplace=True)

    if not pd.api.types.is_datetime64_any_dtype(df.index):
        if debug:
            print("[DEBUG] Índice no es datetime => NO DATA")
        return ("NO DATA", 0, 0)  # <-- 3 elementos

    # 2) Filtrar los últimos `minutes` de datos
    last_timestamp = df.index.max()
    start_time = last_timestamp - pd.Timedelta(minutes=minutes)
    df_window = df.loc[df.index >= start_time].copy()

    if len(df_window) < 2:
        if debug:
            print(f"[DEBUG] Menos de 2 filas en los últimos {minutes} min => NO DATA")
        return ("NO DATA", 0, 0)  # <-- 3 elementos

    if debug:
        print("[DEBUG] df_window shape:", df_window.shape)
        print("[DEBUG] df_window columns:", df_window.columns)

    # 3) Contar diffs
    total_pos = 0
    total_neg = 0

    for col in columns:
        if col not in df_window.columns:
            if debug:
                print(f"[DEBUG] Columna '{col}' no existe => se ignora.")
            continue

        series = df_window[col].dropna()
        if len(series) < 2:
            if debug:
                print(f"[DEBUG] Columna '{col}' con <2 datos => se ignora.")
            continue

        diffs = series.diff().dropna()
        col_pos = (diffs > 0).sum()
        col_neg = (diffs < 0).sum()
        total_pos += col_pos
        total_neg += col_neg

        if debug:
            print(f"[DEBUG] {col}: diffs={len(diffs)}, +={col_pos}, -={col_neg}")

    total_changes = total_pos + total_neg

    if total_changes == 0:
        if debug:
            print("[DEBUG] total_changes=0 => todos diffs=0 => FLAT")
        return ("FLAT", total_pos, total_neg)  # <-- 3 elementos

    pos_ratio = total_pos / total_changes
    neg_ratio = total_neg / total_changes

    if debug:
        print(f"[DEBUG] total_pos={total_pos}, total_neg={total_neg}, "
              f"pos_ratio={pos_ratio:.2f}, neg_ratio={neg_ratio:.2f}")

    # 4) Determinar la tendencia
    if pos_ratio > threshold:
        return ("UP", total_pos, total_neg)
    elif neg_ratio > threshold:
        return ("DOWN", total_pos, total_neg)
    else:
        return ("FLAT", total_pos, total_neg)


def predict_close_oi_gex(df):
    df["oi_gex_weight"] = df["open_interest_call"] + df["open_interest_put"] + df["gex_per_strike"]
    total_ = df["oi_gex_weight"].sum()
    if total_ == 0:
        return 0
    predicted_close = (df["strike"] * df["oi_gex_weight"]).sum() / total_
    return round(predicted_close, 2)


def predict_close_delta_neutral(df):
    df["delta_total"] = df["delta_call"] + df["delta_put"]
    df["delta_abs"] = df["delta_total"].abs()

    total_ = df["delta_abs"].sum()
    if total_ == 0:
        return 0
    predicted_close = (df["strike"] * df["delta_abs"]).sum() / total_
    return round(predicted_close, 2)


def predict_close_volume(df):
    df["total_volume"] = df["volume_call"] + df["volume_put"]
    total_ = df["total_volume"].sum()
    if total_ == 0:
        return 0
    predicted_close = (df["strike"] * df["total_volume"]).sum() / total_
    return round(predicted_close, 2)


def calculate_market_gravity(df):
    df["F_put"] = df["open_interest_put"] * df["gamma_put"] * df["delta_put"] * df["volume_put"]
    df["F_call"] = df["open_interest_call"] * df["gamma_call"] * df["delta_call"] * df["volume_call"]
    df["F_total"] = df["F_call"] - df["F_put"]
    denom = df["F_total"].abs().sum()
    if denom == 0:
        return 0
    P_gravity = (df["strike"] * df["F_total"]).sum() / denom
    return round(P_gravity, 2)


def get_dominant_strikes(df, underlying_price):
    """
    Determina los strikes con mayor Open Interest, GEX y Volumen
    usando el snapshot completo más reciente (sin filtrar por STRIKE_RANGE).
    Retorna un dict con dataframes que tienen los top 2 strikes para:
       'oi', 'gex', y 'volume'
    """

    # Tomamos el snapshot completo más reciente
    latest_timestamp = df.index.max()
    df_latest = df.loc[latest_timestamp]

    # Cálculo de métricas (SIN filtrar por rango)
    df_latest.loc[:, 'total_open_interest'] = df_latest['open_interest_call'] + df_latest['open_interest_put']
    df_latest.loc[:, 'total_volume'] = df_latest['volume_call'] + df_latest['volume_put']

    # Top 2 strikes por OI
    top_oi_strikes = df_latest[['strike', 'total_open_interest']].nlargest(2, 'total_open_interest')
    # Top 2 strikes por GEX
    top_gex_strikes = df_latest[['strike', 'gex_per_strike']].nlargest(2, 'gex_per_strike')
    # Top 2 strikes por Volumen
    top_volume_strikes = df_latest[['strike', 'total_volume']].nlargest(2, 'total_volume')

    return {
        'oi': top_oi_strikes,
        'gex': top_gex_strikes,
        'volume': top_volume_strikes
    }


# # ⚙️ Define la lista de fechas que quieres procesar
# fechas = [
#     "2025-02-03", "2025-02-04", "2025-02-05", "2025-02-06", "2025-02-07",
#     "2025-02-10", "2025-02-11", "2025-02-12", "2025-02-13", "2025-02-14",
#     "2025-02-18", "2025-02-19", "2025-02-20", "2025-02-21",
#     "2025-02-24", "2025-02-25", "2025-02-26", "2025-02-27", "2025-02-28",
#     "2025-03-03", "2025-03-04", "2025-03-05", "2025-03-06", "2025-03-07",
#     "2025-03-10", "2025-03-11", "2025-03-12", "2025-03-13", "2025-03-14",
#     "2025-03-17", "2025-03-18", "2025-03-19", "2025-03-20", "2025-03-21",
#     "2025-03-24", "2025-03-25", "2025-03-26", "2025-03-27", "2025-03-28",
#     "2025-03-31",
#     "2025-04-01", "2025-04-02", "2025-04-03", "2025-04-04",
#     "2025-04-07", "2025-04-08", "2025-04-09", "2025-04-10", "2025-04-11",
#     "2025-04-14", "2025-04-15", "2025-04-16", "2025-04-17", "2025-04-18",
#     "2025-04-21", "2025-04-22", "2025-04-23", "2025-04-24", "2025-04-25",
#     "2025-04-28", "2025-04-29", "2025-04-30", "2025-05-01", "2025-05-02",
#     "2025-05-05", "2025-05-06", "2025-05-07", "2025-05-08", "2025-05-09",
#     "2025-05-12", "2025-05-13", "2025-05-14"
# ]

fechas = ["2025-08-11"]

# fechas = [
#     "2025-05-01", "2025-05-02",
#     "2025-05-05", "2025-05-06", "2025-05-07", "2025-05-08", "2025-05-09",
#     "2025-05-12", "2025-05-13"
# ]
PATH_UBUNTU = "/var/www/html"
symbol = "SPY"

# Función principal para procesar cada fecha

def procesar_fechas(fechas):
    for fecha in fechas:
        input_csv = f"{PATH_UBUNTU}/flask_project/chains/optionChain_{symbol}_{fecha}.csv"
        output_csv = f"{PATH_UBUNTU}/backtestingmarket/predictor_data/data2/prediction_{symbol}_{fecha}_FAST.csv"

        print(f"\n🔎 Procesando fecha: {fecha}")

        if not os.path.exists(input_csv):
            print(f"⚠️ Archivo no encontrado: {input_csv} (se salta)")
            continue

        try:
            generate_prediction_fast_from_snapshots(input_csv, output_csv)

            if os.path.exists(output_csv):
                df_resultado = pd.read_csv(output_csv)
                if df_resultado.empty:
                    print(f"⚠️ El archivo {output_csv} se creó pero está vacío.")
                else:
                    print(f"✅ Archivo {output_csv} creado con {len(df_resultado)} registros.")
            else:
                print(f"❌ No se generó el archivo {output_csv}")

        except Exception as e:
            print(f"❌ Error procesando {fecha}: {e}")


# ==========================================
# Ejecutar
procesar_fechas(fechas)
