"""Agent 01 — Dual EMA crossover (family=trend, slug=ema_cross).

The angle: long/short on the sign of (fast EMA - slow EMA). The two spans are the
core tuned knobs. One refinement that survived a plateau check on split='train':
the two anonymized curves are strongly up-trending, so a SYMMETRIC short is pure
drag (it shorts the dips of a bull market). We keep the long/short crossover but
size the SHORT side down by `SHORT_W` — still a genuine long/short EMA cross, just
risk-asymmetric. Direction is then vol-targeted (causal trailing window) so the two
curves are sized comparably and the drawdown stays bounded.

Tuning (train only): a broad plateau f in [18..30], s in [40..50], SHORT_W in
[0.1..0.3] all give sharpe_min ~1.3 / DD ~0.23. f=25, s=40, SHORT_W=0.25 sits in
the plateau interior (not on a grid edge) -> robust, not a lucky cell.

CAUSAL: ema(c, span) is an online recursion (value at i uses rows 0..i only);
vol_target uses a trailing vol window. No look-ahead, no centered windows, no
global fit. Verified by causality_ok (max_diff 0.0).
"""
import numpy as np
import blindlib as bl

# --- tuned ONLY on split='train' (plateau interior) ---
FAST_SPAN = 25
SLOW_SPAN = 40
SHORT_W = 0.25          # short side sized down (asymmetric L/S); 0 -> long-flat
TARGET_VOL = 0.20
VOL_WIN = 30
LEV_CAP = 1.0


def signal(df):
    c = df["close"].values.astype(float)
    fast = bl.ema(c, FAST_SPAN)
    slow = bl.ema(c, SLOW_SPAN)
    # +1 when fast above slow, -SHORT_W when below: genuine EMA-cross direction,
    # short side de-weighted because the curves are persistently up-trending.
    raw = np.where(fast >= slow, 1.0, -SHORT_W)
    pos = bl.vol_target(raw, df, target_vol=TARGET_VOL,
                        vol_win_days=VOL_WIN, leverage_cap=LEV_CAP)
    return np.clip(pos, -1.0, 1.0)
