# combinadas.py
# Módulo de análisis de estrategia combinada Vertical + Iron Condor
# Colocar en el directorio raíz del proyecto junto a app.py

import os
import glob
import time
import pandas as pd
import numpy as np

DIRECTORIO_RESULTADOS = "/var/www/html/backtestingmarket/resultadosNew"

# Cache en memoria: { cache_key: { 'data': {...}, 'mtime': float } }
_cache = {}


RIESGO_LABELS = {
    "cons": "Conservador",
    "inte": "Intermedio",
    "agre": "Agresivo",
    "ultr": "Ultra Agresivo",
}

SYMBOL_LABELS = {
    "SPX": "SPX",
    "XSP": "XSP",
    "SPY": "SPY",
    "QQQ": "QQQ",
    "RUT": "RUT",
}


def _buscar_csv(estrategia, symbol, riesgo):
    """Busca el CSV más reciente para una combinación dada."""
    patron = os.path.join(
        DIRECTORIO_RESULTADOS,
        f"P&L_{estrategia}_{symbol}_MULTI_*_{riesgo}_CT_0.csv"
    )
    archivos = glob.glob(patron)
    if not archivos:
        return None
    # Si hay más de uno (no debería), toma el más reciente
    return max(archivos, key=os.path.getmtime)


def get_combinaciones_disponibles():
    """
    Lee /resultadosNew/ y devuelve las combinaciones disponibles
    donde existen AMBOS csvs (Vertical + IronCondor).
    Retorna lista de dicts: [{symbol, riesgo, label}, ...]
    """
    patron = os.path.join(DIRECTORIO_RESULTADOS, "P&L_Vertical_*_MULTI_*_CT_0.csv")
    archivos_v = glob.glob(patron)

    combinaciones = []
    for archivo_v in archivos_v:
        nombre = os.path.basename(archivo_v)
        partes = nombre.replace("P&L_", "").split("_")
        # partes: [Vertical, SPX, MULTI, 1005, 1400, n48, desde, hasta, riesgo, CT, 0.csv]
        try:
            symbol = partes[1]
            riesgo = partes[-3]  # antes de CT_0
            # Verificar que existe el IC correspondiente
            archivo_ic = _buscar_csv("IronCondor", symbol, riesgo)
            if archivo_ic and os.path.exists(archivo_ic):
                label = f"{SYMBOL_LABELS.get(symbol, symbol)} — {RIESGO_LABELS.get(riesgo, riesgo)}"
                # Extraer fechas del nombre
                desde_str = partes[6]  # YYYYMMDD
                hasta_str = partes[7]  # YYYYMMDD
                desde_fmt = f"{desde_str[:4]}/{desde_str[4:6]}/{desde_str[6:]}"
                hasta_fmt = f"{hasta_str[:4]}/{hasta_str[4:6]}/{hasta_str[6:]}"
                combinaciones.append({
                    "symbol": symbol,
                    "riesgo": riesgo,
                    "label": label,
                    "desde": desde_fmt,
                    "hasta": hasta_fmt,
                    "riesgo_label": RIESGO_LABELS.get(riesgo, riesgo),
                })
        except (IndexError, Exception):
            continue

    # Ordenar: primero por symbol, luego por riesgo
    orden_riesgo = {"cons": 0, "inte": 1, "agre": 2, "ultr": 3}
    combinaciones.sort(key=lambda x: (x["symbol"], orden_riesgo.get(x["riesgo"], 99)))
    return combinaciones


def _get_mtime_pair(symbol, riesgo):
    """Retorna el max mtime de los dos CSVs del par."""
    csv_v  = _buscar_csv("Vertical",    symbol, riesgo)
    csv_ic = _buscar_csv("IronCondor",  symbol, riesgo)
    if not csv_v or not csv_ic:
        return None
    return max(os.path.getmtime(csv_v), os.path.getmtime(csv_ic))


def _combo(vs_s, ic_s):
    """
    Suma diaria de Vertical + IC usando todos los días disponibles.
    Los días donde solo opera una estrategia contribuyen con su PnL
    y la otra aporta 0 (no operó ese día).
    """
    all_days = vs_s.index.union(ic_s.index)
    return vs_s.reindex(all_days, fill_value=0) + ic_s.reindex(all_days, fill_value=0)


def analizar_combinada(symbol, riesgo):
    """
    Corre el análisis completo de estrategia combinada.
    Usa cache en memoria — invalida si los CSVs cambiaron.
    Retorna dict con todos los datos para el template.
    """
    cache_key = f"{symbol}_{riesgo}"
    mtime_actual = _get_mtime_pair(symbol, riesgo)

    if mtime_actual is None:
        return None

    # Verificar cache
    if cache_key in _cache:
        entrada = _cache[cache_key]
        if entrada["mtime"] == mtime_actual:
            return entrada["data"]

    # Cargar CSVs
    csv_v  = _buscar_csv("Vertical",   symbol, riesgo)
    csv_ic = _buscar_csv("IronCondor", symbol, riesgo)

    v  = pd.read_csv(csv_v)
    ic = pd.read_csv(csv_ic)

    v["Hora"]  = pd.to_numeric(v["Hora"],  errors="coerce")
    v["PL"]    = pd.to_numeric(v["P/L"],   errors="coerce")
    ic["Hora"] = pd.to_numeric(ic["Hora"], errors="coerce")
    ic["PL"]   = pd.to_numeric(ic["P/L"],  errors="coerce")
    v["Day"]   = pd.to_datetime(v["Day"],  errors="coerce")
    ic["Day"]  = pd.to_datetime(ic["Day"], errors="coerce")

    vp  = v.pivot_table(index="Day", columns="Hora", values="PL", aggfunc="mean")
    icp = ic.pivot_table(index="Day", columns="Hora", values="PL", aggfunc="mean")

    # n_dias = union de todos los días disponibles en ambas estrategias
    all_days = vp.index.union(icp.index)
    n_dias = len(all_days)

    horas = sorted([h for h in vp.columns if not pd.isna(h)])

    # ── Stats por hora individuales (sin filtro de días comunes) ──
    def hora_stats(dfp):
        rows = []
        for h in sorted(dfp.columns):
            s = dfp[h].dropna()
            rows.append({
                "hora": int(h),
                "sum": round(float(s.sum()), 2),
                "wr":  round(float((s > 0).mean()), 3),
            })
        return rows

    v_hora_stats  = hora_stats(vp)
    ic_hora_stats = hora_stats(icp)

    # ── Brute force todos los pares ──
    resultados = []
    for hv in horas:
        for hic in horas:
            if hv not in vp.columns or hic not in icp.columns:
                continue
            vs_s  = vp[hv].dropna()
            ic_s  = icp[hic].dropna()
            combo = _combo(vs_s, ic_s)
            if len(combo) < 50:
                continue
            mean  = float(combo.mean())
            std   = float(combo.std())
            # correlacion solo sobre dias en comun (necesita pares)
            c_comun = vs_s.index.intersection(ic_s.index)
            corr = round(float(vs_s[c_comun].corr(ic_s[c_comun])), 3) if len(c_comun) > 1 else 0
            resultados.append({
                "hora_v":  int(hv),
                "hora_ic": int(hic),
                "total":   round(float(combo.sum()), 2),
                "mean":    round(mean, 2),
                "winrate": round(float((combo > 0).mean()), 3),
                "corr":    corr,
                "worst":   round(float(combo.min()), 2),
                "sharpe":  round(mean / std if std > 0 else 0, 4),
                "n":       int(len(combo)),
            })

    df_r = pd.DataFrame(resultados)

    top_pl     = df_r.sort_values("total",  ascending=False).head(15).to_dict("records")
    top_sharpe = df_r.sort_values("sharpe", ascending=False).head(15).to_dict("records")
    top_corr   = df_r[df_r["corr"] < 0.10].sort_values("total", ascending=False).head(15).to_dict("records")

    # ── Heatmap: top 8 V × top 7 IC por suma ──
    v_sums  = {int(h): float(vp[h].dropna().sum())  for h in horas}
    ic_sums = {int(h): float(icp[h].dropna().sum()) for h in horas}
    top_v_h  = sorted(sorted(v_sums,  key=lambda x: v_sums[x],  reverse=True)[:8])
    top_ic_h = sorted(sorted(ic_sums, key=lambda x: ic_sums[x], reverse=True)[:7])
    hm_data = {}
    for hv in top_v_h:
        hm_data[hv] = {}
        for hic in top_ic_h:
            if hv in vp.columns and hic in icp.columns:
                vs_s = vp[hv].dropna()
                ic_s = icp[hic].dropna()
                hm_data[hv][hic] = round(float(_combo(vs_s, ic_s).sum()), 1)

    # ── Consistencia mensual ──
    def monthly_stats(vp_df, icp_df, hv, hic):
        vs_s  = vp_df[hv].dropna()
        ic_s  = icp_df[hic].dropna()
        combo = _combo(vs_s, ic_s).rename("pl").to_frame()
        combo["month"] = pd.to_datetime(combo.index).to_period("M")
        monthly = combo.groupby("month")["pl"].sum()
        return monthly

    mejor_par = top_pl[0] if top_pl else None
    stability_data = []

    if mejor_par:
        # Top por consistencia mensual
        estabilidad = []
        for r in resultados:
            vs_s  = vp[r["hora_v"]].dropna()
            ic_s  = icp[r["hora_ic"]].dropna()
            combo = _combo(vs_s, ic_s).rename("pl").to_frame()
            if len(combo) < 50:
                continue
            combo["month"] = pd.to_datetime(combo.index).to_period("M")
            monthly = combo.groupby("month")["pl"].sum()
            meses_pos = int((monthly > 0).sum())
            meses_total = int(len(monthly))
            estabilidad.append({
                **r,
                "meses_pos":   meses_pos,
                "meses_total": meses_total,
                "consistency": round(meses_pos / meses_total, 3),
                "worst_month": round(float(monthly.min()), 1),
            })

        estabilidad.sort(key=lambda x: (-x["meses_pos"], -x["total"]))
        top_estabilidad = estabilidad[:8]

        # Detalle mensual del par más estable
        par_estable = estabilidad[0]
        monthly_estable = monthly_stats(vp, icp, par_estable["hora_v"], par_estable["hora_ic"])
        monthly_ganador = monthly_stats(vp, icp, mejor_par["hora_v"], mejor_par["hora_ic"])

        # Equity curves (cada 2 días para no sobrecargar)
        def equity_curve(vp_df, icp_df, hv, hic):
            vs_s  = vp_df[hv].dropna()
            ic_s  = icp_df[hic].dropna()
            daily = _combo(vs_s, ic_s).sort_index()
            cum   = daily.cumsum()
            return [round(float(v), 1) for v in cum.values[::2]]

        stability_data = {
            "top_estabilidad": top_estabilidad,
            "par_estable": par_estable,
            "par_ganador": mejor_par,
            "monthly_estable": {
                "labels": [str(m) for m in monthly_estable.index],
                "values": [round(float(v), 1) for v in monthly_estable.values],
            },
            "monthly_ganador": {
                "labels": [str(m) for m in monthly_ganador.index],
                "values": [round(float(v), 1) for v in monthly_ganador.values],
            },
            "equity_estable": equity_curve(vp, icp, par_estable["hora_v"], par_estable["hora_ic"]),
            "equity_ganador": equity_curve(vp, icp, mejor_par["hora_v"],   mejor_par["hora_ic"]),
        }

    # Extraer fechas del nombre del CSV
    nombre_csv = os.path.basename(csv_v)
    partes = nombre_csv.replace("P&L_", "").split("_")
    desde_str = partes[6]
    hasta_str = partes[7]

    data = {
        "symbol":        symbol,
        "riesgo":        riesgo,
        "riesgo_label":  RIESGO_LABELS.get(riesgo, riesgo),
        "n_dias":        n_dias,
        "desde":         f"{desde_str[:4]}/{desde_str[4:6]}/{desde_str[6:]}",
        "hasta":         f"{hasta_str[:4]}/{hasta_str[4:6]}/{hasta_str[6:]}",
        "top_pl":        top_pl,
        "top_sharpe":    top_sharpe,
        "top_corr":      top_corr,
        "v_hora_stats":  v_hora_stats,
        "ic_hora_stats": ic_hora_stats,
        "hm_v_horas":    top_v_h,
        "hm_ic_horas":   top_ic_h,
        "hm_data":       hm_data,
        "stability":     stability_data,
        "mejor_par":     mejor_par,
    }

    # Guardar en cache
    _cache[cache_key] = {"mtime": mtime_actual, "data": data}
    return data