import requests
import json
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
import sys
# nohup /bin/python3 -u /var/www/html/predictor.backtestingmarket/getPredictions_SPX.py > getPredictions_SPX.log 2>&1 &


# ========================================
# CONFIGURACIÓN POR SÍMBOLO
# ========================================
SYMBOL_CONFIG = {
    "$SPX": {"row_symbol": "SPX", "distance": 5, "adjust": [0, 5, 10, 15]},
    "$RUT": {"row_symbol": "RUT", "distance": 5, "adjust": [0, 5, 10, 15]},
    "QQQ": {"row_symbol": "QQQ", "distance": 1, "adjust": [0, 1, 2, 3]},
    "SPY": {"row_symbol": "SPY", "distance": 1, "adjust": [0, 1, 2, 3]},
    "$XSP": {"row_symbol": "XSP", "distance": 1, "adjust": [0, 1, 2, 3]},
}

# ========================================
# PARSE ARGUMENTOS
# ========================================
if len(sys.argv) < 2:
    print("Uso: python3 getPredictions.py <SYMBOL>")
    sys.exit(1)

SYMBOL = sys.argv[1]
if SYMBOL not in SYMBOL_CONFIG:
    print(f"❌ Configuración no encontrada para símbolo: {SYMBOL}")
    sys.exit(1)


# ------------------------------------------------
# CONFIGURACIÓN GLOBAL
# ------------------------------------------------
config = SYMBOL_CONFIG[SYMBOL]
ROW_SYMBOL = config["row_symbol"]
DISTANCE = config["distance"]
ADJUST = config["adjust"]


# SYMBOL = "$SPX"
# ROW_SYMBOL = "SPX"
# DISTANCE = 5
# ADJUST = [0, 5, 10, 15]

fecha_actual = datetime.today().strftime('%Y-%m-%d')

PATH_UBUNTU = "/var/www/html"
INPUT_CSV = f"{PATH_UBUNTU}/flask_project/chains/optionChain_{SYMBOL}_{fecha_actual}.csv"
OUTPUT_CSV = f"{PATH_UBUNTU}/backtestingmarket/predictor_data/data/{ROW_SYMBOL}/prediction_{SYMBOL}_{fecha_actual}.csv"

TIME_INTERVAL = '1min'
SMA_WINDOW = 5
MINUTES_TENDENCIA = 30

# ------------------------------------------------
# FUNCIONES AUXILIARES
# ------------------------------------------------


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)

# ------------------------------------------------
# PREPARACIÓN DE FEATURES (OPCIONAL)
# ------------------------------------------------


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

# ------------------------------------------------
# STRIKES DOMINANTES (OI, GEX, VOL)
# ------------------------------------------------


def get_dominant_strikes(df, underlying_price):


    # 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['total_open_interest'] = df_latest['open_interest_call'] + df_latest['open_interest_put']
    df_latest['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
    }

# ------------------------------------------------
# TENDENCIA AVANZADA + SCORE
# ------------------------------------------------


def calculate_tendencia_30min(df, window_minutes=15, 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


# ------------------------------------------------
# PREDICCIÓN DE CIERRES (VARIOS MÉTODOS)
# ------------------------------------------------
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 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 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()

    # 🔍 Depuración para ver cambios en gex_smooth
    print("📊 Depuración - Primeros valores de `gex_smooth`:")
    print(df_sorted[["strike", "gex_smooth"]].head(10))

    print("📊 Depuración - Últimos valores de `gex_smooth`:")
    print(df_sorted[["strike", "gex_smooth"]].tail(10))

    # 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 get_ironCondor_idea(df, tendencia, distance=5, adjust=0):
    """
    Genera UNA idea de Iron Condor, devolviendo "NO DATA: <razon>" si falla algo.

    - Si la tendencia es 'UP':
        call_delta ~ +0.10
        put_delta  ~ -0.12
    - Si la tendencia es 'DOWN':
        call_delta ~ +0.12
        put_delta  ~ -0.10

    Ajusta los strikes cortos con 'adjust' y define las patas largas
    a ±distance del strike corto. Retorna el string de la operación
    o "NO DATA: <razon>" si no se pudo construir el Iron Condor.
    """

    # 1) Validar DataFrame
    if df.empty:
        return "NO DATA: DataFrame vacío"

    # 2) Filtrar por último timestamp
    last_ts = df.index.max()
    df_last = df.loc[[last_ts]].copy()
    if df_last.empty:
        return f"NO DATA: No hay filas con timestamp={last_ts}"

    # 3) Ajustar deltas según tendencia
    tendencia_lower = tendencia.lower()
    if tendencia_lower == "up":
        call_target = 0.10
        put_target = -0.12
    elif tendencia_lower == "down":
        call_target = 0.12
        put_target = -0.10
    else:
        return f"NO DATA: Tendencia desconocida => {tendencia}"

    # 4) Calcular diffs y extraer la mejor fila para CALL y PUT
    df_last["call_diff"] = (df_last["delta_call"] - call_target).abs()
    df_last["put_diff"] = (df_last["delta_put"] - put_target).abs()

    df_calls_sorted = df_last.sort_values("call_diff")
    if df_calls_sorted.empty:
        return "NO DATA: No se encontraron opciones para el lado CALL"

    row_call_short = df_calls_sorted.iloc[0]

    df_puts_sorted = df_last.sort_values("put_diff")
    if df_puts_sorted.empty:
        return "NO DATA: No se encontraron opciones para el lado PUT"

    row_put_short = df_puts_sorted.iloc[0]

    expiry_str = datetime.now().strftime("%d %b %y")

    # =========================================================
    # LADO CALL
    # =========================================================
    short_call_strike = row_call_short["strike"] - adjust
    long_call_strike = short_call_strike + distance

    df_short_call = df_last[df_last["strike"] == short_call_strike]
    if df_short_call.empty:
        return f"NO DATA: Strike CALL corto ({short_call_strike}) no existe"

    df_long_call = df_last[df_last["strike"] == long_call_strike]
    if df_long_call.empty:
        return f"NO DATA: Strike CALL largo ({long_call_strike}) no existe"

    row_short_call = df_short_call.iloc[0]
    row_long_call = df_long_call.iloc[0]

    short_call_mid = (row_short_call["bid_call"] + row_short_call["ask_call"]) / 2
    long_call_mid = (row_long_call["bid_call"] + row_long_call["ask_call"]) / 2
    net_credit_call = short_call_mid - long_call_mid

    # =========================================================
    # LADO PUT
    # =========================================================
    short_put_strike = row_put_short["strike"] + adjust
    long_put_strike = short_put_strike - distance

    df_short_put = df_last[df_last["strike"] == short_put_strike]
    if df_short_put.empty:
        return f"NO DATA: Strike PUT corto ({short_put_strike}) no existe"

    df_long_put = df_last[df_last["strike"] == long_put_strike]
    if df_long_put.empty:
        return f"NO DATA: Strike PUT largo ({long_put_strike}) no existe"

    row_short_put = df_short_put.iloc[0]
    row_long_put = df_long_put.iloc[0]

    short_put_mid = (row_short_put["bid_put"] + row_short_put["ask_put"]) / 2
    long_put_mid = (row_long_put["bid_put"] + row_long_put["ask_put"]) / 2
    net_credit_put = short_put_mid - long_put_mid

    # =========================================================
    # PRIMA NETA TOTAL
    # =========================================================
    net_credit = round(net_credit_call + net_credit_put, 2)

    # Retorno final
    return (
        f"SELL -1 IRON CONDOR {ROW_SYMBOL} {expiry_str} "
        f"[CALLS {int(short_call_strike)}/{int(long_call_strike)}] + "
        f"[PUTS {int(short_put_strike)}/{int(long_put_strike)}] @"
        f"{net_credit} LMT"
    )


def get_vertical_idea(df, trend, distance=5, adjust=0):
    """
    Genera la 'Idea' de un vertical credit spread buscando delta ~ ±0.20,
    ordenando por la diferencia (call_diff / put_diff) en lugar de usar idxmin().

    Pasos:
    1) Toma el último timestamp (df.index.max()) => df_last
    2) Según trend:
       - DOWN => call_diff = |delta_call - 0.20|
       -  UP  => put_diff  = |delta_put  + 0.20|
       Ordena por esa diferencia de menor a mayor y toma la 1ra fila (iloc[0]).
    3) Determina la pata de compra (long_strike) a +/- distance.
    4) Calcula la prima neta (mid short - mid long).
    5) Retorna un string con la operación o "NO DATA" si algo falla.
    """

    if df.empty:
        print("[DEBUG] DataFrame vacío => NO DATA")
        return "NO DATA"

    # 1) Último timestamp en el índice
    last_ts = df.index.max()
    df_last = df.loc[[last_ts]].copy()
    if df_last.empty:
        print(f"[DEBUG] No hay filas con timestamp={last_ts}")
        return "NO DATA"

    expiry_str = datetime.now().strftime("%d %b %y")

    # ============================================================
    # CASO TENDENCIA 'DOWN': CALL CREDIT SPREAD (delta_call ~ +0.20)
    # ============================================================
    if trend.upper() == "DOWN":
        # Calculamos la diferencia para delta_call y ordenamos
        df_last["call_diff"] = (df_last["delta_call"] - 0.20).abs()
        df_sorted = df_last.sort_values("call_diff")
        # Seleccionamos el row con delta_call más cercano a +0.20 (sin ajuste)
        row_short = df_sorted.iloc[0]

        # Ajustamos el short_strike: restamos el adjust
        short_strike = row_short["strike"] - adjust
        # El long_strike se define a partir del short_strike ajustado (suma distance)
        long_strike = short_strike + distance

        # Buscamos el row correspondiente al short_strike ajustado
        df_short_adjusted = df_last[df_last["strike"] == short_strike]
        if df_short_adjusted.empty:
            print("[DEBUG] => Strike short ajustado no existe => NO DATA")
            return "NO DATA"
        row_short_adjusted = df_short_adjusted.iloc[0]

        # Buscamos el row correspondiente al long_strike ajustado
        df_long = df_last[df_last["strike"] == long_strike]
        print(f"[DEBUG] => Buscando long_strike={long_strike}, filas={df_long.shape[0]}")
        if df_long.empty:
            print("[DEBUG] => Strike de compra no existe => NO DATA")
            return "NO DATA"
        row_long = df_long.iloc[0]

        # Calculamos la prima neta usando los valores ajustados
        short_call_mid = (row_short_adjusted["bid_call"] + row_short_adjusted["ask_call"]) / 2
        long_call_mid = (row_long["bid_call"] + row_long["ask_call"]) / 2
        net_credit = round(short_call_mid - long_call_mid, 2)

        return (f"SELL -1 Vertical {ROW_SYMBOL} {expiry_str} "
                f"{int(short_strike)}/{int(long_strike)} CALL @"
                f"{net_credit} LMT")

    # ============================================================
    # CASO TENDENCIA 'UP': PUT CREDIT SPREAD (delta_put ~ -0.20)
    # ============================================================
    elif trend.upper() == "UP":
        # Calculamos la diferencia para delta_put
        df_last["put_diff"] = (df_last["delta_put"] + 0.20).abs()
        df_sorted = df_last.sort_values("put_diff")

        # Seleccionamos el row que tenga delta_put más cercano a -0.20 (sin ajuste)
        row_short = df_sorted.iloc[0]

        # Ajustamos el strike short y definimos el strike long
        short_strike = row_short["strike"] + adjust
        long_strike = short_strike - distance

        # Buscamos en el DataFrame el row correspondiente al short_strike ajustado
        df_short_adjusted = df_last[df_last["strike"] == short_strike]
        if df_short_adjusted.empty:
            print("[DEBUG] => Strike short ajustado no existe => NO DATA")
            return "NO DATA"
        row_short_adjusted = df_short_adjusted.iloc[0]

        # Buscamos el row correspondiente al long_strike ajustado
        df_long = df_last[df_last["strike"] == long_strike]
        print(f"[DEBUG] => Buscando long_strike={long_strike}, filas={df_long.shape[0]}")
        if df_long.empty:
            print("[DEBUG] => Strike de compra no existe => NO DATA")
            return "NO DATA"
        row_long = df_long.iloc[0]

        # Calculamos la prima neta usando los valores del strike ajustado
        short_put_mid = (row_short_adjusted["bid_put"] + row_short_adjusted["ask_put"]) / 2
        long_put_mid = (row_long["bid_put"] + row_long["ask_put"]) / 2
        net_credit = round(short_put_mid - long_put_mid, 2)

        return (f"SELL -1 Vertical {ROW_SYMBOL} {expiry_str} "
                f"{int(short_strike)}/{int(long_strike)} PUT @"
                f"{net_credit} LMT")

    # ==========================
    # TENDENCIA NO RECONOCIDA
    # ==========================
    else:
        # print(f"[DEBUG] Tendencia desconocida => {trend}")
        return "NO DATA"


def send_update_to_discord_embed(info, webhook_url):
    # Convertir la tendencia a mayúsculas y seleccionar el icono adecuado.
    tendencia = info['tendencia'].upper()
    icono = "↑" if tendencia == "UP" else "↓" if tendencia == "DOWN" else ""

    embed = {
        "title": f"Actualización de Mercado {ROW_SYMBOL}",
        "color": 5814783,  # Puedes ajustar el color (decimal)
        "fields": [
            {
                "name": f"Precio {ROW_SYMBOL}",
                "value": f"${info['precio']:.2f}",
                "inline": True
            },
            {
                "name": "Score",
                "value": f"{info['score']:.4f}",
                "inline": True
            },
            {
                "name": "Tendencia",
                "value": f"{icono} **{tendencia}**",
                "inline": False
            },
            {
                "name": "Strikes dominantes",
                "value": f"OI: {info['oi']}\nGEX: {info['gex']}\nVolumen: {info['volumen']}",
                "inline": False
            },
            {
                "name": "Predicciones de cierre",
                "value": (
                    f"OI_GEX: {info['oi_gex']}\n"
                    f"Delta Neutro: {info['delta_neutro']}\n"
                    f"Volumen: {info['volumen_close']}\n"
                    f"Punto de Gravedad: {info['gravity']}\n"
                    f"Movimiento esperado {ROW_SYMBOL}: {info['expected_move']} puntos\n"
                    f"Gamma Exposure: {info['gamma_exposure']} (Positiva)\n"
                    f"Gamma Flip Level: {info['gamma_flip']}"
                ),
                "inline": False
            },
            {
                "name": "Idea",
                "value": info['idea'],
                "inline": False
            }
        ],
        "footer": {
            "text": "Actualización automatizada"
        },
        "timestamp": datetime.utcnow().isoformat()
    }

    data = {"embeds": [embed]}
    headers = {"Content-Type": "application/json"}
    response = requests.post(webhook_url, data=json.dumps(data), headers=headers)
    if response.status_code == 204:
        print("Mensaje embed enviado a Discord correctamente.")
    else:
        print("Error al enviar mensaje a Discord:", response.status_code, response.text)


# ----------------------------
# MAIN
# ----------------------------
def main():
    print("***** Iniciando monitoreo (v8.0) *****")

    # URL de tu webhook (mantén esta URL privada en producción)
    webhook_url = "https://discord.com/api/webhooks/1344396524616159255/dgj8wh0aNrENw4WWiHTpRsLh3_wVgl6WQ8qB64Tw1Zqy4cRELXiqeGLjBb8CAQMWlE8p"

    while True:
        try:
            if not is_market_open():
                print("Mercado cerrado. Esperando apertura...")
                time.sleep(5)
                continue

            print("Cargando datos...")
            df = load_data(INPUT_CSV)

            if len(df) < SMA_WINDOW:
                print("No hay suficientes datos para cálculos más profundos.")
            else:
                # 1) Preparamos features
                df_prepared = prepare_features(df)

                # 2) Cálculo de TENDENCIA + SCORE
                tendencia_30min, score_30min = calculate_tendencia_30min(df_prepared, window_minutes=MINUTES_TENDENCIA)

                # 3) Obtenemos el último precio subyacente
                latest_price = df_prepared['underlying_price'].iloc[-1]

                # 4) Predicciones de cierre
                predicted_closes = predict_closing_prices(df_prepared)
                expected_move = calculate_expected_move(df_prepared)

                # 5) STRIKES DOMINANTES

                # dominant_strikes_dict = get_dominant_strikes(df_prepared, latest_price)
                dominant_strikes_dict = get_dominant_strikes(df, 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()

                print(GEX_dominantes)

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

                # 6) Estado de Gamma y Gamma Flip Level
                gamma_state = predicted_closes["Gamma State"]
                gex_value = predicted_closes["GEX"]
                gamma_flip_strike = predicted_closes["Gamma Flip Level"]

                timestamp_ny = get_ny_time()

                # 7) Obtenemos la idea (usando el último snapshot)
                latest_timestamp = df_prepared.index.max()
                df_latest = df_prepared.loc[[latest_timestamp]].copy()
                vertical_idea = get_vertical_idea(df_latest, tendencia_30min, distance=DISTANCE, adjust=ADJUST[0])
                vertical_idea_adjust_5 = get_vertical_idea(df_latest, tendencia_30min, distance=DISTANCE, adjust=ADJUST[1])
                vertical_idea_adjust_10 = get_vertical_idea(df_latest, tendencia_30min, distance=DISTANCE, adjust=ADJUST[2])
                vertical_idea_adjust_15 = get_vertical_idea(df_latest, tendencia_30min, distance=DISTANCE, adjust=ADJUST[3])

                ironCondor_idea = get_ironCondor_idea(df_latest, tendencia_30min, distance=DISTANCE, adjust=ADJUST[0])
                ironCondor_idea_adjust_5 = get_ironCondor_idea(df_latest, tendencia_30min, distance=DISTANCE, adjust=ADJUST[1])
                ironCondor_idea_adjust_10 = get_ironCondor_idea(df_latest, tendencia_30min, distance=DISTANCE, adjust=ADJUST[2])
                ironCondor_idea_adjust_15 = get_ironCondor_idea(df_latest, tendencia_30min, distance=DISTANCE, adjust=ADJUST[3])

                # Imprimir la información en consola
                print(f"{timestamp_ny} - {ROW_SYMBOL} precio actual ${latest_price:.2f}")
                print(f"TENDENCIA (últ. {MINUTES_TENDENCIA} min): {tendencia_30min}, SCORE: {score_30min:.4f}")
                print(f"TENDENCIA CORTA: {short_trend}")
                print("Strikes dominantes:")
                print(f" - OI: {OI_dominantes}")
                print(f" - GEX: {GEX_dominantes}")
                print(f" - Volumen: {VOL_dominantes}")
                print("Predicciones de cierre:")
                print(f" - OI_GEX: {predicted_closes['OI_GEX']}")
                print(f" - Delta Neutro: {predicted_closes['Delta Neutro']}")
                print(f" - Volumen: {predicted_closes['Volumen']}")
                print(f" - Punto de Gravedad: {predicted_closes['Punto de Gravedad']}")
                print(f" - Movimiento esperado {ROW_SYMBOL}: {expected_move} puntos")
                print(f" - Gamma Exposure: {gex_value} ({gamma_state})")
                print(f" - Gamma Flip Level: {gamma_flip_strike}")
                print(f" - Idea: {vertical_idea}")

                # 8) Armamos data a guardar en CSV
                data_to_save = {
                    "timestamp": timestamp_ny,
                    "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": vertical_idea_adjust_5,
                    "IDEA_adj10": vertical_idea_adjust_10,
                    "IDEA_adj15": vertical_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)

                # 9) Preparar un diccionario con la info a enviar a Discord
                info = {
                    "tendencia": tendencia_30min,
                    "tendencia_corta": short_trend,
                    "total_pos": total_pos,
                    "total_neg": total_neg,
                    "precio": latest_price,
                    "score": score_30min,
                    "oi": str(OI_dominantes),
                    "gex": str(GEX_dominantes),
                    "volumen": str(VOL_dominantes),
                    "oi_gex": predicted_closes['OI_GEX'],
                    "delta_neutro": predicted_closes['Delta Neutro'],
                    "volumen_close": predicted_closes['Volumen'],
                    "gravity": predicted_closes['Punto de Gravedad'],
                    "expected_move": expected_move,
                    "gamma_exposure": gex_value,
                    "gamma_flip": gamma_flip_strike,
                    "idea": vertical_idea
                }
                # Enviar la actualización a Discord
                # send_update_to_discord_embed(info, webhook_url)

        except Exception as e:
            print(f"Error en la ejecución: {e}")

        # Esperamos 60 segundos para la siguiente iteración
        # time.sleep(60)
        esperar_proximo_minuto()


def esperar_proximo_minuto():
    ahora = datetime.now()
    segundos_actuales = ahora.second

    if segundos_actuales < 30:
        # Esperar hasta que llegue a 30
        segundos_a_esperar = 30 - segundos_actuales
    else:
        # Ya pasó el 30 en este minuto, entonces esperamos al próximo minuto + 30
        segundos_a_esperar = (60 - segundos_actuales) + 30

    time.sleep(segundos_a_esperar)


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".
    """

    # 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)


if __name__ == "__main__":
    main()
