"""
Motore del gioco-SESSION: pattern ORARI intraday. Ogni giorno si osserva il movimento
in una "fascia di controllo" [ctrl_hour, ctrl_hour+ctrl_len) e si scommette sul movimento
della finestra SUBITO DOPO (hold ore), seguendo (trend) o fadando (reversion) la fascia.

Cerca se esistono orari il cui comportamento ANTICIPA la finestra successiva, ripetibile nei
giorni. Dati orari reali (BTC=A, ETH=B), full history. PnL per-trade additivo, fee 0.10% RT.
"""
from __future__ import annotations

import sys
from pathlib import Path

import numpy as np
import pandas as pd

PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))

from src.data.downloader import load_data

FEE_RT = 0.001
MIN_TRADES_PER_MONTH = 10.0
BARS_PER_MONTH = 24 * 30


def load_session(asset: str = "BTC"):
    df = load_data(asset, "1h").copy()
    dt = pd.to_datetime(df["datetime"])
    return {"close": df["close"].to_numpy(float),
            "open": df["open"].to_numpy(float),
            "hour": dt.dt.hour.to_numpy(),
            "day": (dt.dt.year * 366 + dt.dt.dayofyear).to_numpy(),  # indice giorno
            "dt": dt.to_numpy(), "n": len(df)}


def evaluate(data, spec, sl=None, fee=FEE_RT):
    """spec = {ctrl_hour, ctrl_len, entry_thr(%), direction, hold}. Una valutazione per giorno:
    a fine fascia di controllo, se |ret_fascia| > entry_thr entra e tiene hold ore."""
    c, hour = data["close"], data["hour"]
    n = data["n"]
    s, e = (sl if sl else (0, n))
    ch = int(spec["ctrl_hour"]) % 24
    cl = max(1, int(spec["ctrl_len"]))
    thr = float(spec["entry_thr"]) / 100.0
    hold = max(1, int(spec["hold"]))
    sign = 1 if spec.get("direction", "trend") == "trend" else -1

    # indici in cui inizia la fascia di controllo (bar all'ora ch)
    starts = np.where(hour[s:e] == ch)[0] + s
    rets = []
    for st in starts:
        be = st + cl - 1                 # ultima barra della fascia
        ex = be + hold                   # uscita
        if ex >= e or st == 0:
            continue
        ctrl_ret = c[be] / c[st - 1] - 1.0     # ritorno della fascia (causale: chiude a be)
        if abs(ctrl_ret) < thr:
            continue
        d = sign * (1 if ctrl_ret > 0 else -1)  # trend segue, reversion fada
        entry = c[be]; exit_px = c[ex]
        net = d * (exit_px - entry) / entry - fee
        rets.append(net)
    rets = np.array(rets)
    nbars = e - s
    months = nbars / BARS_PER_MONTH
    n_tr = len(rets)
    tpm = n_tr / months if months > 0 else 0.0
    if n_tr == 0:
        return dict(n_trades=0, win_rate=0.0, pnl_pct=0.0, tpm=0.0, sharpe=0.0,
                    avg_ret=0.0, qualified=False, fitness=-1e6)
    win = float(np.mean(rets > 0))
    pnl = float(np.sum(rets)) * 100
    avg = float(np.mean(rets)) * 100
    sharpe = float(np.mean(rets) / (np.std(rets) + 1e-12) * np.sqrt(tpm * 12)) \
        if np.std(rets) > 0 else 0.0
    qualified = tpm >= MIN_TRADES_PER_MONTH
    fitness = pnl + 50.0 * win
    if not qualified:
        fitness = -1e6 + pnl
    return dict(n_trades=n_tr, win_rate=win, pnl_pct=pnl, tpm=tpm, sharpe=sharpe,
                avg_ret=avg, qualified=qualified, fitness=fitness)


def splits3(data, train_frac=0.60, valid_frac=0.20):
    n = data["n"]
    c1 = int(n * train_frac); c2 = int(n * (train_frac + valid_frac))
    return (0, c1), (c1, c2), (c2, n)


if __name__ == "__main__":
    d = load_session("BTC"); tr, va, te = splits3(d)
    for ch in [0, 8, 13, 20]:
        for dr in ["trend", "reversion"]:
            sp = {"ctrl_hour": ch, "ctrl_len": 2, "entry_thr": 0.3, "direction": dr, "hold": 4}
            f = evaluate(d, sp, None); o = evaluate(d, sp, te)
            print(f"h{ch:>2} {dr:>9} len2 hold4 thr0.3 | FULL pnl{f['pnl_pct']:7.0f} win{f['win_rate']*100:3.0f} "
                  f"tpm{f['tpm']:4.0f} Sh{f['sharpe']:5.1f} | OOS Sh{o['sharpe']:5.1f}")
