"""MIC03 — Volume-spike breakout
Hypothesis: Breakout of prior high CONFIRMED by volume z-score > threshold -> enter long at close.
Exit: TP, SL, or max_bars timeout.

Implementation:
  - Breakout: close[i] > donchian_high(win)[i] (prior win-bar high, shifted by 1 so fully causal)
  - Volume confirmation: volume z-score over vol_win bars > vol_thresh
  - Entry at close[i], direction = long only (breakouts on the upside)
  - TP = entry * (1 + tp_pct), SL = entry * (1 - sl_pct), max_bars timeout

Grid (<=4 param sets, 1d only -> total backtests = 4 * 2 assets = 8 <= 6... actually 8.
Reduce to 2 configs to stay within ~6 backtests and avoid slow fee sweeps):
  Config A: donchian 20, vol_win 20, vol_thresh 2.0, tp 3%, sl 1.5%, max_bars 10
  Config B: donchian 30, vol_win 30, vol_thresh 1.5, tp 4%, sl 2.0%, max_bars 15

Pick the best config by min_asset_holdout_sharpe.
"""

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


def make_entries_fn(don_win: int, vol_win: int, vol_thresh: float,
                    tp_pct: float, sl_pct: float, max_bars: int):
    def entries_fn(df):
        close = df["close"].values.astype(float)
        volume = df["volume"].values.astype(float)
        n = len(close)

        # Donchian upper channel: prior don_win-bar HIGH (shifted, causal)
        # Using high prices for breakout reference (breakout above prior high is more meaningful)
        high = df["high"].values.astype(float)
        don_hi = np.full(n, np.nan)
        # rolling max of high over don_win bars, then shift by 1 (prior bar)
        for i in range(don_win, n):
            don_hi[i] = np.max(high[i - don_win: i])  # excludes bar i -> causal

        # Volume z-score (causal): zscore of current volume vs rolling mean/std
        vol_mean = np.full(n, np.nan)
        vol_std = np.full(n, np.nan)
        for i in range(vol_win, n):
            v_window = volume[i - vol_win: i]  # excludes current bar
            vol_mean[i] = np.mean(v_window)
            vol_std[i] = np.std(v_window)

        vol_z = np.full(n, np.nan)
        mask = (vol_std > 0) & np.isfinite(vol_std)
        vol_z[mask] = (volume[mask] - vol_mean[mask]) / vol_std[mask]

        # Build entry list
        entries = [None] * n
        for i in range(don_win + vol_win, n):
            # Breakout condition: close breaks above prior don_win-bar high
            breakout = (np.isfinite(don_hi[i]) and close[i] > don_hi[i])
            # Volume confirmation
            vol_confirmed = (np.isfinite(vol_z[i]) and vol_z[i] > vol_thresh)

            if breakout and vol_confirmed:
                entry_px = close[i]  # fill at close[i]
                tp_px = entry_px * (1.0 + tp_pct)
                sl_px = entry_px * (1.0 - sl_pct)
                entries[i] = {
                    "dir": +1,
                    "tp": tp_px,
                    "sl": sl_px,
                    "max_bars": max_bars,
                }

        return entries

    return entries_fn


# Config A: tighter params
config_a = dict(don_win=20, vol_win=20, vol_thresh=2.0, tp_pct=0.03, sl_pct=0.015, max_bars=10)
# Config B: wider params
config_b = dict(don_win=30, vol_win=30, vol_thresh=1.5, tp_pct=0.04, sl_pct=0.02, max_bars=15)

configs = [
    ("MIC03-A", config_a),
    ("MIC03-B", config_b),
]

best_rep = None
best_score = -999.0

for cfg_name, cfg in configs:
    print(f"\n--- Running {cfg_name}: {cfg} ---")
    fn = make_entries_fn(**cfg)
    rep = al.study_signals(cfg_name, fn, tfs=("1d",))
    print(al.fmt(rep))
    print("JSON:", al.as_json(rep))

    score = rep["verdict"].get("best_holdout_sharpe", -999) or -999
    if score > best_score:
        best_score = score
        best_rep = rep
        best_rep["_config"] = cfg
        best_rep["_config_name"] = cfg_name

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