"""XAS04 — Lead-lag BTC->ETH

HYPOTHESIS: BTC returns lead ETH returns. ETH position = sign of BTC lagged return.
We evaluate on ETH series with BTC-derived signals. BTC and ETH data must be at the SAME
timeframe so that timestamp alignment is exact (no cross-TF look-ahead).

CAUSAL GUARANTEE:
  - BTC and ETH are loaded at the SAME timeframe (1d or 12h).
  - We align BTC to ETH by merging on common timestamps (inner/exact match).
  - target[i] uses BTC close[i] (same bar as ETH close[i]) -> held during bar i+1.
  - The altlib eval_weights shift handles the i->i+1 transition.
  - No cross-TF artifacts.

CRITICAL BUG AVOIDED: loading BTC at 1d and aligning to ETH at 12h creates look-ahead
because the 1d bar timestamp (midnight) has the day's FINAL close, so the midnight 12h bar
gets the full day's close which is the FUTURE relative to the midnight price.
FIX: always use the same TF for both BTC and ETH.

CONFIGS TESTED:
  1. Sign of BTC 1-bar return (pure lag-1 momentum applied to ETH)
  2. BTC EMA5/20 cross -> ETH direction (BTC trend applied to ETH)
  3. BTC TSMOM multi-horizon (30/90/180d) -> ETH direction
  4. Blend: require BTC lag-1 AND BTC EMA trend to agree before entering ETH
"""
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
import altlib as al
import numpy as np
import pandas as pd


def _align_same_tf(eth_df: pd.DataFrame, btc_df: pd.DataFrame) -> np.ndarray:
    """Align BTC close to ETH timestamps using exact same-TF merge.
    Both DataFrames have the SAME timeframe, so merge on timestamp directly.
    Returns BTC close values aligned to ETH bar indices, NaN where missing."""
    eth_ts = eth_df["timestamp"].astype("int64")
    btc_df2 = btc_df[["timestamp", "close"]].copy()
    btc_df2["timestamp"] = btc_df2["timestamp"].astype("int64")
    btc_df2 = btc_df2.rename(columns={"close": "btc_close"})

    merged = eth_ts.to_frame().merge(btc_df2, on="timestamp", how="left")
    return merged["btc_close"].values.astype(float)


def make_target_lag1(tf: str):
    """Config 1: Sign of BTC 1-bar return -> ETH vol-targeted position."""
    btc_df = al.get("BTC", tf)

    def target_fn(df):
        # Align BTC close to ETH at same TF
        btc_c = _align_same_tf(df, btc_df)
        n = len(df)

        # BTC 1-bar lagged return: r[i] = BTC_close[i] / BTC_close[i-1] - 1
        btc_ret = np.zeros(n)
        btc_ret[1:] = btc_c[1:] / btc_c[:-1] - 1.0

        # Direction = sign of BTC return
        direction = np.sign(btc_ret)
        direction[~np.isfinite(direction)] = 0.0
        direction[:5] = 0.0  # warmup

        # Vol-target position on ETH
        return al.vol_target(direction, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0)

    return target_fn


def make_target_ema(tf: str, ema_fast: int = 5, ema_slow: int = 20):
    """Config 2: BTC EMA cross -> ETH direction (long-flat)."""
    btc_df = al.get("BTC", tf)

    def target_fn(df):
        btc_c = _align_same_tf(df, btc_df)
        n = len(df)

        fast = al.ema(btc_c, ema_fast)
        slow = al.ema(btc_c, ema_slow)

        direction = np.where(fast > slow, 1.0, 0.0)
        direction[~np.isfinite(direction)] = 0.0
        direction[:ema_slow + 5] = 0.0  # warmup

        return al.vol_target(direction, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0)

    return target_fn


def make_target_tsmom(tf: str):
    """Config 3: BTC TSMOM multi-horizon (30/90/180 day) -> ETH direction (long-flat)."""
    btc_df = al.get("BTC", tf)

    def target_fn(df):
        btc_c = _align_same_tf(df, btc_df)
        n = len(df)
        bpd = al.bars_per_day(df)

        signal = np.zeros(n)
        for days in (30, 90, 180):
            h = int(days * bpd)
            if h < n:
                s = np.full(n, np.nan)
                valid = ~np.isnan(btc_c) & (np.roll(btc_c, h) != 0)
                s[h:] = np.sign(btc_c[h:] / btc_c[:-h] - 1.0)
                signal = signal + np.nan_to_num(s)

        direction = np.clip(np.sign(signal), 0, None)  # long-flat
        direction[:int(180 * bpd) + 5] = 0.0

        return al.vol_target(direction, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0)

    return target_fn


def make_target_blend(tf: str, lag: int = 1, ema_span: int = 10):
    """Config 4: Blend BTC lag-1 sign + BTC EMA trend; enter ETH only when both agree."""
    btc_df = al.get("BTC", tf)

    def target_fn(df):
        btc_c = _align_same_tf(df, btc_df)
        n = len(df)

        # Signal 1: BTC 1-bar lag sign
        btc_ret = np.zeros(n)
        btc_ret[lag:] = btc_c[lag:] / btc_c[:-lag] - 1.0
        sig1 = np.sign(btc_ret)
        sig1[:lag + 3] = 0.0

        # Signal 2: BTC EMA momentum
        fast = al.ema(btc_c, ema_span)
        slow = al.ema(btc_c, ema_span * 4)
        sig2 = np.where(fast > slow, 1.0, 0.0)
        sig2[:ema_span * 4 + 5] = 0.0

        # Both must agree (long ETH only when BTC shows positive momentum AND positive lag)
        direction = np.where((sig1 > 0) & (sig2 > 0), 1.0, 0.0)
        direction[~np.isfinite(direction)] = 0.0

        return al.vol_target(direction, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0)

    return target_fn


if __name__ == "__main__":
    print("XAS04 — Lead-lag BTC->ETH (same-TF, causal alignment)")
    print("=" * 70)

    # We test on both 1d and 12h but use SAME TF for BTC signal source.
    # This avoids the cross-TF look-ahead artifact.
    # The study_weights will apply the function to BOTH BTC and ETH at each TF.
    # For BTC asset: BTC signal applied to BTC (degenerate = BTC follows BTC lag)
    # For ETH asset: BTC signal applied to ETH (the actual lead-lag hypothesis)

    # Config 1: pure lag-1 BTC signal -> run on 1d and 12h
    print("\n--- C1: Lag-1 BTC return sign -> ETH (1d) ---")
    rep_c1_1d = al.study_weights(
        "XAS04-C1-lag1-1d",
        make_target_lag1("1d"),
        tfs=("1d",)
    )
    print(al.fmt(rep_c1_1d))

    print("\n--- C1: Lag-1 BTC return sign -> ETH (12h) ---")
    rep_c1_12h = al.study_weights(
        "XAS04-C1-lag1-12h",
        make_target_lag1("12h"),
        tfs=("12h",)
    )
    print(al.fmt(rep_c1_12h))

    print("\n--- C2: BTC EMA5/20 cross -> ETH (1d) ---")
    rep_c2 = al.study_weights(
        "XAS04-C2-ema5x20-1d",
        make_target_ema("1d", 5, 20),
        tfs=("1d",)
    )
    print(al.fmt(rep_c2))

    print("\n--- C3: BTC TSMOM -> ETH (1d) ---")
    rep_c3 = al.study_weights(
        "XAS04-C3-tsmom-1d",
        make_target_tsmom("1d"),
        tfs=("1d",)
    )
    print(al.fmt(rep_c3))

    print("\n--- C4: BTC blend lag+ema -> ETH (1d) ---")
    rep_c4 = al.study_weights(
        "XAS04-C4-blend-1d",
        make_target_blend("1d", lag=1, ema_span=10),
        tfs=("1d",)
    )
    print(al.fmt(rep_c4))

    # Identify best config
    all_reps = [rep_c1_1d, rep_c1_12h, rep_c2, rep_c3, rep_c4]
    best_rep = max(all_reps, key=lambda r: r["verdict"].get("best_holdout_sharpe") or -999)

    print("\n" + "=" * 70)
    print(f"BEST CONFIG: {best_rep['name']} -> {best_rep['verdict']['grade']}")
    print(al.fmt(best_rep))
    print("\nJSON:", al.as_json(best_rep))
