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

# nohup /bin/python3 -u /var/www/html/predictor.backtestingmarket/upOrDown8_0.py > upOrDown8_0.log 2>&1 &

# ------------------------------------------------
# CONFIGURACIÓN GLOBAL
# ------------------------------------------------
symbol = "$SPX"
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/prediction_{symbol}_{fecha_actual}.csv"

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

# Rango para filtrar strikes cercanos al precio subyacente para "dominant strikes"
STRIKE_RANGE = 120



# ------------------------------------------------
# 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):
    """
    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['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, symbol="SPX", distance=5, adjust=0):
    """
    Genera UNA idea de Iron Condor, devolviendo "NO DATA: <razón>" 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: <razón>" si no se pudo construir el Iron Condor.
    """

    if df.empty:
        return "NO DATA: DataFrame vacío"

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

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

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

    # CALL side
    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

    # PUT side
    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

    net_credit = round(net_credit_call + net_credit_put, 2)

    return (
        f"SELL -1 IRON CONDOR {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, symbol="SPX", distance=5, adjust=0):
    """
    Genera la 'Idea' de un vertical credit spread buscando delta ~ ±0.20.
    """
    if df.empty:
        print("[DEBUG] DataFrame vacío => NO DATA")
        return "NO DATA"

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

    if trend.upper() == "DOWN":
        df_last["call_diff"] = (df_last["delta_call"] - 0.20).abs()
        df_sorted = df_last.sort_values("call_diff")
        row_short = df_sorted.iloc[0]
        short_strike = row_short["strike"] - adjust
        long_strike = short_strike + distance

        df_short_adjusted = df_last[df_last["strike"] == short_strike]
        if df_short_adjusted.empty:
            return "NO DATA"
        row_short_adjusted = df_short_adjusted.iloc[0]

        df_long = df_last[df_last["strike"] == long_strike]
        if df_long.empty:
            return "NO DATA"
        row_long = df_long.iloc[0]

        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 {symbol} {expiry_str} "
                f"{int(short_strike)}/{int(long_strike)} CALL @"
                f"{net_credit} LMT")

    elif trend.upper() == "UP":
        df_last["put_diff"] = (df_last["delta_put"] + 0.20).abs()
        df_sorted = df_last.sort_values("put_diff")
        row_short = df_sorted.iloc[0]
        short_strike = row_short["strike"] + adjust
        long_strike = short_strike - distance

        df_short_adjusted = df_last[df_last["strike"] == short_strike]
        if df_short_adjusted.empty:
            return "NO DATA"
        row_short_adjusted = df_short_adjusted.iloc[0]

        df_long = df_last[df_last["strike"] == long_strike]
        if df_long.empty:
            return "NO DATA"
        row_long = df_long.iloc[0]

        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 {symbol} {expiry_str} "
                f"{int(short_strike)}/{int(long_strike)} PUT @"
                f"{net_credit} LMT")

    else:
        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": "Actualización de Mercado SPX",
        "color": 5814783,  # Puedes ajustar el color (decimal)
        "fields": [
            {
                "name": "Precio SPX",
                "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 SPX: {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)



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)




def run_for_symbol(symbol):
    print(f"\n***** Iniciando monitoreo para {symbol} *****")

    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/prediction_{symbol}_{fecha_actual}.csv"

    webhook_url = "https://discord.com/api/webhooks/1344396524616159255/dgj8wh0aNrENw4WWiHTpRsLh3_wVgl6WQ8qB64Tw1Zqy4cRELXiqeGLjBb8CAQMWlE8p"

    # Determinar distancia entre strikes según el símbolo
    distance = 5 if symbol.upper() in ["$SPX", "$RUT"] else 1

    try:
        if not is_market_open():
            print("Mercado cerrado. Esperando apertura...")
            return

        print("Cargando datos...")
        df = load_data(INPUT_CSV)
        if len(df) < SMA_WINDOW:
            print("No hay suficientes datos para cálculos más profundos.")
            return

        df_prepared = prepare_features(df)
        tendencia_30min, score_30min = calculate_tendencia_30min(df_prepared, window_minutes=MINUTES_TENDENCIA)
        latest_price = df_prepared['underlying_price'].iloc[-1]
        predicted_closes = predict_closing_prices(df_prepared)
        expected_move = calculate_expected_move(df_prepared)

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

        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
        )

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

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

        symbol_clean = symbol.replace('$','')

        # Determinar distancia y ajuste según símbolo
        if symbol.upper() in ["$SPX", "$RUT"]:
            distance = 5
            adjust_values = [0, 5, 10, 15]
        else:
            distance = 1
            adjust_values = [0, 1, 2, 3]

        vertical_idea       = get_vertical_idea(df_latest, tendencia_30min, symbol=symbol_clean, distance=distance, adjust=adjust_values[0])
        idea_adjust_5       = get_vertical_idea(df_latest, tendencia_30min, symbol=symbol_clean, distance=distance, adjust=adjust_values[1])
        idea_adjust_10       = get_vertical_idea(df_latest, tendencia_30min, symbol=symbol_clean, distance=distance, adjust=adjust_values[2])
        idea_adjust_15       = get_vertical_idea(df_latest, tendencia_30min, symbol=symbol_clean, distance=distance, adjust=adjust_values[3])

        ironCondor_idea     = get_ironCondor_idea(df_latest, tendencia_30min, symbol=symbol_clean, distance=distance, adjust=adjust_values[0])
        ironCondor_idea_adjust_5 = get_ironCondor_idea(df_latest, tendencia_30min, symbol=symbol_clean, distance=distance, adjust=adjust_values[1])
        ironCondor_idea_adjust_10 = get_ironCondor_idea(df_latest, tendencia_30min, symbol=symbol_clean, distance=distance, adjust=adjust_values[2])
        ironCondor_idea_adjust_15 = get_ironCondor_idea(df_latest, tendencia_30min, symbol=symbol_clean, distance=distance, adjust=adjust_values[3])

        print(f"{timestamp_ny} - {symbol_clean} 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 {symbol_clean}: {expected_move} puntos")
        print(f" - Gamma Exposure: {gex_value} ({gamma_state})")
        print(f" - Gamma Flip Level: {gamma_flip_strike}")
        print(f" - Idea: {vertical_idea}")

        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": 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)

        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
        }
        # send_update_to_discord_embed(info, webhook_url)

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




def main():
    symbols = ["$RUT"]
    while True:
        for symbol in symbols:
            run_for_symbol(symbol)
        esperar_proximo_minuto()

if __name__ == "__main__":
    main()
