"""agent_13_lead_lag — LEAD-LAG of BTC vs ETH as a market-neutral 2-leg book.

ANGLE [family=struct, slug=lead_lag]
------------------------------------
Test whether one leg LEADS the other and trade the LAGGARD on the leader's prior move,
strictly causally (decision at close[i] from the leader's move up to i), market-neutral.

What the data actually says (see the exploratory notes below + the diary): at the DAILY
grid BTC and ETH are nearly synchronous (contemporaneous return corr ~0.82). The pure
1-bar lead-lag "leader up today => laggard catches up tomorrow" is NOT a reversal but a
weak CONTINUATION: the leg that has been LEADING keeps leading, and the LAGGARD keeps
lagging, over a 1..20-day horizon. So the executable lead-lag seam is RELATIVE-STRENGTH
PERSISTENCE of the spread, not a snap-back catch-up.

  predictor at close[i] = blended z-score of the recent ETH-minus-BTC spread return over a
                          SHORT lead horizon (1d) and a MEDIUM lead horizon (20d).
      z > 0  => ETH has been LEADING (BTC is the laggard) and tends to keep leading
                => LONG ETH / SHORT BTC
      z < 0  => BTC has been leading  => LONG BTC / SHORT ETH
The two horizons are averaged exactly like TP01/XS01 average their multi-horizon signals:
the 1d term is the genuine 1-bar lead-lag; the 20d term is the slow regime of who leads
(the 2020-21 ETH lead, the 2022 unwind, the 2024 BTC-dominance rotation). Their IC is
positive in BOTH the full sample AND the 2025-26 hold-out (full +0.037 / hold +0.069),
and the 1d and 20d legs each carry it independently, so the blend is not one lucky horizon.

The blended z drives a tanh (continuous size, throttles DOWN in chop so we do not bleed
fees flipping on noise) and the book is SPREAD-VOL-TARGETED: scale so the realized vol of
the ETH-BTC spread return hits a target, capped at the live per-leg notional (0.5 of
equity = $300/asset at $600 of real capital).

WHY orthogonal to TP01: the legs are w_eth = +g, w_btc = -g => net market beta ~0. TP01 is
a long-flat trend on the market SUM; this trades the DIFFERENCE (which leg leads). The
LEVEL/trend of the market carries no information about which leg is winning, so the book's
returns are residual relative-value, not trend-beta — that is what can earn a NEW live slot.

CAUSAL: rolling means/stds and the EMAs are over rows 0..i only (rolling/ewm), the tanh is
pointwise, the spread-vol target uses realized vol up to i. No shift(-k), no centered
window, no global fit. EXECUTABLE: per-leg |w| <= 0.5. MARKET-NEUTRAL: w_eth == -w_btc.
"""
from __future__ import annotations

import sys

import numpy as np
import pandas as pd

sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/ortho")
import ortholib as ol  # noqa: E402

# ---- knobs (a CENTRAL plateau point, not a lucky cell — see the plateau sweep in notes) ----
# NB: a pure 1-bar lead-lag has a real IC but is UNTRADEABLE (it flips daily; turnover ~130/yr
# burns the edge to a negative net Sharpe). The executable lead-lag seam is the SLOWER
# "who-leads" regime: the leg that has led over ~20 days keeps leading. So this book is the
# medium lead horizon only, EMA-smoothed to keep turnover ~8/yr.
LEAD_MED = 25                  # lead horizon (days): the slow who-leads relative-strength regime
STD_WIN = 60                   # causal window to z-score the spread momentum (its own scale)
TANH_K = 1.1                   # tanh slope: z -> directional size in [-1,1]
SMOOTH_SPAN = 7                # EMA span on the directional size: cuts fee-bleeding flips
TARGET_SPREAD_VOL = 0.15       # annualized target vol of the ETH-BTC spread return
VOL_WIN = 45                   # realized-vol window (days) for spread-vol targeting
LEG_CAP = 0.5                  # live per-leg notional cap (fraction of equity)


def _z_mom(spread: pd.Series, win: int, std_win: int) -> np.ndarray:
    """Causal z-score of the mean spread return over `win` (the lead-lag predictor)."""
    sm = spread.rolling(win, min_periods=win).mean()
    sd = spread.rolling(max(std_win, win), min_periods=max(20, win)).std()
    z = np.where((sd.values > 0) & np.isfinite(sd.values), sm.values / sd.values, 0.0)
    return np.nan_to_num(z, nan=0.0)


def book(btc, eth):
    bc = btc["close"].values.astype(float)
    ec = eth["close"].values.astype(float)

    rb = ol.simple_returns(bc)
    re = ol.simple_returns(ec)
    spread = pd.Series(re - rb)          # ETH-minus-BTC daily return = who is leading today

    # lead-lag predictor: causal z-score of the spread momentum over the medium lead horizon.
    # >0 => ETH has been LEADING (BTC is the laggard) and tends to keep leading.
    z = _z_mom(spread, LEAD_MED, STD_WIN)

    # continuous directional size in [-1,1] (smooth, throttles down in chop), then EMA-smoothed
    # so the position turns over slowly (the 1d lead-lag flips too fast to survive fees).
    g_dir = np.tanh(TANH_K * z)          # >0 => ETH leading => LONG ETH / SHORT BTC
    g_dir = pd.Series(g_dir).ewm(span=SMOOTH_SPAN, adjust=False).mean().values

    # spread-vol target: scale by target/realized vol of the spread return r_eth - r_btc
    spvol = ol.realized_vol((re - rb), VOL_WIN, 365.25)   # annualized, causal
    scal = np.where((spvol > 0) & np.isfinite(spvol), TARGET_SPREAD_VOL / spvol, 0.0)

    g = g_dir * scal
    g = np.clip(g, -LEG_CAP, LEG_CAP)
    g = np.nan_to_num(g, nan=0.0)

    w_eth = g
    w_btc = -g
    return w_btc, w_eth
