"""MRV06 — VWAP Deviation Reversion

IDEA: On 1h bars, compute a rolling session VWAP (using typical price * volume).
Fade deviations > k*sigma back to VWAP (mean-reversion).
Regime gate: only trade in the direction of the daily trend (using a simple trend filter).

Variants tested:
  - k = 1.5 vs 2.0 (deviation threshold)
  - sigma window = 24h vs 48h (rolling window for sigma)

TF: 1h (VWAP is most meaningful at 1h granularity)
Style: continuous weights (study_weights)
"""

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 compute_vwap_deviation(df: pd.DataFrame, vwap_win: int, k: float,
                           sigma_win: int) -> np.ndarray:
    """
    Compute VWAP deviation signal with regime gate.

    VWAP: rolling typical_price * volume / rolling volume (causal window).
    Signal: when price deviates > k*sigma above VWAP -> short (expect reversion)
            when price deviates > k*sigma below VWAP -> long (expect reversion)
    Regime gate: only long when daily trend (slow EMA > fast EMA at 1h scale).

    All computations causal (value at i uses data <= i).
    """
    close = df["close"].values.astype(float)
    high = df["high"].values.astype(float)
    low = df["low"].values.astype(float)
    volume = df["volume"].values.astype(float)

    # Typical price (causal: same bar is fine, we're using it for VWAP at i)
    typical = (high + low + close) / 3.0

    # Rolling VWAP (causal window)
    s = pd.Series
    tp_vol = typical * np.where(volume > 0, volume, np.nan)

    # Rolling VWAP over vwap_win bars
    vwap_num = s(tp_vol).rolling(vwap_win, min_periods=vwap_win // 2).sum()
    vwap_den = s(volume).rolling(vwap_win, min_periods=vwap_win // 2).sum()
    vwap = (vwap_num / vwap_den.replace(0, np.nan)).values

    # Deviation from VWAP
    deviation = close - vwap

    # Rolling sigma of deviation
    sigma = s(deviation).rolling(sigma_win, min_periods=sigma_win // 2).std().values

    # Normalized deviation (z-score wrt rolling sigma)
    z = np.where(sigma > 0, deviation / sigma, 0.0)

    # Mean-reversion signal:
    # z > k => price is too high above VWAP => short (negative position)
    # z < -k => price is too low below VWAP => long (positive position)
    # Gradual: use -z/k clipped to [-1, 1] when |z| > k, else 0
    signal = np.where(np.abs(z) > k, -np.sign(z), 0.0)

    # Regime gate using daily trend: EMA(50d) vs EMA(10d) at 1h scale
    # Only allow long when fast EMA > slow EMA (uptrend), allow short any time
    # (crypto is fundamentally bullish-biased; mean-reversion shorts in downtrend risky)
    ema_fast = al.ema(close, 10 * 24)  # 10-day EMA
    ema_slow = al.ema(close, 50 * 24)  # 50-day EMA

    # In uptrend (fast > slow): allow both long and short mean-reversion
    # In downtrend (fast < slow): allow only short mean-reversion (with VWAP)
    uptrend = ema_fast > ema_slow

    # Filter: only take longs in uptrend regime
    gated = np.where(signal > 0, signal * uptrend.astype(float), signal)

    # Apply vol-targeting for position sizing
    result = al.vol_target(gated, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0)
    result = np.nan_to_num(result, nan=0.0)

    return result


def make_target(vwap_win: int, k: float, sigma_win: int):
    """Factory: returns a target_fn(df) -> weights array."""
    def target_fn(df: pd.DataFrame) -> np.ndarray:
        return compute_vwap_deviation(df, vwap_win=vwap_win, k=k, sigma_win=sigma_win)
    target_fn.__name__ = f"vwap_dev_w{vwap_win}_k{k}_s{sigma_win}"
    return target_fn


# Small internal grid (<=4 param sets)
# VWAP window: 24h (1 session) vs 48h (2 sessions)
# k threshold: 1.5 vs 2.0
# sigma_win tied to vwap_win
CONFIGS = [
    # (vwap_win, k, sigma_win, label)
    (24,  1.5, 48,  "vwap24h_k1.5_s48h"),
    (24,  2.0, 48,  "vwap24h_k2.0_s48h"),
    (48,  1.5, 96,  "vwap48h_k1.5_s96h"),
    (48,  2.0, 96,  "vwap48h_k2.0_s96h"),
]

best_rep = None
best_hold = -999.0

print("=== MRV06 VWAP Deviation Reversion ===")
print(f"Testing {len(CONFIGS)} configs on 1h bars (BTC + ETH)\n")

for vwap_win, k, sigma_win, label in CONFIGS:
    print(f"--- Config: {label} ---")
    fn = make_target(vwap_win, k, sigma_win)
    rep = al.study_weights(
        f"MRV06-{label}",
        fn,
        tfs=("1h",)
    )
    print(al.fmt(rep))
    hold_sharpe = rep["verdict"].get("best_holdout_sharpe", -999)
    if hold_sharpe > best_hold:
        best_hold = hold_sharpe
        best_rep = rep
    print()

# Print best config
print("\n\n=== BEST CONFIG ===")
print(al.fmt(best_rep))
print("JSON:", al.as_json(best_rep))
