"""RSK03 — Inverse-vol Risk Parity (2-asset blend BTC+ETH).

IDEA: Scale each asset's exposure by the inverse of its realized volatility,
      normalized so the blended portfolio targets a fixed volatility (20%).
      This is risk-parity weighting: assets contribute equally to portfolio risk
      rather than receiving equal capital. Compare to fixed 50/50 exposure.

TWO sub-configs tested (small grid, <=4 param sets total over 2 TFs):
  Config A: vol_win=30d, leverage_cap=2.0 (standard)
  Config B: vol_win=60d, leverage_cap=2.0 (smoother vol estimate)

Approach:
  - For each bar, compute realized vol for BTC and ETH
  - Assign each an inverse-vol weight, normalize so sum of weights = 1
  - Scale combined weight to target_vol=20% using blended portfolio vol
  - Both assets always long (long-flat risk parity proxy)
  - Result is a single "blended" return series; reported per-asset for consistency,
    but the real edge is the BTC/ETH blend with risk-parity weighting

Since study_weights evaluates per-asset independently, we test two approaches:
  1. Per-asset vol-targeted weights (each asset gets its own vol-targeting)
  2. Cross-asset: for the combined report, we show the blend explicitly

For the per-asset evaluation compatible with altlib, we use vol_target per asset
(which IS inverse-vol risk parity when both assets are long) and let the library
evaluate each independently. The cross-asset blend is computed separately and
printed as the "combined" result.
"""

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

# ── Config grid ─────────────────────────────────────────────────────────────
# vol_win_days, leverage_cap
CONFIGS = [
    (30, 2.0),   # A: standard 30d window
    (60, 2.0),   # B: smoother 60d window
]


def make_target(vol_win_days: int, leverage_cap: float):
    """Returns a target_fn: df -> per-bar position.
    Long-only, vol-targeted using inverse realized vol.
    This is the per-asset component of inverse-vol RP.
    direction=+1 always (long-flat), then scaled by target_vol/realized_vol.
    """
    def target_fn(df):
        direction = np.ones(len(df))  # always long
        return al.vol_target(direction, df,
                             target_vol=0.20,
                             vol_win_days=vol_win_days,
                             leverage_cap=leverage_cap)
    return target_fn


def combined_rp_report(vol_win_days: int, leverage_cap: float, tf: str):
    """Compute blended BTC+ETH inverse-vol risk-parity returns.
    At each bar, blend BTC and ETH using inverse-vol weights normalized to 1,
    then apply an overall vol-target to the combined portfolio.
    Returns (sharpe_full, maxdd_full, sharpe_holdout, ret_full, ret_holdout).
    """
    df_btc = al.get("BTC", tf)
    df_eth = al.get("ETH", tf)

    # Align BTC and ETH by timestamp (BTC starts 2018, ETH 2019)
    df_btc = df_btc.set_index("datetime")
    df_eth = df_eth.set_index("datetime")
    common_idx = df_btc.index.intersection(df_eth.index)
    df_btc = df_btc.loc[common_idx].reset_index()
    df_eth = df_eth.loc[common_idx].reset_index()

    c_btc = df_btc["close"].values.astype(float)
    c_eth = df_eth["close"].values.astype(float)

    bpd = al.bars_per_day(df_btc)
    bpy = bpd * 365.25
    vol_win = max(2, vol_win_days * bpd)

    r_btc = al.simple_returns(c_btc)
    r_eth = al.simple_returns(c_eth)

    vol_btc = al.realized_vol(r_btc, vol_win, bpy)
    vol_eth = al.realized_vol(r_eth, vol_win, bpy)

    # Inverse-vol weights (causal: at i, vol computed using data<=i)
    # weight_i = (1/vol_i) / (1/vol_btc + 1/vol_eth)
    inv_btc = np.where((vol_btc > 0) & np.isfinite(vol_btc), 1.0 / vol_btc, np.nan)
    inv_eth = np.where((vol_eth > 0) & np.isfinite(vol_eth), 1.0 / vol_eth, np.nan)
    inv_sum = inv_btc + inv_eth
    w_btc = np.where(np.isfinite(inv_sum) & (inv_sum > 0), inv_btc / inv_sum, 0.5)
    w_eth = np.where(np.isfinite(inv_sum) & (inv_sum > 0), inv_eth / inv_sum, 0.5)

    # Blended portfolio return (before vol-targeting)
    r_blend = w_btc * r_btc + w_eth * r_eth

    # Now vol-target the blended return to 20%
    vol_blend = al.realized_vol(r_blend, vol_win, bpy)
    scal = np.where((vol_blend > 0) & np.isfinite(vol_blend), 0.20 / vol_blend, 0.0)
    pos = np.clip(scal, 0, leverage_cap)  # long-flat only
    pos = np.nan_to_num(pos, nan=0.0)

    # Honest shift: pos[i] decided at close[i], held during bar i+1
    pos_held = np.zeros(len(pos))
    pos_held[1:] = pos[:-1]

    gross = pos_held * r_blend
    turn = np.abs(np.diff(pos_held, prepend=0.0))
    fee_side = al.FEE_SIDE
    net = gross - fee_side * turn
    net[0] = 0.0

    # Use BTC index for timestamps (both aligned)
    idx = pd.DatetimeIndex(pd.to_datetime(df_btc["datetime"], utc=True))

    full = al._metrics_from_net(net, idx)
    hmask = idx >= al.HOLDOUT
    if hmask.sum() > 3:
        hold = al._metrics_from_net(net[hmask], idx[hmask])
    else:
        hold = dict(sharpe=0.0, ret=0.0, n=0)

    yearly = al._yearly(net, idx)
    return full, hold, yearly


# ── Main ────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
    # Run per-asset study (vol-targeted, long-flat per asset)
    # This is equivalent to inverse-vol RP: each asset separately scaled by 1/vol
    TFS = ("1d", "12h")

    best_rep = None
    best_holdout = -999

    for (vol_win, lev_cap) in CONFIGS:
        name = f"RSK03-InvVol-vw{vol_win}d"
        fn = make_target(vol_win, lev_cap)
        rep = al.study_weights(name, fn, tfs=TFS)

        verdict = rep["verdict"]
        hold_sh = verdict.get("best_holdout_sharpe", -999) or -999
        print(al.fmt(rep))
        print()

        if hold_sh > best_holdout:
            best_holdout = hold_sh
            best_rep = rep

    # Also print the combined BTC+ETH blend for the best config
    best_vw = CONFIGS[0][0] if best_rep is None else (
        int(best_rep["name"].split("vw")[1].replace("d", ""))
    )
    best_lev = CONFIGS[0][1]

    print("\n=== COMBINED BTC+ETH Blend (Inverse-Vol Risk Parity) ===")
    for tf in TFS:
        full, hold, yearly = combined_rp_report(best_vw, best_lev, tf)
        yr_str = " ".join(f"{y}:{d['ret']*100:+.0f}%" for y, d in list(yearly.items()))
        print(f"  TF {tf}: FULL Sh={full['sharpe']:+.2f} DD={full['maxdd']*100:.0f}% "
              f"ret={full['ret']*100:+.0f}% | HOLD Sh={hold.get('sharpe',0):+.2f} "
              f"ret={hold.get('ret',0)*100:+.0f}% | {yr_str}")

    print()
    print(al.fmt(best_rep))
    print("JSON:", al.as_json(best_rep))
