"""agent_05_ratio_ewma_cross — EMA-CROSS on log(ETH/BTC), market-neutral 2-leg book.

ANGLE [family=rv, slug=ratio_ewma_cross]
----------------------------------------
A 2-leg BTC/ETH relative-value book driven by a classic moving-average CROSS of the
relative price. Build the log ratio s = log(ETH/BTC) and take a FAST EMA and a SLOW EMA of
it. The cross drives the direction of the spread:
    fast > slow  => the ETH/BTC ratio is trending up => LONG the ratio (LONG ETH / SHORT BTC)
    fast < slow  => the ratio is trending down       => SHORT the ratio (SHORT ETH / LONG BTC)
The book is symmetric (RV has no structural up-bias, so the SHORT side is allowed and used
exactly as the long side). The cross magnitude (fast-slow normalized by the spread's own
scale) sizes a tanh, then the book is SPREAD-VOL-TARGETED 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: legs are w_eth = +g, w_btc = -g => net market beta ~0. TP01 is a
long-flat trend on the market SUM; this is a trend on the DIFFERENCE. The level of the
market (TP01's signal) carries no information about which leg is winning, so the book's
returns are residual relative-value, not trend-beta — that is what earns a NEW live slot.

WHY an EMA-cross (vs the z-score momentum blend of agent_00 or the Donchian breakout of
agent_04): the EMA-cross is a SMOOTH, recency-weighted trend filter on the ratio. It rides
the long, slow regimes of relative strength (the 2020-21 ETH catch-up, the 2022 unwind, the
2024 rotation) while a tanh on the normalized gap throttles size DOWN inside chop (small gap
=> small position => fewer fee-bleeding flips). It is neither a hard breakout state (agent_04
holds full size until reversed) nor a multi-horizon z (agent_00 mean-reverts when extended):
a continuously-sized cross has its own return texture, so it can blend rather than duplicate.

CAUSAL: EMAs are recursive over rows 0..i only (ewm), the gap normalization uses a causal
rolling std, the tanh is pointwise, and 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 by construction.
"""
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 PLATEAU point, not a lucky cell — see notes) ----------------
FAST = 20                      # fast EMA span (days) on the log ratio
SLOW = 80                      # slow EMA span (days) on the log ratio
NORM_WIN = 90                  # causal window to normalize the fast-slow gap (its own scale)
TANH_K = 1.6                   # tanh slope: normalized gap -> directional size in [-1,1]
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 book(btc, eth):
    bc = btc["close"].values.astype(float)
    ec = eth["close"].values.astype(float)

    # relative price in logs: rising => ETH outperforming BTC
    logratio = np.log(ec) - np.log(bc)

    # smooth recency-weighted trend filter: fast vs slow EMA of the ratio (causal ewm)
    fast = ol.ema(logratio, FAST)
    slow = ol.ema(logratio, SLOW)
    gap = fast - slow                       # >0 => ratio trending up (long ETH/short BTC)

    # normalize the gap by its OWN causal scale so the tanh sees a stationary input across
    # regimes (the raw log-ratio drifts; the gap's dispersion changes with vol). Rolling std
    # of the gap uses only rows 0..i.
    gsd = pd.Series(gap).rolling(NORM_WIN, min_periods=max(2, NORM_WIN // 2)).std().values
    gnorm = np.where((gsd > 0) & np.isfinite(gsd), gap / gsd, 0.0)
    gnorm = np.nan_to_num(gnorm, nan=0.0)

    # continuous directional size in [-1,1] (smooth, throttles down in chop)
    g_dir = np.tanh(TANH_K * gnorm)

    # spread-vol target: scale by target/realized vol of the spread return r_eth - r_btc
    rb = ol.simple_returns(bc)
    re = ol.simple_returns(ec)
    spread_ret = re - rb
    spvol = ol.realized_vol(spread_ret, 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
