"""RSK07 — Drawdown-scaled exposure
HYPOTHESIS: Exposure proportional to (1 - recent rolling drawdown) on a long-only base.
De-risk into weakness: when the asset is in a large drawdown, reduce position size.

Style: al.study_weights (continuous position / vol-targeted)

Idea:
  - Compute the rolling drawdown over a lookback window.
  - Target exposure = (1 - drawdown_fraction) where drawdown_fraction in [0, 1].
  - Apply vol-targeting on top to keep risk constant.
  - Long-only base (no shorting).

The rolling drawdown at bar i = (rolling_max(close, dd_win) - close[i]) / rolling_max(close, dd_win)
This is causal: uses close[i] and prior highs.

Exposure(i) = max(0, 1 - drawdown(i))
With vol-targeting, this scales by (target_vol / realized_vol).

Small grid (<=4 configs, total backtests = 4 * 2 assets <= 8):
  A: dd_win=20,  vol_target=0.20
  B: dd_win=60,  vol_target=0.20
  C: dd_win=120, vol_target=0.20
  D: dd_win=60,  vol_target=0.15

TFs tested: 1d, 12h (2 TFs * 4 configs * 2 assets = 16 total — but study_weights
runs per config, so we do 4 configs across 2 TFs = 8 backtest calls)
"""

import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
import altlib as al
import numpy as np


# ---------------------------------------------------------------------------
# Core target function
# ---------------------------------------------------------------------------
def make_target(df, dd_win: int = 60, target_vol: float = 0.20) -> np.ndarray:
    """
    Long-only drawdown-scaled exposure with vol-targeting.

    Steps:
    1. Compute rolling max of close over dd_win bars (causal: max(close[i-dd_win:i+1]))
    2. Drawdown fraction = (rolling_max - close) / rolling_max
    3. Raw exposure = max(0, 1 - drawdown_fraction)  in [0, 1]
    4. Apply vol-target scaling: multiply by (target_vol / realized_vol), cap at 2x
    5. Result: long-only position in [0, 2], decided with data <= close[i]
    """
    c = df["close"].values.astype(float)
    n = len(c)

    # Causal rolling maximum: max of close over [i-dd_win+1 .. i]
    # Use pandas rolling with min_periods=1
    c_series = df["close"].astype(float)
    roll_max = c_series.rolling(dd_win, min_periods=1).max().values

    # Drawdown fraction (0 = at high-water mark, 1 = fully drawn down)
    dd_frac = np.where(roll_max > 0, (roll_max - c) / roll_max, 0.0)
    dd_frac = np.clip(dd_frac, 0.0, 1.0)

    # Raw direction/size: (1 - drawdown), always long [0, 1]
    raw_exposure = 1.0 - dd_frac  # 1.0 at HWM, 0.0 at full drawdown

    # Vol-targeting: scale so expected volatility = target_vol
    # Use al.vol_target with direction=raw_exposure (already in [0,1])
    # But al.vol_target expects direction in {-1, 0, 1}; we'll do manual vol-scaling
    # Realized vol: rolling std of log returns
    log_ret = np.diff(np.log(c), prepend=np.nan)
    vol_win = int(30 * al.bars_per_day(df))
    vol_win = max(vol_win, 5)
    r_series = pd.Series(log_ret) if False else __import__('pandas').Series(log_ret)

    # Realized vol: annualized
    log_ret_arr = al.log_returns(c)
    bpy = al.bars_per_year(df)
    rv = al.realized_vol(log_ret_arr, vol_win, bpy)

    # Vol-target scaling
    lev = np.where((rv > 0) & np.isfinite(rv), target_vol / rv, 1.0)
    lev = np.clip(lev, 0.0, 2.0)

    # Final target: drawdown-scaled exposure * vol lever
    target = raw_exposure * lev

    # Cap at 2.0 (leverage cap)
    target = np.clip(target, 0.0, 2.0)

    # First few bars: NaN until we have enough data
    warmup = max(dd_win, vol_win)
    target[:warmup] = np.nan

    return target


# ---------------------------------------------------------------------------
# Grid search
# ---------------------------------------------------------------------------
import pandas as pd  # noqa: E402  (needed above via __import__, explicit now)

GRID = [
    {"dd_win": 20,  "target_vol": 0.20, "label": "dd=20  vol=20%"},
    {"dd_win": 60,  "target_vol": 0.20, "label": "dd=60  vol=20%"},
    {"dd_win": 120, "target_vol": 0.20, "label": "dd=120 vol=20%"},
    {"dd_win": 60,  "target_vol": 0.15, "label": "dd=60  vol=15%"},
]

best_rep = None
best_score = -999.0
best_label = ""

for params in GRID:
    dd_win     = params["dd_win"]
    target_vol = params["target_vol"]
    label      = f"RSK07 {params['label']}"

    print(f"\n--- Testing {label} ---")

    rep = al.study_weights(
        label,
        lambda df, dw=dd_win, tv=target_vol: make_target(df, dd_win=dw, target_vol=tv),
        tfs=("1d", "12h"),
    )
    print(al.fmt(rep))

    # Score by min-asset hold-out Sharpe
    best_cell = max(rep["cells"], key=lambda c: c.get("min_asset_holdout_sharpe", -9))
    score = best_cell.get("min_asset_holdout_sharpe", -9.0)
    if score > best_score:
        best_score = score
        best_rep = rep
        best_label = label

# ---------------------------------------------------------------------------
# Final report
# ---------------------------------------------------------------------------
print("\n" + "=" * 60)
print(f"BEST CONFIG: {best_label}")
print(al.fmt(best_rep))
print("JSON:", al.as_json(best_rep))
