#!/usr/bin/env bash
# Alpha pipeline — full 2-quarter validation after fxcache lands.
#
# 1. Locate the freshly-built Q1+Q2 2024 fxcache (newest .fxcache in
#    test_data/feature-cache/ that is NOT the existing Q1-only file).
# 2. Train alpha_train_stacker on it → alpha_logits_cache_2q.bin.
# 3. Run 4-condition cross-quarter walk-forward CV:
#      A: baseline (trained Mamba2 + C51, no regime scale, fixed cost)
#      B: + cost randomization U[0.0625, 0.5]
#      C: + vol regime scale only
#      D: + both (the combined regime-defence hypothesis)
# 4. Aggregate Sharpe across conditions × folds.
#
# Run from the sp19-20-wr-first worktree root.

set -euo pipefail

REPO=/home/jgrusewski/Work/foxhunt/.worktrees/sp19-20-wr-first
CACHE_DIR=/home/jgrusewski/Work/foxhunt/test_data/feature-cache
FILL=config/ml/alpha_fill_coeffs.json
BIN_BT=./target/release/examples/alpha_baseline
BIN_STACK=./target/release/examples/alpha_train_stacker

cd "$REPO"

# Identify the Q1+Q2 fxcache: newest .fxcache whose hash is NOT the
# known Q1-only one.
Q1_HASH=9297017b6db6795f75e57be8aefb03e45e6427513f42c08e22521e58fa025d5e
NEW_CACHE=$(ls -t "$CACHE_DIR"/*.fxcache 2>/dev/null | grep -v "$Q1_HASH" | head -1)
if [[ -z "$NEW_CACHE" ]]; then
  echo "FATAL: no new fxcache found in $CACHE_DIR (only the Q1 one)"
  exit 1
fi
echo "Using fxcache: $NEW_CACHE"

# ----- 1. Train alpha_train_stacker → alpha_logits_cache_2q.bin -----
ALPHA_OUT=config/ml/alpha_logits_cache_2q.bin
if [[ ! -f "$ALPHA_OUT" ]]; then
  echo "===================================================================="
  echo "Stage 1: alpha_train_stacker on $NEW_CACHE"
  echo "===================================================================="
  SQLX_OFFLINE=true cargo build --release --example alpha_train_stacker -p ml-alpha
  SQLX_OFFLINE=true RUST_LOG=info "$BIN_STACK" \
    --fxcache-path "$NEW_CACHE" \
    --horizon 6000 \
    --seq-len 32 \
    --hidden-dim 64 \
    --state-dim 16 \
    --epochs 5 \
    --batch-size 128 \
    --lr 3e-3 \
    --train-frac 0.8 \
    --alpha-cache-out "$ALPHA_OUT"
fi
echo "Alpha cache: $ALPHA_OUT"

# ----- 2. Build backtest binary (in case of code changes) -----
SQLX_OFFLINE=true cargo build --release --example alpha_baseline -p ml >/dev/null

# ----- 3. Walk-forward CV across 4 conditions × 3 folds -----
# Fold A: offset=0,      window=1.2M → train 0..720K, eval 720K..1.2M
# Fold B: offset=900K,   window=1.2M → train 900K..1.62M, eval 1.62M..2.1M
# Fold C: offset=1.8M,   window=1.2M → train 1.8M..2.52M, eval 2.52M..3.0M
# (Assumes Q1+Q2 fxcache has ~3-6M bars total at MBP-10 resolution.)

# Cost randomization is the only opt-in left: `--train-cost-hi 0.5` enables
# uniform[0.0625, 0.5] per-epoch cost sampling. C51 / temporal / ISV-continual
# / regime-scale are now always-on properties of alpha_baseline; the A/B
# split is now just fixed-cost vs cost-randomised training.
declare -a CONDS=(
  "A_fixed:--window-k 16"
  "B_costrand:--window-k 16 --train-cost-hi 0.5"
)

declare -a FOLDS=(
  "A:0:1200000"
  "B:900000:1200000"
  "C:1800000:1200000"
)

mkdir -p /tmp/alpha_cv_results
for cond in "${CONDS[@]}"; do
  cname="${cond%%:*}"
  cflags="${cond##*:}"
  for fold in "${FOLDS[@]}"; do
    IFS=':' read -r fname offset window <<<"$fold"
    out="/tmp/alpha_cv_results/${cname}_fold-${fname}.json"
    if [[ -f "$out" ]]; then
      echo "Skip (exists): $out"; continue
    fi
    echo "===================================================================="
    echo "Cond $cname, fold $fname (offset=$offset, window=$window)"
    echo "===================================================================="
    SQLX_OFFLINE=true RUST_LOG=info "$BIN_BT" \
      --fxcache-path "$NEW_CACHE" \
      --alpha-cache "$ALPHA_OUT" \
      --fill-coeffs "$FILL" \
      --data-start-offset "$offset" \
      --max-snapshots "$window" \
      --train-frac 0.6 \
      $cflags \
      --out-path "$out"
  done
done

# ----- 4. Aggregate -----
echo
echo "===================================================================="
echo "Alpha pipeline cross-quarter walk-forward summary"
echo "===================================================================="
python3 - <<'PY'
import json, os, statistics
conds = ["A_fixed", "B_costrand"]
folds = ["A", "B", "C"]

# For each (cond, cost): list of best Sharpe across folds
by_cell = {}
for cond in conds:
    for f in folds:
        p = f"/tmp/alpha_cv_results/{cond}_fold-{f}.json"
        if not os.path.exists(p):
            continue
        with open(p) as fh:
            j = json.load(fh)
        for b in j["bins"]:
            c = b["cost"]
            key = (cond, c)
            best = by_cell.get(key, (-1e9, None, None, None))
            if b["sharpe_annualised"] > best[0]:
                by_cell[key] = (
                    b["sharpe_annualised"],
                    b["threshold"],
                    b["win_rate"],
                    f,
                )

# Per (cond, cost) — collect across folds for std-dev
fold_sharpes = {}  # (cond, cost) -> [Sharpe per fold]
for cond in conds:
    for f in folds:
        p = f"/tmp/alpha_cv_results/{cond}_fold-{f}.json"
        if not os.path.exists(p):
            continue
        with open(p) as fh:
            j = json.load(fh)
        by_cost = {}
        for b in j["bins"]:
            c = b["cost"]
            if c not in by_cost or b["sharpe_annualised"] > by_cost[c]:
                by_cost[c] = b["sharpe_annualised"]
        for c, sa in by_cost.items():
            fold_sharpes.setdefault((cond, c), []).append(sa)

costs = sorted({c for (_, c) in fold_sharpes.keys()})
print(f"{'cost':>8}  " + "  ".join(f"{c:>22}" for c in conds))
for c in costs:
    cells = []
    for cond in conds:
        sas = fold_sharpes.get((cond, c), [])
        if not sas:
            cells.append("        n/a          ")
        elif len(sas) == 1:
            cells.append(f"  {sas[0]:+7.2f}              ")
        else:
            m = statistics.mean(sas)
            sd = statistics.stdev(sas)
            cells.append(f"  {m:+7.2f} ± {sd:5.2f}    ")
    print(f"{c:>8.4f}  " + "  ".join(cells))

print()
print("Per-condition aggregate stats (across all folds × costs):")
for cond in conds:
    all_sas = []
    for c in costs:
        all_sas.extend(fold_sharpes.get((cond, c), []))
    if not all_sas:
        continue
    m = statistics.mean(all_sas)
    sd = statistics.stdev(all_sas) if len(all_sas) > 1 else 0.0
    print(f"  {cond:14}  mean Sharpe = {m:+7.2f}  std = {sd:5.2f}  n = {len(all_sas)}")
PY
