#!/usr/bin/env python3
"""
**Best Ideas** combined book — Approach B stack:

* **Dynamic VXX Regime Strategy Stack** (``pnl_stack``)
* **S055, S057, S059, S089** (literature Theta stack)
* **VRP** (4-regime daily ``pnl_vrp``)
* **Equity dip (split):** S&P 500 + Russell 3000 pct-drop sleeves on one **$100k** slot (``--with-equity-dip``; default **Sharpe weights** ~46% / ~54%)
* Optional **VXX long OTM call** tail hedge (``--with-vxx-long-call``; ``run_vxx_long_call_daily.py``)
* Optional **Macro AW options** (8 ETF sleeves, equal-weight; ``--with-macro-aw``; ``macro_aw_options_portfolio.py``)
* Optional **QS actionable ETF** (7-sleeve calendar/overnight/MR diversifiers; ``--with-qs-actionable-etf``; ``run_qs_actionable_etf_standard.py``)
* Optional **Sector momentum** (SPDR 12-1 rotation; ``--with-sector-momentum``; ``run_sector_momentum_standard.py``)
* Optional **Tactical All Weather** (SPY/TLT/IEF/GLD/DBC gates; ``--with-tactical-aw``; ``run_tactical_all_weather_standard.py``)
* Optional **Ride-rockets 50/50** (near_52w_high top25 + ten_rockets top10; ``--with-ride-rockets``; ``run_ride_rockets_5050_standard.py``)
* Optional **TSMOM / Managed Futures** (8-asset time-series momentum; ``--with-tsmom``; ``run_tsmom_managed_futures.py``)
* Optional **Johansen ETF triplets** (6-sleeve Chan stat-arb book; ``--with-johansen-etf``; ``run_johansen_triplet_etf_portfolio.py``)
* Optional **Zarattini 5m ORB** (Stocks in Play opening-range breakout; ``--with-orb-zarattini``; ``run_orb_zarattini.py``)
* Optional **MA slope S&P 500 top-N** (monthly dual-slope rotation + 2× ATR trail; ``--with-ma-slope-topn``)
* Optional **MA slope inverse SPY bear hedge** (SH, SPY regime gates; ``--with-ma-slope-inverse``)
* Shorthand ``--with-ma-slope`` enables both MA slope sleeves
* Optional **MA slope intraday** (Alpaca 5m confirm_entry_4b + top-5 + 20%% max weight; ``--with-ma-slope-intraday``; **on by default with** ``--stock-only``)

See ``BEST_IDEAS.md``.

Example (maintainer default — MTM + quarterly NAV rebase)::

    cd /Users/robzingale/trading_bot
    PYTHONUNBUFFERED=1 .venv/bin/python RenTech/strategy_stack/combine_best_ideas_stack.py \\
        --start 2016-01-04 --end 2026-06-18 --capital 100000 \\
        --mtm --nav-rebalance quarterly \\
        --with-equity-dip --with-vxx-long-call \\
        --out-prefix RenTech/data/logs/best_ideas_stack

Stock-only book (9 sleeves: vol edge, QS actionable-4, MA slope top-N + inverse SH + intraday — all default with ``--stock-only``)::

    cd /Users/robzingale/trading_bot
    PYTHONUNBUFFERED=1 .venv/bin/python RenTech/strategy_stack/combine_best_ideas_stack.py \\
        --stock-only --start 2016-01-04 --end 2026-06-18 --capital 100000 \\
        --fund-scale 1.5 --fund-nav-rebalance quarterly \\
        --out-prefix RenTech/data/logs/stock_only_ma_slope
"""

from __future__ import annotations

import argparse
import json
import math
import sys
from pathlib import Path

import numpy as np
import pandas as pd

_REPO = Path(__file__).resolve().parents[2]
if str(_REPO) not in sys.path:
    sys.path.insert(0, str(_REPO))

from RenTech.strategy_stack.portfolio_vrp_plus_vxx import _metrics_block
from RenTech.strategy_stack.run_vix_dynamic_scale import (
    apply_vix_dynamic_scale_to_panel,
    vix_scale_tiers_for_base,
)
from RenTech.strategy_stack.unified_margin_tracker import (
    build_sleeve_margin_map,
    build_combined_margin,
)

LOGS = _REPO / "RenTech" / "data" / "logs"
STACK_NAME = "Best Ideas Stack"
COMBINE_MODE = "stack_full_pnl"
COMBINE_MODE_NAV_Q = "stack_nav_quarterly_rebase"
COMBINE_MODE_FUND = "fund_nav_weighted_returns"
COMBINE_MODE_FUND_NAV_Q = "fund_nav_quarterly_sized"
DEBUG_LOG_PATH = _REPO / ".cursor" / "debug-e12856.log"
# Default sleeve capital weights (must sum to 1.0 among active sleeves).
DEFAULT_FUND_WEIGHTS: dict[str, float] = {
    "spy_theta": 0.40,
    "vxx_regime": 0.25,
    "equity_dip": 0.25,
    "vxx_long_call": 0.10,
}
# Extended book (macro + sector, no tactical) — sums to 1.0 among active sleeves.
FUND_WEIGHT_TABLE_EXTENDED: dict[str, float] = {
    "spy_theta": 0.28,
    "vxx_regime": 0.18,
    "equity_dip": 0.18,
    "vxx_long_call": 0.08,
    "macro_aw": 0.05,
    "sector_momentum": 0.06,
    "qs_actionable_etf": 0.04,
    "johansen_etf": 0.08,
    "orb_zarattini": 0.05,
    "ma_slope_topn": 0.08,
    "ma_slope_inverse": 0.05,
    "ma_slope_intraday": 0.06,
    "ride_rockets": 0.06,
}
# Tactical AW @ 20%; equity dip cut disproportionately to 5%; others scaled — no tsmom.
FUND_WEIGHT_TABLE_TACTICAL: dict[str, float] = {
    "spy_theta": 0.30,
    "vxx_regime": 0.1875,
    "equity_dip": 0.05,
    "vxx_long_call": 0.075,
    "macro_aw": 0.09375,
    "sector_momentum": 0.05375,
    "qs_actionable_etf": 0.04,
    "tactical_aw": 0.07,
    "johansen_etf": 0.08,
    "orb_zarattini": 0.05,
    "ma_slope_intraday": 0.06,
    "ride_rockets": 0.06,
}
# Extended + tactical + tsmom — sums to 1.0 when all rows active.
# 6-sleeve default (no equity_dip / sector_momentum): ~15% theta · ~18% vxx · ~40% tactical AW.
FUND_WEIGHT_TABLE_TACTICAL_TSMOM: dict[str, float] = {
    "spy_theta": 0.14,
    "vxx_regime": 0.1625,
    "equity_dip": 0.05,
    "vxx_long_call": 0.065,
    "macro_aw": 0.08125,
    "sector_momentum": 0.04125,
    "qs_actionable_etf": 0.04,
    "tactical_aw": 0.136,
    "ride_rockets": 0.06,
    "tsmom": 0.10,
    "johansen_etf": 0.08,
    "orb_zarattini": 0.05,
    "ma_slope_topn": 0.08,
    "ma_slope_inverse": 0.05,
    "ma_slope_intraday": 0.06,
}
# Legacy alias (tsmom-only extended preset without tactical).
FUND_WEIGHT_TABLE: dict[str, float] = {
    "spy_theta": 0.25,
    "vxx_regime": 0.16,
    "equity_dip": 0.16,
    "vxx_long_call": 0.07,
    "macro_aw": 0.09,
    "sector_momentum": 0.05,
    "qs_actionable_etf": 0.04,
    "ride_rockets": 0.06,
    "tsmom": 0.05,
    "johansen_etf": 0.08,
    "orb_zarattini": 0.05,
    "ma_slope_intraday": 0.06,
}
# Stock/ETF book only (no options). Default: ~65% tactical AW core + 4% QS SPY diversifiers.
FUND_WEIGHT_TABLE_STOCK_ONLY: dict[str, float] = {
    "tactical_aw": 0.615,
    "equity_dip": 0.15,
    "ride_rockets": 0.06,
    "tsmom": 0.095,
    "johansen_etf": 0.10,
    "qs_actionable_etf": 0.04,
}
# Stock-only + MA slope long top-N (8%) + inverse SH hedge (5%).
FUND_WEIGHT_TABLE_STOCK_ONLY_MA_SLOPE: dict[str, float] = {
    "tactical_aw": 0.47,
    "equity_dip": 0.13,
    "ride_rockets": 0.06,
    "tsmom": 0.08,
    "johansen_etf": 0.08,
    "ma_slope_topn": 0.08,
    "ma_slope_inverse": 0.05,
    "ma_slope_intraday": 0.06,
    "qs_actionable_etf": 0.04,
}
# Stock book + Volatility Edge ETN sleeve (SSRN 5316487). Vol edge 12.5%; CM dip 20%.
FUND_WEIGHT_TABLE_STOCK_VOL_EDGE: dict[str, float] = {
    "tactical_aw": 0.454375,
    "vol_edge": 0.125,
    "equity_dip": 0.20,
    "ride_rockets": 0.06,
    "tsmom": 0.0853125,
    "johansen_etf": 0.0953125,
    "qs_actionable_etf": 0.04,
}
# Stock + Vol Edge + MA slope sleeves (top-N 8%, inverse 5%).
FUND_WEIGHT_TABLE_STOCK_VOL_EDGE_MA_SLOPE: dict[str, float] = {
    "tactical_aw": 0.325,
    "vol_edge": 0.11,
    "equity_dip": 0.17,
    "ride_rockets": 0.06,
    "tsmom": 0.0725,
    "johansen_etf": 0.0825,
    "ma_slope_topn": 0.08,
    "ma_slope_inverse": 0.05,
    "ma_slope_intraday": 0.06,
    "qs_actionable_etf": 0.04,
}
# Same + SPY OTM bear-call credit spread (7%; trim tactical / vol edge / dip).
FUND_WEIGHT_TABLE_STOCK_VOL_EDGE_MA_SLOPE_BCC: dict[str, float] = {
    "tactical_aw": 0.295,
    "vol_edge": 0.095,
    "equity_dip": 0.155,
    "ride_rockets": 0.06,
    "tsmom": 0.0725,
    "johansen_etf": 0.0825,
    "ma_slope_topn": 0.08,
    "ma_slope_inverse": 0.05,
    "ma_slope_intraday": 0.06,
    "spy_bear_call": 0.07,
    "qs_actionable_etf": 0.04,
}
# Stock book + Vol Edge + market-neutral L/S equity momentum pod (default stock book).
FUND_WEIGHT_TABLE_STOCK_VOL_LS: dict[str, float] = {
    "tactical_aw": 0.462,
    "vol_edge": 0.1125,
    "equity_dip": 0.118125,
    "ls_equity": 0.10,
    "ride_rockets": 0.06,
    "tsmom": 0.077,
    "johansen_etf": 0.090375,
    "qs_actionable_etf": 0.04,
}
# Stock + Vol Edge + L/S equity + MA slope sleeves.
FUND_WEIGHT_TABLE_STOCK_VOL_LS_MA_SLOPE: dict[str, float] = {
    "tactical_aw": 0.334,
    "vol_edge": 0.098,
    "equity_dip": 0.103,
    "ls_equity": 0.087,
    "ride_rockets": 0.06,
    "tsmom": 0.067,
    "johansen_etf": 0.081,
    "ma_slope_topn": 0.08,
    "ma_slope_inverse": 0.05,
    "ma_slope_intraday": 0.06,
    "qs_actionable_etf": 0.04,
}
DEFAULT_VOL_EDGE_DAILY = LOGS / "volatility_edge_etn_evrp_boc_daily.csv"
DEFAULT_MA_SLOPE_INTRADAY_DAILY = (
    LOGS / "ma_slope_intraday_confirm4b_top5_cap20_standard_daily.csv"
)
DEFAULT_SPY_BEAR_CALL_DAILY = LOGS / "spy_bear_call_spread_standard_daily.csv"
DEFAULT_LS_EQUITY_DAILY = LOGS / "ls_equity_momentum_standard_daily.csv"
DEFAULT_MACRO_AW_DAILY = LOGS / "macro_aw_options_portfolio_eq_daily.csv"
DEFAULT_MACRO_AW_EQUITY_COL = "PORTFOLIO_EQUAL_WEIGHT"
DEFAULT_SECTOR_MOMENTUM_DAILY = LOGS / "sector_momentum_standard_daily.csv"
DEFAULT_QS_ACTIONABLE_ETF_DAILY = LOGS / "qs_actionable_etf_standard_daily.csv"
DEFAULT_QS_ACTIONABLE_4_DAILY = LOGS / "qs_actionable_4_standard_daily.csv"
DEFAULT_TACTICAL_AW_DAILY = LOGS / "tactical_aw_standard_daily.csv"
DEFAULT_TSMOM_DAILY = LOGS / "tsmom_managed_futures_daily.csv"
DEFAULT_RIDE_ROCKETS_DAILY = LOGS / "ride_rockets_5050_standard_daily.csv"
DEFAULT_JOHANSEN_ETF_DAILY = LOGS / "johansen_triplet_etf_standard_daily.csv"
DEFAULT_ORB_ZARATTINI_DAILY = LOGS / "orb_zarattini_5m_standard_daily.csv"
DEFAULT_MA_SLOPE_TOPN_DAILY = (
    LOGS / "ma_slope_sp500_top10_top10_dual_product_monthly_atr2x_daily.csv"
)
DEFAULT_MA_SLOPE_INVERSE_DAILY = (
    LOGS
    / "ma_slope_inverse_spy_balanced_bear_dual_or_sma200_spy_slope_or_sma200_invreq_top_n1_SH_daily.csv"
)

DEFAULT_VXX_DAILY = LOGS / "vxx_regime_mtm_2016_2026_dynamic_vxx_regime_stack_daily_mtm.csv"
DEFAULT_LIT_EQUITY = LOGS / "combine_lit_stack_S055_S057_S059_S089_VRP_equity.csv"
DEFAULT_LIT_MTM_DAILY = LOGS / "lit_stack_vrp_margin_daily.csv"
DEFAULT_SPY6_MTM_DAILY = LOGS / "best_ideas_spy6_margin_daily.csv"
DEFAULT_D6_EQUITY = LOGS / "best_ideas_d6_portfolio_equity_100k.csv"
DEFAULT_VRP_PNL = LOGS / "portfolio_opt_10dd_sharpe_fullvrp.csv"
DEFAULT_EQUITY_DIP_DAILY = LOGS / "sp500_dip_standard_daily.csv"
DEFAULT_STOCK_RELATIVE_DIP_HEDGED_DAILY = LOGS / "sp500_relative_dip_hedged_daily.csv"
DEFAULT_CRACKING_MARKETS_DIP_DAILY = LOGS / "cracking_markets_sp100_dip_daily.csv"
DEFAULT_RUSSELL3000_DIP_DAILY = LOGS / "russell3000_dip_standard_daily.csv"
# 10y experiment snapshot (buy_the_dip_experiment); used when --equity-dip-sharpe-weight (default).
EQUITY_DIP_SHARPE_SP500 = 1.38
EQUITY_DIP_SHARPE_RUSSELL3000 = 1.61
DEFAULT_SP500_DIP_TOP_N = 5
DEFAULT_RUSSELL3000_DIP_TOP_N = 10
LEGACY_SECTOR_DIP_DAILY = LOGS / "sector_dip_standard_daily.csv"
DEFAULT_VXX_LONG_CALL_DAILY = LOGS / "vxx_long_call_standard_daily.csv"
LIT_SIDS = ("S055", "S057", "S059", "S089")
D6_SIDS = ("D095", "D081", "D039", "D018", "D041", "D065")


def _daily_from_equity(eq: pd.Series) -> pd.Series:
    return eq.diff().fillna(0.0)


def _load_macro_aw_pnl(
    path: Path,
    idx: pd.DatetimeIndex,
    *,
    equity_col: str = DEFAULT_MACRO_AW_EQUITY_COL,
) -> pd.Series:
    """Daily $ PnL from macro AW portfolio equity curve (equal-weight book at $100k)."""
    df = pd.read_csv(path, index_col=0, parse_dates=True)
    df.index = pd.to_datetime(df.index).normalize()
    if equity_col not in df.columns:
        raise KeyError(f"{path} missing column {equity_col!r}")
    eq = df[equity_col].astype(np.float64).reindex(idx).ffill()
    return eq.diff().fillna(0.0)


def _load_macro_aw_daily_return(
    path: Path,
    idx: pd.DatetimeIndex,
    *,
    equity_col: str = DEFAULT_MACRO_AW_EQUITY_COL,
) -> pd.Series:
    df = pd.read_csv(path, index_col=0, parse_dates=True)
    df.index = pd.to_datetime(df.index).normalize()
    if equity_col not in df.columns:
        raise KeyError(f"{path} missing column {equity_col!r}")
    eq = df[equity_col].astype(np.float64).reindex(idx).ffill()
    if len(eq) == 0 or not np.isfinite(eq.iloc[0]) or eq.iloc[0] <= 0:
        return pd.Series(0.0, index=idx, dtype=np.float64)
    return eq.pct_change().fillna(0.0)


def _load_equity_dip_pnl(
    path: Path,
    idx: pd.DatetimeIndex,
    capital: float,
    *,
    weight: float = 1.0,
) -> pd.Series:
    df = pd.read_csv(path, parse_dates=["date"])
    df["date"] = pd.to_datetime(df["date"]).dt.normalize()
    df = df.set_index("date").sort_index()
    if "daily_pnl_usd" in df.columns:
        pnl = df["daily_pnl_usd"].astype(np.float64)
    elif "daily_ret" in df.columns:
        pnl = df["daily_ret"].astype(np.float64) * float(capital)
    else:
        raise KeyError(f"{path} needs daily_pnl_usd or daily_ret")
    return pnl.reindex(idx).fillna(0.0) * float(weight)


def _resolve_equity_dip_weights(
    *,
    sp500_frac: float | None,
    russell3000_frac: float | None,
    sharpe_weight: bool,
    equal_weight: bool,
) -> tuple[float, float]:
    if sp500_frac is not None and russell3000_frac is not None:
        w_sp, w_r3k = float(sp500_frac), float(russell3000_frac)
    elif sp500_frac is not None:
        w_sp = float(sp500_frac)
        w_r3k = 1.0 - w_sp
    elif russell3000_frac is not None:
        w_r3k = float(russell3000_frac)
        w_sp = 1.0 - w_r3k
    elif equal_weight:
        w_sp = w_r3k = 0.5
    elif sharpe_weight:
        tot = EQUITY_DIP_SHARPE_SP500 + EQUITY_DIP_SHARPE_RUSSELL3000
        w_sp = EQUITY_DIP_SHARPE_SP500 / tot
        w_r3k = EQUITY_DIP_SHARPE_RUSSELL3000 / tot
    else:
        w_sp = w_r3k = 0.5
    s = w_sp + w_r3k
    if s <= 0:
        raise ValueError("equity dip weights must sum to a positive number")
    return w_sp / s, w_r3k / s


def _add_equity_dip_to_panel(
    panel: pd.DataFrame,
    idx: pd.DatetimeIndex,
    capital: float,
    *,
    sp500_daily: Path,
    russell3000_daily: Path,
    sp500_only: bool,
    w_sp: float,
    w_r3k: float,
) -> dict[str, float]:
    """Add ``pnl_sp500_dip`` / ``pnl_russell3000_dip`` (weighted); return weight meta."""
    meta_w: dict[str, float] = {}
    if sp500_only:
        panel["pnl_sp500_dip"] = _load_equity_dip_pnl(sp500_daily, idx, capital, weight=1.0)
        meta_w = {"sp500_dip_frac": 1.0, "russell3000_dip_frac": 0.0}
    else:
        panel["pnl_sp500_dip"] = _load_equity_dip_pnl(
            sp500_daily, idx, capital, weight=w_sp
        )
        panel["pnl_russell3000_dip"] = _load_equity_dip_pnl(
            russell3000_daily, idx, capital, weight=w_r3k
        )
        meta_w = {"sp500_dip_frac": w_sp, "russell3000_dip_frac": w_r3k}
    return meta_w


def _debug_log(
    *,
    hypothesis_id: str,
    location: str,
    message: str,
    data: dict,
    run_id: str = "fund-mode",
) -> None:
    # #region agent log
    import time

    payload = {
        "sessionId": "e12856",
        "runId": run_id,
        "hypothesisId": hypothesis_id,
        "location": location,
        "message": message,
        "data": data,
        "timestamp": int(time.time() * 1000),
    }
    try:
        DEBUG_LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
        with DEBUG_LOG_PATH.open("a", encoding="utf-8") as fh:
            fh.write(json.dumps(payload, default=str) + "\n")
    except OSError:
        pass
    # #endregion


def _daily_return_from_pnl_or_equity(
    pnl: pd.Series,
    capital: float,
    equity: pd.Series | None = None,
) -> pd.Series:
    """Standalone backtest daily return (for fund-mode weighting)."""
    if equity is not None:
        eq = equity.astype(np.float64).reindex(pnl.index).ffill()
        if eq.iloc[0] > 0:
            return eq.pct_change().fillna(0.0)
    cap = float(capital)
    if cap <= 0:
        return pnl * 0.0
    return (pnl.astype(np.float64) / cap).fillna(0.0)


def _load_dip_daily_return(path: Path, idx: pd.DatetimeIndex) -> pd.Series:
    df = pd.read_csv(path, parse_dates=["date"])
    df["date"] = pd.to_datetime(df["date"]).dt.normalize()
    df = df.set_index("date").sort_index()
    if "daily_ret" in df.columns:
        ret = df["daily_ret"].astype(np.float64)
    elif "daily_pnl_usd" in df.columns:
        ret = df["daily_pnl_usd"].astype(np.float64) / 100_000.0
    else:
        raise KeyError(f"{path} needs daily_ret or daily_pnl_usd")
    return ret.reindex(idx).fillna(0.0)


def _align_stock_calendar_idx(start: str, end: str, *daily_paths: Path) -> pd.DatetimeIndex:
    idx: pd.DatetimeIndex | None = None
    for path in daily_paths:
        df = pd.read_csv(path, parse_dates=["date"])
        dates = pd.DatetimeIndex(pd.to_datetime(df["date"]).dt.normalize())
        idx = dates if idx is None else idx.intersection(dates)
    if idx is None or len(idx) == 0:
        raise SystemExit("stock-only: no overlapping sessions across stock daily CSVs")
    t0, t1 = pd.Timestamp(start), pd.Timestamp(end)
    idx = idx[(idx >= t0) & (idx <= t1)]
    if len(idx) < 100:
        raise SystemExit(f"stock-only: too few aligned sessions ({len(idx)}); check date range")
    return idx.sort_values()


def _fund_weight_preset(active: dict[str, bool], *, stock_only: bool = False) -> dict[str, float]:
    if stock_only:
        with_ma = (
            active.get("ma_slope_topn")
            or active.get("ma_slope_inverse")
            or active.get("ma_slope_intraday")
        )
        if active.get("ls_equity"):
            table = (
                FUND_WEIGHT_TABLE_STOCK_VOL_LS_MA_SLOPE
                if with_ma
                else FUND_WEIGHT_TABLE_STOCK_VOL_LS
            )
        elif active.get("vol_edge"):
            if active.get("spy_bear_call"):
                table = (
                    FUND_WEIGHT_TABLE_STOCK_VOL_EDGE_MA_SLOPE_BCC
                    if with_ma
                    else FUND_WEIGHT_TABLE_STOCK_VOL_EDGE
                )
            else:
                table = (
                    FUND_WEIGHT_TABLE_STOCK_VOL_EDGE_MA_SLOPE
                    if with_ma
                    else FUND_WEIGHT_TABLE_STOCK_VOL_EDGE
                )
        elif active.get("spy_bear_call"):
            table = (
                FUND_WEIGHT_TABLE_STOCK_VOL_EDGE_MA_SLOPE_BCC
                if with_ma
                else FUND_WEIGHT_TABLE_STOCK_VOL_EDGE
            )
        else:
            table = (
                FUND_WEIGHT_TABLE_STOCK_ONLY_MA_SLOPE
                if with_ma
                else FUND_WEIGHT_TABLE_STOCK_ONLY
            )
        return {k: table[k] for k in table if active.get(k)}
    optional = (
        "macro_aw", "sector_momentum", "qs_actionable_etf", "tsmom", "ride_rockets", "tactical_aw", "johansen_etf",
        "orb_zarattini", "ma_slope_topn", "ma_slope_inverse", "ma_slope_intraday",
    )
    if not any(active.get(k) for k in optional):
        return {k: DEFAULT_FUND_WEIGHTS[k] for k in DEFAULT_FUND_WEIGHTS if active.get(k)}
    if active.get("tactical_aw"):
        table = (
            FUND_WEIGHT_TABLE_TACTICAL_TSMOM
            if active.get("tsmom")
            else FUND_WEIGHT_TABLE_TACTICAL
        )
    elif active.get("tsmom"):
        table = FUND_WEIGHT_TABLE
    else:
        table = FUND_WEIGHT_TABLE_EXTENDED
    return {k: table[k] for k in table if active.get(k)}


def _resolve_fund_weights(
    *,
    spy: float | None,
    vxx: float | None,
    equity_dip: float | None,
    vxx_long_call: float | None,
    macro_aw: float | None,
    sector_momentum: float | None,
    qs_actionable_etf: float | None,
    tactical_aw: float | None,
    tsmom: float | None,
    ride_rockets: float | None,
    johansen_etf: float | None,
    orb_zarattini: float | None,
    ma_slope_topn: float | None,
    ma_slope_inverse: float | None,
    ma_slope_intraday: float | None,
    vol_edge: float | None,
    ls_equity: float | None,
    spy_bear_call: float | None,
    active: dict[str, bool],
    stock_only: bool = False,
) -> dict[str, float]:
    preset = _fund_weight_preset(active, stock_only=stock_only)
    raw: dict[str, float] = {}
    if active.get("spy_theta"):
        raw["spy_theta"] = float(spy if spy is not None else preset["spy_theta"])
    if active.get("vxx_regime"):
        raw["vxx_regime"] = float(vxx if vxx is not None else preset["vxx_regime"])
    if active.get("equity_dip"):
        raw["equity_dip"] = float(
            equity_dip if equity_dip is not None else preset["equity_dip"]
        )
    if active.get("vxx_long_call"):
        raw["vxx_long_call"] = float(
            vxx_long_call if vxx_long_call is not None else preset["vxx_long_call"]
        )
    if active.get("macro_aw"):
        raw["macro_aw"] = float(macro_aw if macro_aw is not None else preset["macro_aw"])
    if active.get("sector_momentum"):
        raw["sector_momentum"] = float(
            sector_momentum if sector_momentum is not None else preset["sector_momentum"]
        )
    if active.get("qs_actionable_etf"):
        raw["qs_actionable_etf"] = float(
            qs_actionable_etf
            if qs_actionable_etf is not None
            else preset["qs_actionable_etf"]
        )
    if active.get("tactical_aw"):
        raw["tactical_aw"] = float(
            tactical_aw if tactical_aw is not None else preset["tactical_aw"]
        )
    if active.get("tsmom"):
        raw["tsmom"] = float(tsmom if tsmom is not None else preset["tsmom"])
    if active.get("ride_rockets"):
        raw["ride_rockets"] = float(
            ride_rockets if ride_rockets is not None else preset.get("ride_rockets", 0.06)
        )
    if active.get("johansen_etf"):
        raw["johansen_etf"] = float(
            johansen_etf if johansen_etf is not None else preset["johansen_etf"]
        )
    if active.get("orb_zarattini"):
        raw["orb_zarattini"] = float(
            orb_zarattini if orb_zarattini is not None else preset["orb_zarattini"]
        )
    if active.get("ma_slope_topn"):
        raw["ma_slope_topn"] = float(
            ma_slope_topn if ma_slope_topn is not None else preset.get("ma_slope_topn", 0.08)
        )
    if active.get("ma_slope_inverse"):
        raw["ma_slope_inverse"] = float(
            ma_slope_inverse
            if ma_slope_inverse is not None
            else preset.get("ma_slope_inverse", 0.05)
        )
    if active.get("ma_slope_intraday"):
        raw["ma_slope_intraday"] = float(
            ma_slope_intraday
            if ma_slope_intraday is not None
            else preset.get("ma_slope_intraday", 0.06)
        )
    if active.get("vol_edge"):
        raw["vol_edge"] = float(
            vol_edge if vol_edge is not None else preset.get("vol_edge", 0.0)
        )
    if active.get("ls_equity"):
        raw["ls_equity"] = float(
            ls_equity if ls_equity is not None else preset.get("ls_equity", 0.0)
        )
    if active.get("spy_bear_call"):
        raw["spy_bear_call"] = float(
            spy_bear_call if spy_bear_call is not None else preset.get("spy_bear_call", 0.0)
        )
    s = sum(raw.values())
    if s <= 0:
        raise ValueError("fund-mode: no active sleeves or weights sum to zero")
    return {k: v / s for k, v in raw.items()}


def _fund_sleeve_base_pnl(
    panel: pd.DataFrame,
    fund_weights: dict[str, float],
    *,
    with_equity_dip: bool,
    sp500_only: bool,
) -> dict[str, pd.Series]:
    """Map fund sleeve keys → daily $ PnL at standalone ``--capital`` ($100k default)."""
    out: dict[str, pd.Series] = {}
    if "spy_theta" in fund_weights:
        out["spy_theta"] = panel["pnl_spy_theta_mtm"].astype(np.float64)
    if "vxx_regime" in fund_weights:
        out["vxx_regime"] = panel["pnl_vxx_regime_stack"].astype(np.float64)
    if "equity_dip" in fund_weights and with_equity_dip:
        if sp500_only:
            out["equity_dip"] = panel["pnl_sp500_dip"].astype(np.float64)
        else:
            out["equity_dip"] = (
                panel["pnl_sp500_dip"].astype(np.float64)
                + panel["pnl_russell3000_dip"].astype(np.float64)
            )
    if "vxx_long_call" in fund_weights and "pnl_vxx_long_call" in panel.columns:
        out["vxx_long_call"] = panel["pnl_vxx_long_call"].astype(np.float64)
    if "macro_aw" in fund_weights and "pnl_macro_aw" in panel.columns:
        out["macro_aw"] = panel["pnl_macro_aw"].astype(np.float64)
    if "sector_momentum" in fund_weights and "pnl_sector_momentum" in panel.columns:
        out["sector_momentum"] = panel["pnl_sector_momentum"].astype(np.float64)
    if "qs_actionable_etf" in fund_weights and "pnl_qs_actionable_etf" in panel.columns:
        out["qs_actionable_etf"] = panel["pnl_qs_actionable_etf"].astype(np.float64)
    if "tactical_aw" in fund_weights and "pnl_tactical_aw" in panel.columns:
        out["tactical_aw"] = panel["pnl_tactical_aw"].astype(np.float64)
    if "tsmom" in fund_weights and "pnl_tsmom" in panel.columns:
        out["tsmom"] = panel["pnl_tsmom"].astype(np.float64)
    if "ride_rockets" in fund_weights and "pnl_ride_rockets" in panel.columns:
        out["ride_rockets"] = panel["pnl_ride_rockets"].astype(np.float64)
    if "johansen_etf" in fund_weights and "pnl_johansen_etf" in panel.columns:
        out["johansen_etf"] = panel["pnl_johansen_etf"].astype(np.float64)
    if "orb_zarattini" in fund_weights and "pnl_orb_zarattini" in panel.columns:
        out["orb_zarattini"] = panel["pnl_orb_zarattini"].astype(np.float64)
    if "ma_slope_topn" in fund_weights and "pnl_ma_slope_topn" in panel.columns:
        out["ma_slope_topn"] = panel["pnl_ma_slope_topn"].astype(np.float64)
    if "ma_slope_inverse" in fund_weights and "pnl_ma_slope_inverse" in panel.columns:
        out["ma_slope_inverse"] = panel["pnl_ma_slope_inverse"].astype(np.float64)
    if "ma_slope_intraday" in fund_weights and "pnl_ma_slope_intraday" in panel.columns:
        out["ma_slope_intraday"] = panel["pnl_ma_slope_intraday"].astype(np.float64)
    if "vol_edge" in fund_weights and "pnl_vol_edge" in panel.columns:
        out["vol_edge"] = panel["pnl_vol_edge"].astype(np.float64)
    if "ls_equity" in fund_weights and "pnl_ls_equity" in panel.columns:
        out["ls_equity"] = panel["pnl_ls_equity"].astype(np.float64)
    if "spy_bear_call" in fund_weights and "pnl_spy_bear_call" in panel.columns:
        out["spy_bear_call"] = panel["pnl_spy_bear_call"].astype(np.float64)
    return out


def _apply_fund_mode_mtm_quarterly_sized(
    panel: pd.DataFrame,
    idx: pd.DatetimeIndex,
    capital: float,
    *,
    fund_weights: dict[str, float],
    fund_scale: float = 1.0,
    with_equity_dip: bool,
    sp500_only: bool,
    lit_mtm_daily: Path | None,
    vxx_regime_daily: Path | None,
    tsmom_daily: Path,
    ride_rockets_daily: Path,
    tactical_aw_daily: Path,
    macro_aw_daily: Path,
    equity_dip_daily: Path,
    vol_edge_daily: Path | None = None,
    ls_equity_daily: Path | None = None,
    orb_zarattini_daily: Path | None = None,
    margin_cap_frac: float = 0.85,
) -> pd.DataFrame:
    """
    Fund-mode with **quarterly NAV-referenced sizing** (realistic live book).

    At each calendar quarter open, set ``nav_ref = prior close NAV``. Each sleeve
    runs at notional ``w_i × fund_scale × nav_ref`` for that quarter; daily PnL and
    margin scale linearly from the standalone ``--capital`` backtest curves
    (``scaled_pnl = base_pnl × notional / capital``).
    """
    panel = panel.copy()
    ref_cap = float(capital)
    if ref_cap <= 0:
        raise ValueError("capital must be positive")

    base_pnl = _fund_sleeve_base_pnl(
        panel, fund_weights, with_equity_dip=with_equity_dip, sp500_only=sp500_only
    )
    active_sleeves = set(fund_weights.keys())
    macro_aw_trades_path = (
        Path(str(macro_aw_daily).replace("_daily.csv", "_trades.csv"))
        if macro_aw_daily is not None
        else Path("/nonexistent")
    )
    sleeve_margin_base = build_sleeve_margin_map(
        idx,
        active_sleeves=active_sleeves,
        lit_mtm_daily=lit_mtm_daily or DEFAULT_LIT_MTM_DAILY,
        vxx_regime_daily=vxx_regime_daily or DEFAULT_VXX_DAILY,
        tsmom_daily=tsmom_daily,
        vol_edge_daily=vol_edge_daily,
        ls_equity_daily=ls_equity_daily,
        tactical_aw_daily=tactical_aw_daily,
        macro_aw_trades_path=macro_aw_trades_path,
        equity_dip_daily=equity_dip_daily,
        orb_zarattini_daily=orb_zarattini_daily,
        fund_weights=fund_weights,
        fund_scale=1.0,
        capital=ref_cap,
    )

    qstarts = _quarter_start_dates(idx)
    n = len(idx)
    nav = ref_cap
    navs: list[float] = []
    pnls: list[float] = []
    nav_ref_vals: list[float] = []
    rebase_scale_vals: list[float] = []
    port_ret_vals: list[float] = []
    margin_combined_vals: list[float] = []
    margin_cap_mult_vals: list[float] = []
    sleeve_notional: dict[str, float] = {
        k: float(w) * float(fund_scale) * ref_cap for k, w in fund_weights.items()
    }
    nav_ref = ref_cap

    for i in range(n):
        dt = idx[i]
        if i > 0 and dt in qstarts:
            nav_ref = nav
            sleeve_notional = {
                k: float(w) * float(fund_scale) * nav_ref for k, w in fund_weights.items()
            }

        nav_ref_vals.append(nav_ref)
        rebase_scale_vals.append(nav_ref / ref_cap)

        day_pnl = 0.0
        day_margin = 0.0
        for key, w in fund_weights.items():
            if key not in base_pnl:
                continue
            notional = sleeve_notional[key]
            scale = notional / ref_cap
            day_pnl += float(base_pnl[key].iloc[i]) * scale
            if key in sleeve_margin_base:
                day_margin += float(sleeve_margin_base[key].iloc[i]) * scale
            panel.at[dt, f"notional_{key}_usd"] = notional
            panel.at[dt, f"pnl_fund_{key}"] = float(base_pnl[key].iloc[i]) * scale

        margin_cap_mult = 1.0
        if day_margin > 0.0 and nav > 0.0:
            max_margin = nav * margin_cap_frac
            if day_margin > max_margin:
                margin_cap_mult = max_margin / day_margin
                day_pnl *= margin_cap_mult

        pnls.append(day_pnl)
        nav = nav + day_pnl
        navs.append(nav)
        margin_combined_vals.append(day_margin * margin_cap_mult)
        margin_cap_mult_vals.append(margin_cap_mult)
        port_ret_vals.append(day_pnl / nav_ref if nav_ref > 0 else 0.0)

    prev_nav = pd.Series([ref_cap] + navs[:-1], index=idx, dtype=np.float64)
    panel["nav_ref_usd"] = nav_ref_vals
    panel["fund_nav_rebase_scale"] = rebase_scale_vals
    panel["equity_mtm_usd"] = navs
    panel["pnl_best_ideas_mtm"] = pd.Series(pnls, index=idx, dtype=np.float64)
    panel["portfolio_return_fund"] = pd.Series(port_ret_vals, index=idx, dtype=np.float64)
    panel["daily_return_mtm"] = panel["equity_mtm_usd"].pct_change().fillna(0.0)
    panel["margin_combined_usd"] = margin_combined_vals
    panel["margin_cap_multiplier"] = margin_cap_mult_vals
    panel["bp_utilization_pct"] = (
        np.array(margin_combined_vals) / prev_nav.to_numpy(dtype=np.float64) * 100.0
    )
    panel["effective_fund_scale"] = float(fund_scale)

    peak = panel["equity_mtm_usd"].cummax()
    dd_pct = float(((panel["equity_mtm_usd"] / peak - 1).min()) * 100)
    _debug_log(
        hypothesis_id="H1-fund-nav-q",
        location="combine_best_ideas_stack.py:_apply_fund_mode_mtm_quarterly_sized",
        message="fund_mode_quarterly_nav_summary",
        data={
            "end_nav": float(panel["equity_mtm_usd"].iloc[-1]),
            "total_return_pct": float(panel["equity_mtm_usd"].iloc[-1] / ref_cap - 1) * 100,
            "max_dd_pct": dd_pct,
            "fund_weights": fund_weights,
            "fund_scale": fund_scale,
            "mean_margin_usd": float(np.mean(margin_combined_vals)),
            "peak_margin_usd": float(np.max(margin_combined_vals)),
        },
    )
    return panel


def _apply_fund_mode_mtm_returns_blend(
    panel: pd.DataFrame,
    idx: pd.DatetimeIndex,
    capital: float,
    *,
    fund_weights: dict[str, float],
    fund_scale: float = 1.0,
    spy_equity: pd.Series,
    vxx_equity: pd.Series | None,
    with_equity_dip: bool,
    sp500_only: bool,
    equity_dip_daily: Path,
    russell3000_dip_daily: Path,
    w_sp: float,
    w_r3k: float,
    with_vxx_long_call: bool,
    vxx_long_call_daily: Path,
    with_macro_aw: bool,
    macro_aw_daily: Path,
    macro_aw_equity_col: str,
    with_sector_momentum: bool,
    sector_momentum_daily: Path,
    with_qs_actionable_etf: bool = False,
    qs_actionable_etf_daily: Path | None = None,
    with_tactical_aw: bool,
    tactical_aw_daily: Path,
    with_tsmom: bool,
    with_ride_rockets: bool,
    tsmom_daily: Path,
    ride_rockets_daily: Path,
    with_johansen_etf: bool = False,
    johansen_etf_daily: Path | None = None,
    with_orb_zarattini: bool = False,
    orb_zarattini_daily: Path | None = None,
    with_ma_slope_topn: bool = False,
    ma_slope_topn_daily: Path | None = None,
    with_ma_slope_inverse: bool = False,
    ma_slope_inverse_daily: Path | None = None,
    with_ma_slope_intraday: bool = False,
    ma_slope_intraday_daily: Path | None = None,
    with_vol_edge: bool = False,
    vol_edge_daily: Path | None = None,
    with_ls_equity: bool = False,
    ls_equity_daily: Path | None = None,
    with_spy_bear_call: bool = False,
    spy_bear_call_daily: Path | None = None,
    # Margin tracking (Fix 3)
    lit_mtm_daily: Path | None = None,
    vxx_regime_daily: Path | None = None,
    margin_cap_frac: float = 0.85,
) -> pd.DataFrame:
    """
    One NAV account: ``portfolio_ret = sum(w_i * sleeve_ret_i)``; NAV compounds daily.
    Sleeve returns come from each sleeve's standalone backtest (``pct_change`` on equity).
    """
    panel = panel.copy()
    ret_map: dict[str, pd.Series] = {}
    if "spy_theta" in fund_weights:
        ret_spy = _daily_return_from_pnl_or_equity(
            panel["pnl_spy_theta_mtm"], capital, spy_equity.reindex(idx)
        )
        ret_map["spy_theta"] = ret_spy
        panel["ret_spy_theta"] = ret_spy
    if "vxx_regime" in fund_weights:
        if vxx_equity is not None and vxx_equity.reindex(idx).ffill().iloc[0] > 0:
            ret_vxx = vxx_equity.reindex(idx).ffill().pct_change().fillna(0.0)
        else:
            ret_vxx = _daily_return_from_pnl_or_equity(panel["pnl_vxx_regime_stack"], capital)
        ret_map["vxx_regime"] = ret_vxx
        panel["ret_vxx_regime"] = ret_vxx
    if "equity_dip" in fund_weights and with_equity_dip:
        ret_sp = _load_dip_daily_return(equity_dip_daily, idx)
        if sp500_only:
            ret_dip = ret_sp
        else:
            ret_r3 = _load_dip_daily_return(russell3000_dip_daily, idx)
            ret_dip = ret_sp * w_sp + ret_r3 * w_r3k
        ret_map["equity_dip"] = ret_dip
        panel["ret_equity_dip"] = ret_dip
    if "vxx_long_call" in fund_weights and with_vxx_long_call:
        ret_lc = _load_dip_daily_return(vxx_long_call_daily, idx)
        ret_map["vxx_long_call"] = ret_lc
        panel["ret_vxx_long_call"] = ret_lc
    if "macro_aw" in fund_weights and with_macro_aw:
        ret_macro = _load_macro_aw_daily_return(
            macro_aw_daily, idx, equity_col=macro_aw_equity_col
        )
        ret_map["macro_aw"] = ret_macro
        panel["ret_macro_aw"] = ret_macro
    if "sector_momentum" in fund_weights and with_sector_momentum:
        ret_sec = _load_dip_daily_return(sector_momentum_daily, idx)
        ret_map["sector_momentum"] = ret_sec
        panel["ret_sector_momentum"] = ret_sec
    if (
        "qs_actionable_etf" in fund_weights
        and with_qs_actionable_etf
        and qs_actionable_etf_daily is not None
    ):
        ret_qs = _load_dip_daily_return(qs_actionable_etf_daily, idx)
        ret_map["qs_actionable_etf"] = ret_qs
        panel["ret_qs_actionable_etf"] = ret_qs
    if "tactical_aw" in fund_weights and with_tactical_aw:
        ret_tac = _load_dip_daily_return(tactical_aw_daily, idx)
        ret_map["tactical_aw"] = ret_tac
        panel["ret_tactical_aw"] = ret_tac
    if "tsmom" in fund_weights and with_tsmom:
        ret_tsmom = _load_dip_daily_return(tsmom_daily, idx)
        ret_map["tsmom"] = ret_tsmom
        panel["ret_tsmom"] = ret_tsmom
    if "ride_rockets" in fund_weights and with_ride_rockets:
        ret_rr = _load_dip_daily_return(ride_rockets_daily, idx)
        ret_map["ride_rockets"] = ret_rr
        panel["ret_ride_rockets"] = ret_rr
    if "johansen_etf" in fund_weights and with_johansen_etf and johansen_etf_daily is not None:
        ret_joh = _load_dip_daily_return(johansen_etf_daily, idx)
        ret_map["johansen_etf"] = ret_joh
        panel["ret_johansen_etf"] = ret_joh
    if "orb_zarattini" in fund_weights and with_orb_zarattini and orb_zarattini_daily is not None:
        ret_orb = _load_dip_daily_return(orb_zarattini_daily, idx)
        ret_map["orb_zarattini"] = ret_orb
        panel["ret_orb_zarattini"] = ret_orb
    if "ma_slope_topn" in fund_weights and with_ma_slope_topn and ma_slope_topn_daily is not None:
        ret_topn = _load_dip_daily_return(ma_slope_topn_daily, idx)
        ret_map["ma_slope_topn"] = ret_topn
        panel["ret_ma_slope_topn"] = ret_topn
    if (
        "ma_slope_inverse" in fund_weights
        and with_ma_slope_inverse
        and ma_slope_inverse_daily is not None
    ):
        ret_inv = _load_dip_daily_return(ma_slope_inverse_daily, idx)
        ret_map["ma_slope_inverse"] = ret_inv
        panel["ret_ma_slope_inverse"] = ret_inv
    if (
        "ma_slope_intraday" in fund_weights
        and with_ma_slope_intraday
        and ma_slope_intraday_daily is not None
    ):
        ret_intra = _load_dip_daily_return(ma_slope_intraday_daily, idx)
        ret_map["ma_slope_intraday"] = ret_intra
        panel["ret_ma_slope_intraday"] = ret_intra
    if "vol_edge" in fund_weights and with_vol_edge:
        ret_ve = _load_dip_daily_return(vol_edge_daily, idx)
        ret_map["vol_edge"] = ret_ve
        panel["ret_vol_edge"] = ret_ve
    if "ls_equity" in fund_weights and with_ls_equity:
        ret_ls = _load_dip_daily_return(ls_equity_daily, idx)
        ret_map["ls_equity"] = ret_ls
        panel["ret_ls_equity"] = ret_ls
    if "spy_bear_call" in fund_weights and with_spy_bear_call:
        ret_bcc = _load_dip_daily_return(spy_bear_call_daily, idx)
        ret_map["spy_bear_call"] = ret_bcc
        panel["ret_spy_bear_call"] = ret_bcc

    # ── Unscaled portfolio return (weights sum to 1.0) ───────────────────────
    port_ret_scale1 = pd.Series(0.0, index=idx, dtype=np.float64)
    for key, w in fund_weights.items():
        port_ret_scale1 = port_ret_scale1 + float(w) * ret_map[key]

    # ── Build combined margin estimate (Fix 3: UnifiedMarginTracker) ─────────
    active_sleeves = set(fund_weights.keys())
    macro_aw_trades_path = (
        Path(str(macro_aw_daily).replace("_daily.csv", "_trades.csv"))
        if macro_aw_daily is not None else Path("/nonexistent")
    )
    sleeve_margin_map = build_sleeve_margin_map(
        idx,
        active_sleeves=active_sleeves,
        lit_mtm_daily=lit_mtm_daily or DEFAULT_LIT_MTM_DAILY,
        vxx_regime_daily=vxx_regime_daily or DEFAULT_VXX_DAILY,
        tsmom_daily=tsmom_daily,
        vol_edge_daily=vol_edge_daily,
        tactical_aw_daily=tactical_aw_daily,
        macro_aw_trades_path=macro_aw_trades_path,
        equity_dip_daily=equity_dip_daily,
        ls_equity_daily=ls_equity_daily,
        orb_zarattini_daily=orb_zarattini_daily,
        fund_weights=fund_weights,
        fund_scale=fund_scale,
        capital=capital,
    )
    margin_df = build_combined_margin(
        idx, sleeve_margin_map, fund_weights=fund_weights, fund_scale=fund_scale
    )
    margin_scale1 = margin_df["margin_scale1_usd"].to_numpy(dtype=np.float64)

    # ── NAV loop with per-day dynamic scale cap ───────────────────────────────
    # On each day: effective_scale = min(fund_scale, margin_cap_frac × prev_NAV / margin_at_scale_1)
    # This prevents margin utilisation from exceeding margin_cap_frac of NAV.
    nav = float(capital)
    navs: list[float] = []
    pnls: list[float] = []
    port_ret_vals: list[float] = []
    eff_scale_vals: list[float] = []
    for i, r_s1 in enumerate(port_ret_scale1.to_numpy(dtype=np.float64)):
        m_s1 = margin_scale1[i]
        if m_s1 > 0.0:
            max_scale = (nav * margin_cap_frac) / m_s1
            eff_scale = min(fund_scale, max(0.0, max_scale))
        else:
            eff_scale = fund_scale
        eff_scale_vals.append(eff_scale)

        r = r_s1 * eff_scale
        port_ret_vals.append(r)
        pnl = nav * r
        pnls.append(pnl)
        nav = nav + pnl
        navs.append(nav)

    port_ret = pd.Series(port_ret_vals, index=idx, dtype=np.float64)
    eff_scale_series = pd.Series(eff_scale_vals, index=idx, dtype=np.float64)

    panel["portfolio_return_fund"] = port_ret
    panel["equity_mtm_usd"] = navs
    panel["pnl_best_ideas_mtm"] = pd.Series(pnls, index=idx)
    panel["daily_return_mtm"] = port_ret
    panel["effective_fund_scale"] = eff_scale_series
    panel["margin_combined_usd"] = margin_df["margin_combined_usd"].values
    panel["margin_scale1_usd"] = margin_df["margin_scale1_usd"].values
    panel["bp_utilization_pct"] = (
        margin_df["margin_combined_usd"].values
        / pd.Series(navs, index=idx).shift(1).fillna(capital).values
        * 100.0
    )
    for key, w in fund_weights.items():
        panel[f"pnl_fund_{key}"] = ret_map[key] * float(w) * panel["equity_mtm_usd"].shift(1).fillna(capital)

    peak = panel["equity_mtm_usd"].cummax()
    dd_pct = float(((panel["equity_mtm_usd"] / peak - 1).min()) * 100)
    _debug_log(
        hypothesis_id="H1-stacked-notional",
        location="combine_best_ideas_stack.py:_apply_fund_mode_mtm",
        message="fund_mode_nav_summary",
        data={
            "end_nav": float(panel["equity_mtm_usd"].iloc[-1]),
            "total_return_pct": float(panel["equity_mtm_usd"].iloc[-1] / capital - 1) * 100,
            "max_dd_pct": dd_pct,
            "fund_weights": fund_weights,
            "mean_abs_port_ret_bps": float(port_ret.abs().mean() * 10000),
        },
    )
    return panel


def _apply_fund_mode_mtm(
    panel: pd.DataFrame,
    idx: pd.DatetimeIndex,
    capital: float,
    *,
    fund_weights: dict[str, float],
    fund_scale: float = 1.0,
    fund_nav_rebalance: str = "quarterly",
    spy_equity: pd.Series,
    vxx_equity: pd.Series | None,
    with_equity_dip: bool,
    sp500_only: bool,
    equity_dip_daily: Path,
    russell3000_dip_daily: Path,
    w_sp: float,
    w_r3k: float,
    with_vxx_long_call: bool,
    vxx_long_call_daily: Path,
    with_macro_aw: bool,
    macro_aw_daily: Path,
    macro_aw_equity_col: str,
    with_sector_momentum: bool,
    sector_momentum_daily: Path,
    with_qs_actionable_etf: bool = False,
    qs_actionable_etf_daily: Path | None = None,
    with_tactical_aw: bool,
    tactical_aw_daily: Path,
    with_tsmom: bool,
    with_ride_rockets: bool,
    tsmom_daily: Path,
    ride_rockets_daily: Path,
    with_johansen_etf: bool = False,
    johansen_etf_daily: Path | None = None,
    with_orb_zarattini: bool = False,
    orb_zarattini_daily: Path | None = None,
    with_ma_slope_topn: bool = False,
    ma_slope_topn_daily: Path | None = None,
    with_ma_slope_inverse: bool = False,
    ma_slope_inverse_daily: Path | None = None,
    with_ma_slope_intraday: bool = False,
    ma_slope_intraday_daily: Path | None = None,
    with_vol_edge: bool = False,
    vol_edge_daily: Path | None = None,
    with_ls_equity: bool = False,
    ls_equity_daily: Path | None = None,
    with_spy_bear_call: bool = False,
    spy_bear_call_daily: Path | None = None,
    lit_mtm_daily: Path | None = None,
    vxx_regime_daily: Path | None = None,
    margin_cap_frac: float = 0.85,
) -> pd.DataFrame:
    """Dispatch fund-mode combine: quarterly sized (default) or legacy return blend."""
    if fund_nav_rebalance == "quarterly":
        return _apply_fund_mode_mtm_quarterly_sized(
            panel,
            idx,
            capital,
            fund_weights=fund_weights,
            fund_scale=fund_scale,
            with_equity_dip=with_equity_dip,
            sp500_only=sp500_only,
            lit_mtm_daily=lit_mtm_daily,
            vxx_regime_daily=vxx_regime_daily,
            tsmom_daily=tsmom_daily,
            ride_rockets_daily=ride_rockets_daily,
            vol_edge_daily=vol_edge_daily,
            ls_equity_daily=ls_equity_daily,
            tactical_aw_daily=tactical_aw_daily,
            macro_aw_daily=macro_aw_daily,
            equity_dip_daily=equity_dip_daily,
            orb_zarattini_daily=orb_zarattini_daily,
            margin_cap_frac=margin_cap_frac,
        )
    return _apply_fund_mode_mtm_returns_blend(
        panel,
        idx,
        capital,
        fund_weights=fund_weights,
        fund_scale=fund_scale,
        spy_equity=spy_equity,
        vxx_equity=vxx_equity,
        with_equity_dip=with_equity_dip,
        sp500_only=sp500_only,
        equity_dip_daily=equity_dip_daily,
        russell3000_dip_daily=russell3000_dip_daily,
        w_sp=w_sp,
        w_r3k=w_r3k,
        with_vxx_long_call=with_vxx_long_call,
        vxx_long_call_daily=vxx_long_call_daily,
        with_macro_aw=with_macro_aw,
        macro_aw_daily=macro_aw_daily,
        macro_aw_equity_col=macro_aw_equity_col,
        with_sector_momentum=with_sector_momentum,
        sector_momentum_daily=sector_momentum_daily,
        with_qs_actionable_etf=with_qs_actionable_etf,
        qs_actionable_etf_daily=qs_actionable_etf_daily,
        with_tactical_aw=with_tactical_aw,
        tactical_aw_daily=tactical_aw_daily,
        with_tsmom=with_tsmom,
        with_ride_rockets=with_ride_rockets,
        tsmom_daily=tsmom_daily,
        ride_rockets_daily=ride_rockets_daily,
        with_johansen_etf=with_johansen_etf,
        johansen_etf_daily=johansen_etf_daily,
        with_orb_zarattini=with_orb_zarattini,
        orb_zarattini_daily=orb_zarattini_daily,
        with_ma_slope_topn=with_ma_slope_topn,
        ma_slope_topn_daily=ma_slope_topn_daily,
        with_ma_slope_inverse=with_ma_slope_inverse,
        ma_slope_inverse_daily=ma_slope_inverse_daily,
        with_ma_slope_intraday=with_ma_slope_intraday,
        ma_slope_intraday_daily=ma_slope_intraday_daily,
        with_vol_edge=with_vol_edge,
        vol_edge_daily=vol_edge_daily,
        with_ls_equity=with_ls_equity,
        ls_equity_daily=ls_equity_daily,
        with_spy_bear_call=with_spy_bear_call,
        spy_bear_call_daily=spy_bear_call_daily,
        lit_mtm_daily=lit_mtm_daily,
        vxx_regime_daily=vxx_regime_daily,
        margin_cap_frac=margin_cap_frac,
    )


def _out_suffix(
    *,
    with_d6: bool,
    with_equity_dip: bool,
    equity_dip_sp500_only: bool,
    with_vxx_long_call: bool,
    with_macro_aw: bool,
    with_sector_momentum: bool,
    with_qs_actionable_etf: bool = False,
    with_tactical_aw: bool = False,
    with_tsmom: bool = False,
    with_ride_rockets: bool = False,
    with_johansen_etf: bool = False,
    with_orb_zarattini: bool = False,
    with_ma_slope_topn: bool = False,
    with_ma_slope_inverse: bool = False,
    with_ma_slope_intraday: bool = False,
    with_vol_edge: bool = False,
    with_ls_equity: bool = False,
    with_spy_bear_call: bool = False,
    nav_rebalance: str = "none",
    fund_mode: bool = False,
    fund_nav_rebalance: str = "quarterly",
    stock_only: bool = False,
    with_vix_dynamic_scale: bool = False,
) -> str:
    parts: list[str] = []
    if stock_only:
        parts.append("stock_only")
    if with_d6:
        parts.append("d6")
    if with_equity_dip:
        parts.append("sp500_dip" if equity_dip_sp500_only else "equity_dip")
    if with_vxx_long_call:
        parts.append("vxx_long_call")
    if with_macro_aw:
        parts.append("macro_aw")
    if with_sector_momentum:
        parts.append("sector_momentum")
    if with_qs_actionable_etf:
        parts.append("qs_actionable_etf")
    if with_tactical_aw:
        parts.append("tactical_aw")
    if with_tsmom:
        parts.append("tsmom")
    if with_ride_rockets:
        parts.append("ride_rockets")
    if with_johansen_etf:
        parts.append("johansen_etf")
    if with_orb_zarattini:
        parts.append("orb_zarattini")
    if with_ma_slope_topn:
        parts.append("ma_slope_topn")
    if with_ma_slope_inverse:
        parts.append("ma_slope_inverse")
    if with_ma_slope_intraday:
        parts.append("ma_slope_intraday")
    if with_vol_edge:
        parts.append("vol_edge")
    if with_ls_equity:
        parts.append("ls_equity")
    if with_spy_bear_call:
        parts.append("spy_bear_call")
    if fund_mode:
        parts.append("fund")
        if fund_nav_rebalance == "quarterly":
            parts.append("nav_q")
    elif nav_rebalance == "quarterly":
        parts.append("nav_q")
    if with_vix_dynamic_scale:
        parts.append("vix_dyn")
    if not parts:
        return ""
    return "_plus_" + "_plus_".join(parts)


def _quarter_start_dates(index: pd.DatetimeIndex) -> set[pd.Timestamp]:
    """First trading session of each calendar quarter in *index*."""
    out: set[pd.Timestamp] = set()
    prev_q: tuple[int, int] | None = None
    for dt in index:
        q = (int(dt.year), (int(dt.month) - 1) // 3)
        if q != prev_q:
            out.add(pd.Timestamp(dt).normalize())
            prev_q = q
    return out


def _apply_quarterly_nav_rebase(
    panel: pd.DataFrame,
    pnl_cols: list[str],
    initial_capital: float,
    *,
    total_col: str,
) -> pd.DataFrame:
    """
    At the **open** of each calendar quarter (first session in the index), set
    ``scale = NAV / initial_capital`` using prior close NAV; multiply that
    quarter's sleeve ``*_base`` dollar PnL by ``scale``.

    Adds ``nav_rebase_scale``, ``nav_usd``, and ``{col}_base`` for each rebased sleeve.
    """
    missing = [c for c in pnl_cols if c not in panel.columns]
    if missing:
        raise KeyError(f"nav rebase missing columns: {missing}")
    panel = panel.copy()
    for col in pnl_cols:
        panel[f"{col}_base"] = panel[col].astype(np.float64)

    qstarts = _quarter_start_dates(panel.index)
    n = len(panel)
    scales = np.ones(n, dtype=np.float64)
    nav = float(initial_capital)
    cur_scale = 1.0
    base_total = panel[pnl_cols].sum(axis=1).to_numpy(dtype=np.float64)
    dates = panel.index

    for i in range(n):
        dt = dates[i]
        if i > 0 and dt in qstarts:
            cur_scale = nav / float(initial_capital)
        scales[i] = cur_scale
        nav += base_total[i] * cur_scale

    scale_s = pd.Series(scales, index=panel.index, name="nav_rebase_scale")
    for col in pnl_cols:
        panel[col] = panel[f"{col}_base"] * scale_s
    extra_pnl = [
        c
        for c in panel.columns
        if c.startswith("pnl_")
        and not c.endswith("_base")
        and c not in pnl_cols
        and c != total_col
        and "best_ideas" not in c
    ]
    for col in extra_pnl:
        panel[col] = panel[col] * scale_s
    panel[total_col] = panel[pnl_cols].sum(axis=1)
    panel["nav_rebase_scale"] = scale_s
    panel["nav_usd"] = float(initial_capital) + panel[total_col].cumsum()
    return panel


def _stack_pnl_columns(
    *,
    with_d6: bool,
    with_equity_dip: bool,
    sp500_only: bool,
    with_vxx_long_call: bool,
    with_macro_aw: bool,
    with_sector_momentum: bool,
    with_qs_actionable_etf: bool = False,
    with_tactical_aw: bool = False,
    with_tsmom: bool = False,
    with_ride_rockets: bool = False,
    with_johansen_etf: bool = False,
    with_orb_zarattini: bool = False,
    with_ma_slope_topn: bool = False,
    with_ma_slope_inverse: bool = False,
    with_ma_slope_intraday: bool = False,
    with_vol_edge: bool = False,
    with_ls_equity: bool = False,
    with_spy_bear_call: bool = False,
    mtm: bool,
    stock_only: bool = False,
) -> list[str]:
    if stock_only:
        cols: list[str] = []
    elif mtm:
        cols = ["pnl_spy_theta_mtm", "pnl_vxx_regime_stack"]
    else:
        cols = ["pnl_vxx_regime_stack", *[f"pnl_{s}" for s in LIT_SIDS], "pnl_VRP"]
        if with_d6:
            cols.extend(f"pnl_{s}" for s in D6_SIDS)
    if with_equity_dip:
        cols.append("pnl_sp500_dip")
        if not sp500_only:
            cols.append("pnl_russell3000_dip")
    if with_vxx_long_call:
        cols.append("pnl_vxx_long_call")
    if with_macro_aw:
        cols.append("pnl_macro_aw")
    if with_sector_momentum:
        cols.append("pnl_sector_momentum")
    if with_qs_actionable_etf:
        cols.append("pnl_qs_actionable_etf")
    if with_tactical_aw:
        cols.append("pnl_tactical_aw")
    if with_tsmom:
        cols.append("pnl_tsmom")
    if with_ride_rockets:
        cols.append("pnl_ride_rockets")
    if with_johansen_etf:
        cols.append("pnl_johansen_etf")
    if with_orb_zarattini:
        cols.append("pnl_orb_zarattini")
    if with_ma_slope_topn:
        cols.append("pnl_ma_slope_topn")
    if with_ma_slope_inverse:
        cols.append("pnl_ma_slope_inverse")
    if with_ma_slope_intraday:
        cols.append("pnl_ma_slope_intraday")
    if with_vol_edge:
        cols.append("pnl_vol_edge")
    if with_ls_equity:
        cols.append("pnl_ls_equity")
    if with_spy_bear_call:
        cols.append("pnl_spy_bear_call")
    return cols


def _load_lit_daily_from_equity_csv(path: Path, idx: pd.DatetimeIndex) -> dict[str, pd.Series]:
    df = pd.read_csv(path, index_col=0, parse_dates=True)
    df.index = pd.to_datetime(df.index).normalize()
    out: dict[str, pd.Series] = {}
    for sid in LIT_SIDS:
        col = f"eq_{sid}"
        if col not in df.columns:
            raise KeyError(f"Missing {col} in {path}")
        out[sid] = _daily_from_equity(df[col]).reindex(idx).fillna(0.0)
    if "eq_VRP" in df.columns:
        out["VRP"] = _daily_from_equity(df["eq_VRP"]).reindex(idx).fillna(0.0)
    return out


def _yearly_table(
    df: pd.DataFrame,
    capital: float,
    *,
    pnl_col: str = "pnl_best_ideas",
    fund_mode: bool = False,
) -> pd.DataFrame:
    """
  Yearly stats with two return definitions (Approach B dollar PnL):

  * **return_pct_constant** — ``pnl_usd / capital`` with fixed ``capital``
    (default $100k). Valid only when ``pnl_col`` is **unsized** sleeve PnL
    (``nav_q`` research combine). **NaN in fund-mode** — sized account PnL
    divided by $100k is not a return (see ``return_pct_chained``).
  * **return_pct_chained** — ``pnl_usd / start-of-year equity``. Account
    return; use this for fund-mode / live NAV books.
    """
    if pnl_col not in df.columns:
        raise KeyError(f"yearly table needs column {pnl_col!r}")
    rows = []
    eq_chained = float(capital)
    cap = float(capital)
    for yr, g in df.groupby(df["date"].dt.year, sort=True):
        pnl = float(g[pnl_col].sum())
        if fund_mode:
            ret_constant = float("nan")
            dd_fixed = float("nan")
        else:
            ret_constant = (pnl / cap) * 100.0 if cap > 0 else 0.0
            intra_fixed = cap + g[pnl_col].cumsum()
            dd_fixed = (
                float(((intra_fixed / intra_fixed.cummax()) - 1).min()) * 100
                if len(intra_fixed)
                else 0.0
            )
        end_chained = eq_chained + pnl
        ret_chained = (end_chained / eq_chained - 1) * 100 if eq_chained > 0 else 0.0
        intra_chained = eq_chained + g[pnl_col].cumsum()
        dd_chained = (
            float(((intra_chained / intra_chained.cummax()) - 1).min()) * 100
            if len(intra_chained)
            else 0.0
        )
        rows.append(
            {
                "year": int(yr),
                "pnl_usd": round(pnl, 0),
                "return_pct_constant": round(ret_constant, 2),
                "max_dd_pct_constant": round(dd_fixed, 2),
                "return_pct_chained": round(ret_chained, 2),
                "start_equity_chained": round(eq_chained, 0),
                "end_equity_chained": round(end_chained, 0),
                "max_dd_pct_chained": round(dd_chained, 2),
                "sessions": len(g),
            }
        )
        eq_chained = end_chained
    return pd.DataFrame(rows)


def _maybe_apply_vix_dynamic_scale(
    panel: pd.DataFrame,
    args: argparse.Namespace,
    *,
    fund_mode: bool,
    start: str,
    end: str,
    capital: float,
    pnl_cols: list[str] | None = None,
) -> tuple[pd.DataFrame, dict]:
    """Apply VIX-regime leverage when ``--vix-dynamic-scale`` is set."""
    if not bool(getattr(args, "vix_dynamic_scale", False)):
        return panel, {}
    base_scale = float(getattr(args, "fund_scale", 1.0)) if fund_mode else 1.0
    sh, sm, sl = vix_scale_tiers_for_base(
        base_scale,
        scale_high=getattr(args, "vix_scale_high", None),
        scale_mid=getattr(args, "vix_scale_mid", None),
        scale_low=getattr(args, "vix_scale_low", None),
    )
    gate = not bool(getattr(args, "no_vix_spy_gate", False))
    print(
        f"VIX dynamic scale: calm→{sh:.2f}×  normal→{sm:.2f}×  "
        f"stressed bear→{sl:.2f}×  (base={base_scale:.2f}×, "
        f"{'SPY>SMA200 gate' if gate else 'no SPY gate'})",
        flush=True,
    )
    return apply_vix_dynamic_scale_to_panel(
        panel,
        start=start,
        end=end,
        capital=capital,
        base_scale=base_scale,
        vix_low=float(args.vix_low),
        vix_high=float(args.vix_high),
        scale_high=getattr(args, "vix_scale_high", None),
        scale_mid=getattr(args, "vix_scale_mid", None),
        scale_low=getattr(args, "vix_scale_low", None),
        smooth=int(args.vix_smooth),
        spy_trend_gate=gate,
        spy_sma=int(args.vix_spy_sma),
        pnl_cols=pnl_cols,
    )


def main() -> None:
    ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
    ap.add_argument("--start", default="2016-01-04")
    ap.add_argument("--end", default="2026-06-18")
    ap.add_argument("--capital", type=float, default=100_000.0)
    ap.add_argument("--vxx-daily", type=Path, default=DEFAULT_VXX_DAILY)
    ap.add_argument("--lit-equity-csv", type=Path, default=DEFAULT_LIT_EQUITY)
    ap.add_argument("--vrp-pnl-csv", type=Path, default=DEFAULT_VRP_PNL)
    ap.add_argument("--vrp-pnl-col", default="pnl_vrp")
    ap.add_argument(
        "--run-lit",
        action="store_true",
        help="Re-run combine_lit_stack_sleeves.py (slow) instead of --lit-equity-csv",
    )
    ap.add_argument(
        "--mtm",
        action="store_true",
        help=(
            "Use MTM daily PnL: lit4+VRP from evaluate_theta_margin daily equity; "
            "VXX from dynamic_vxx_regime_stack (per-sleeve MTM columns). "
            "Writes *_mtm_daily.csv instead of exit-day-only combine."
        ),
    )
    ap.add_argument(
        "--nav-rebalance",
        choices=("none", "quarterly"),
        default="none",
        help=(
            "Rebase all stack sleeve dollar PnL at each calendar quarter start: "
            "scale = NAV / --capital (prior close). Used for non-fund MTM combine only."
        ),
    )
    ap.add_argument(
        "--fund-mode",
        action="store_true",
        help=(
            "Single NAV account with fractional sleeve weights. Default sizing rebases "
            "each sleeve's notional at quarter open to weight × --fund-scale × NAV "
            "(see --fund-nav-rebalance)."
        ),
    )
    ap.add_argument(
        "--fund-nav-rebalance",
        choices=("quarterly", "none"),
        default="quarterly",
        help=(
            "How fund-mode sizes sleeves (default quarterly). "
            "quarterly: each sleeve notional = weight × fund-scale × prior-close NAV "
            "at calendar quarter open; PnL and margin scale from standalone backtests. "
            "none: legacy return-blend (fixed $100k sleeve sim, does not resize)."
        ),
    )
    ap.add_argument(
        "--fund-weight-spy",
        type=float,
        default=None,
        metavar="W",
        help="Capital weight for SPY theta+VRP MTM sleeve (default 0.40; renormalized)",
    )
    ap.add_argument(
        "--fund-weight-vxx",
        type=float,
        default=None,
        metavar="W",
        help="Capital weight for VXX regime stack (default 0.25)",
    )
    ap.add_argument(
        "--fund-weight-equity-dip",
        type=float,
        default=None,
        metavar="W",
        help="Capital weight for equity dip slot (default 0.25)",
    )
    ap.add_argument(
        "--fund-weight-vxx-long-call",
        type=float,
        default=None,
        metavar="W",
        help="Capital weight for VXX long call (default 0.10)",
    )
    ap.add_argument(
        "--fund-weight-macro-aw",
        type=float,
        default=None,
        metavar="W",
        help="Capital weight for Macro AW options book (default 0.11 when --with-macro-aw)",
    )
    ap.add_argument(
        "--with-macro-aw",
        action="store_true",
        help=(
            "Macro all-weather ETF options (8 sleeves equal-weight from "
            "macro_aw_options_portfolio.py)"
        ),
    )
    ap.add_argument(
        "--macro-aw-daily",
        type=Path,
        default=DEFAULT_MACRO_AW_DAILY,
        help="Macro AW portfolio daily equity CSV (default equal_weight run)",
    )
    ap.add_argument(
        "--macro-aw-equity-col",
        default=DEFAULT_MACRO_AW_EQUITY_COL,
        help="Portfolio equity column in --macro-aw-daily (default PORTFOLIO_EQUAL_WEIGHT)",
    )
    ap.add_argument(
        "--run-macro-aw",
        action="store_true",
        help="Regenerate --macro-aw-daily via macro_aw_options_portfolio.py (slow)",
    )
    ap.add_argument(
        "--with-sector-momentum",
        action="store_true",
        help="SPDR sector ETF 12-1 momentum rotation (top-k monthly; run_sector_momentum_standard.py)",
    )
    ap.add_argument(
        "--sector-momentum-daily",
        type=Path,
        default=DEFAULT_SECTOR_MOMENTUM_DAILY,
        help="Sector momentum daily CSV from run_sector_momentum_standard.py",
    )
    ap.add_argument(
        "--fund-weight-sector-momentum",
        type=float,
        default=None,
        metavar="W",
        help="Capital weight for sector momentum (default 0.10 when active)",
    )
    ap.add_argument(
        "--run-sector-momentum",
        action="store_true",
        help="Regenerate --sector-momentum-daily before combine",
    )
    ap.add_argument(
        "--with-qs-actionable-etf",
        action="store_true",
        help=(
            "QS actionable ETF diversifiers (actionable-7 on options book; "
            "actionable-4 on --stock-only). Off on options book unless set; "
            "on by default with --stock-only."
        ),
    )
    ap.add_argument(
        "--no-qs-actionable-etf",
        action="store_true",
        help="With --stock-only, disable QS actionable-4 SPY sleeve (default: enabled)",
    )
    ap.add_argument(
        "--qs-actionable-etf-daily",
        type=Path,
        default=DEFAULT_QS_ACTIONABLE_ETF_DAILY,
        help="QS actionable ETF daily CSV from run_qs_actionable_etf_standard.py",
    )
    ap.add_argument(
        "--fund-weight-qs-actionable-etf",
        type=float,
        default=None,
        metavar="W",
        help="Capital weight for QS actionable ETF sleeve (default 0.04 when active)",
    )
    ap.add_argument(
        "--run-qs-actionable-etf",
        action="store_true",
        help="Regenerate --qs-actionable-etf-daily before combine",
    )
    ap.add_argument(
        "--sector-momentum-top-k",
        type=int,
        default=3,
        help="Top SPDR sectors held each month when using --run-sector-momentum (default 3)",
    )
    ap.add_argument(
        "--with-tactical-aw",
        action="store_true",
        help=(
            "Tactical All Weather equity book (SPY/TLT/IEF/GLD/DBC gates; "
            "run_tactical_all_weather_standard.py)"
        ),
    )
    ap.add_argument(
        "--tactical-aw-daily",
        type=Path,
        default=DEFAULT_TACTICAL_AW_DAILY,
        help="Tactical AW daily CSV from run_tactical_all_weather_standard.py",
    )
    ap.add_argument(
        "--fund-weight-tactical-aw",
        type=float,
        default=None,
        metavar="W",
        help="Capital weight for tactical AW (default 0.20 when active; dip cut to 5%%)",
    )
    ap.add_argument(
        "--run-tactical-aw",
        action="store_true",
        help="Regenerate --tactical-aw-daily before combine",
    )
    ap.add_argument(
        "--with-tsmom",
        action="store_true",
        help=(
            "Time-series momentum / managed futures (8-asset 3/6/12m blend; "
            "run_tsmom_managed_futures.py)"
        ),
    )
    ap.add_argument(
        "--tsmom-daily",
        type=Path,
        default=DEFAULT_TSMOM_DAILY,
        help="TSMOM daily CSV from run_tsmom_managed_futures.py",
    )
    ap.add_argument(
        "--fund-weight-tsmom",
        type=float,
        default=None,
        metavar="W",
        help="Capital weight for TSMOM sleeve (default 0.11 when active; renormalized)",
    )
    ap.add_argument(
        "--with-ride-rockets",
        action="store_true",
        help=(
            "Ride-rockets 50/50 sleeve (near_52w_high top25 + ten_rockets top10; "
            "run_ride_rockets_5050_standard.py)"
        ),
    )
    ap.add_argument(
        "--ride-rockets-daily",
        type=Path,
        default=DEFAULT_RIDE_ROCKETS_DAILY,
        help="Ride-rockets 50/50 daily CSV from run_ride_rockets_5050_standard.py",
    )
    ap.add_argument(
        "--fund-weight-ride-rockets",
        type=float,
        default=None,
        metavar="W",
        help="Capital weight for ride-rockets sleeve (default 0.06 when active; renormalized)",
    )
    ap.add_argument(
        "--run-ride-rockets",
        action="store_true",
        help="Regenerate --ride-rockets-daily via run_ride_rockets_5050_standard.py before combine",
    )
    ap.add_argument(
        "--with-orb-zarattini",
        action="store_true",
        help=(
            "Zarattini 5m Opening Range Breakout on Stocks in Play "
            "(run_orb_zarattini.py). Not available with --stock-only."
        ),
    )
    ap.add_argument(
        "--orb-zarattini-daily",
        type=Path,
        default=DEFAULT_ORB_ZARATTINI_DAILY,
        help="ORB Zarattini daily CSV from run_orb_zarattini.py",
    )
    ap.add_argument(
        "--fund-weight-orb-zarattini",
        type=float,
        default=None,
        metavar="W",
        help="Capital weight for ORB Zarattini sleeve (default 0.05 when active; renormalized)",
    )
    ap.add_argument(
        "--with-vol-edge",
        action="store_true",
        help=(
            "Volatility Edge VIX ETN sleeve (SSRN 5316487 Strategy 3). "
            "On by default with --stock-only; use --no-vol-edge to disable."
        ),
    )
    ap.add_argument(
        "--no-vol-edge",
        action="store_true",
        help="With --stock-only, use legacy weights without Vol Edge ETN sleeve",
    )
    ap.add_argument(
        "--vol-edge-daily",
        type=Path,
        default=DEFAULT_VOL_EDGE_DAILY,
        help="Vol Edge daily CSV from run_volatility_edge_etn.py",
    )
    ap.add_argument(
        "--vol-edge-variant",
        default="evrp_boc",
        choices=["evrp_boc", "evrp_boc_sizing_v200"],
        help="Variant when using --run-vol-edge (default Strategy 3)",
    )
    ap.add_argument(
        "--fund-weight-vol-edge",
        type=float,
        default=None,
        metavar="W",
        help="Capital weight for Vol Edge sleeve (default 0.125 with --stock-only; renormalized)",
    )
    ap.add_argument(
        "--run-vol-edge",
        action="store_true",
        help="Regenerate --vol-edge-daily via run_volatility_edge_etn.py before combine",
    )
    ap.add_argument(
        "--with-ls-equity",
        action="store_true",
        help=(
            "Experimental market-neutral L/S equity momentum pod (AQR 12-1). "
            "Off by default — weak standalone in-sample; hedged dip + TSMOM already "
            "provide low-beta exposure."
        ),
    )
    ap.add_argument(
        "--ls-equity-daily",
        type=Path,
        default=DEFAULT_LS_EQUITY_DAILY,
        help="L/S equity momentum daily CSV from run_ls_equity_momentum_standard.py",
    )
    ap.add_argument(
        "--fund-weight-ls-equity",
        type=float,
        default=None,
        metavar="W",
        help="Capital weight for L/S equity pod (default 0.10 with --stock-only; renormalized)",
    )
    ap.add_argument(
        "--run-ls-equity",
        action="store_true",
        help="Regenerate --ls-equity-daily via run_ls_equity_momentum_standard.py before combine",
    )
    ap.add_argument(
        "--with-spy-bear-call",
        action="store_true",
        help=(
            "SPY OTM bear-call credit spread (short vol complement; Theta 15:45 from 2016). "
            "Requires --stock-only."
        ),
    )
    ap.add_argument(
        "--spy-bear-call-daily",
        type=Path,
        default=DEFAULT_SPY_BEAR_CALL_DAILY,
        help="SPY bear-call spread daily CSV from run_spy_bear_call_spread_standard.py",
    )
    ap.add_argument(
        "--fund-weight-spy-bear-call",
        type=float,
        default=None,
        metavar="W",
        help="Capital weight for SPY bear-call spread (default 0.07 when active; renormalized)",
    )
    ap.add_argument(
        "--run-spy-bear-call",
        action="store_true",
        help="Regenerate --spy-bear-call-daily via run_spy_bear_call_spread_standard.py before combine",
    )
    ap.add_argument(
        "--fund-scale",
        type=float,
        default=1.0,
        metavar="S",
        help=(
            "Leverage multiplier applied to the normalized fund-mode portfolio return "
            "each day (default 1.0 = no leverage). Use e.g. 2.5 to scale up to ~10%% "
            "max drawdown. Compounding is applied on the levered daily return."
        ),
    )
    ap.add_argument(
        "--vix-dynamic-scale",
        action="store_true",
        help=(
            "VIX-regime daily leverage: prior-day 5-EMA VIX < --vix-low → high scale; "
            "VIX ≥ --vix-high AND SPY < SMA200 → low scale; else mid. Tiers default "
            "proportional to --fund-scale (fund-mode) or 2.0/1.5/1.0 (nav_q)."
        ),
    )
    ap.add_argument(
        "--vix-low",
        type=float,
        default=15.0,
        help="VIX below this → high scale tier (default 15)",
    )
    ap.add_argument(
        "--vix-high",
        type=float,
        default=25.0,
        help="VIX at/above this + bear SPY → low scale tier (default 25)",
    )
    ap.add_argument(
        "--vix-scale-high",
        type=float,
        default=None,
        metavar="S",
        help="Override calm-regime scale (default: proportional to --fund-scale)",
    )
    ap.add_argument(
        "--vix-scale-mid",
        type=float,
        default=None,
        metavar="S",
        help="Override normal-regime scale (default: --fund-scale or 1.5)",
    )
    ap.add_argument(
        "--vix-scale-low",
        type=float,
        default=None,
        metavar="S",
        help="Override stressed-bear scale (default: proportional to --fund-scale)",
    )
    ap.add_argument(
        "--vix-smooth",
        type=int,
        default=5,
        help="EMA smoothing days for VIX signal (default 5)",
    )
    ap.add_argument(
        "--no-vix-spy-gate",
        action="store_true",
        help="Disable SPY>SMA200 gate (delever on high VIX even during recovery)",
    )
    ap.add_argument(
        "--vix-spy-sma",
        type=int,
        default=200,
        help="SPY SMA window for VIX stressed gate (default 200)",
    )
    ap.add_argument(
        "--run-tsmom",
        action="store_true",
        help="Regenerate --tsmom-daily via run_tsmom_managed_futures.py before combine",
    )
    ap.add_argument(
        "--with-johansen-etf",
        action="store_true",
        help=(
            "Johansen ETF triplet stat-arb (top-5 scan + EWA-EWC-IGE; "
            "run_johansen_triplet_etf_portfolio.py). On by default with --stock-only."
        ),
    )
    ap.add_argument(
        "--no-johansen-etf",
        action="store_true",
        help="With --stock-only, disable Johansen ETF sleeve (legacy stock book)",
    )
    ap.add_argument(
        "--johansen-etf-daily",
        type=Path,
        default=DEFAULT_JOHANSEN_ETF_DAILY,
        help="Johansen ETF triplet portfolio daily CSV",
    )
    ap.add_argument(
        "--fund-weight-johansen-etf",
        type=float,
        default=None,
        metavar="W",
        help="Capital weight for Johansen ETF sleeve (default 0.08 when active; renormalized)",
    )
    ap.add_argument(
        "--run-johansen-etf",
        action="store_true",
        help="Regenerate --johansen-etf-daily via run_johansen_triplet_etf_portfolio.py before combine",
    )
    ap.add_argument(
        "--with-ma-slope",
        action="store_true",
        help=(
            "MA slope S&P 500 top-N long + inverse SPY bear hedge (both sleeves). "
            "On by default with --stock-only."
        ),
    )
    ap.add_argument(
        "--no-ma-slope",
        action="store_true",
        help="With --stock-only, disable MA slope top-N + inverse SH sleeves (default: enabled)",
    )
    ap.add_argument(
        "--with-ma-slope-topn",
        action="store_true",
        help=(
            "S&P 500 MA slope top-10 monthly rotation with 2× ATR trail "
            "(run_ma_slope_sp500_topn_standard.py)"
        ),
    )
    ap.add_argument(
        "--with-ma-slope-inverse",
        action="store_true",
        help=(
            "Inverse SPY bear hedge (SH, SPY regime gates; "
            "run_ma_slope_inverse_spy_standard.py)"
        ),
    )
    ap.add_argument(
        "--ma-slope-topn-daily",
        type=Path,
        default=DEFAULT_MA_SLOPE_TOPN_DAILY,
        help="MA slope top-N daily CSV (default: top10 dual_product monthly atr2x)",
    )
    ap.add_argument(
        "--ma-slope-inverse-daily",
        type=Path,
        default=DEFAULT_MA_SLOPE_INVERSE_DAILY,
        help="MA slope inverse SPY bear hedge daily CSV",
    )
    ap.add_argument(
        "--fund-weight-ma-slope-topn",
        type=float,
        default=None,
        metavar="W",
        help="Capital weight for MA slope top-N sleeve (default 0.08 when active; renormalized)",
    )
    ap.add_argument(
        "--fund-weight-ma-slope-inverse",
        type=float,
        default=None,
        metavar="W",
        help="Capital weight for MA slope inverse hedge (default 0.05 when active; renormalized)",
    )
    ap.add_argument(
        "--run-ma-slope-topn",
        action="store_true",
        help="Regenerate --ma-slope-topn-daily via run_ma_slope_sp500_topn_standard.py before combine",
    )
    ap.add_argument(
        "--run-ma-slope-inverse",
        action="store_true",
        help="Regenerate --ma-slope-inverse-daily via run_ma_slope_inverse_spy_standard.py before combine",
    )
    ap.add_argument(
        "--with-ma-slope-intraday",
        action="store_true",
        help=(
            "Alpaca 5m MA-slope day-trade: confirm_entry_4b + top-5 + max 20%% weight "
            "(run_ma_slope_intraday_standard.py). On by default with --stock-only."
        ),
    )
    ap.add_argument(
        "--no-ma-slope-intraday",
        action="store_true",
        help="With --stock-only, disable MA slope intraday day-trade sleeve (default: enabled)",
    )
    ap.add_argument(
        "--ma-slope-intraday-daily",
        type=Path,
        default=DEFAULT_MA_SLOPE_INTRADAY_DAILY,
        help="MA slope intraday standard daily CSV (confirm4b top5 cap20)",
    )
    ap.add_argument(
        "--fund-weight-ma-slope-intraday",
        type=float,
        default=None,
        metavar="W",
        help="Capital weight for MA slope intraday sleeve (default 0.06 when active; renormalized)",
    )
    ap.add_argument(
        "--run-ma-slope-intraday",
        action="store_true",
        help="Regenerate --ma-slope-intraday-daily via run_ma_slope_intraday_standard.py before combine",
    )
    ap.add_argument(
        "--lit-mtm-daily",
        type=Path,
        default=DEFAULT_LIT_MTM_DAILY,
        help="lit4-vrp margin sim daily CSV (--mtm, without --with-d6)",
    )
    ap.add_argument(
        "--spy-mtm-daily",
        type=Path,
        default=DEFAULT_SPY6_MTM_DAILY,
        help="lit4+D6+VRP margin sim daily CSV (--mtm --with-d6)",
    )
    ap.add_argument(
        "--with-d6",
        action="store_true",
        help="Include D095,D081,D039,D018,D041,D065 (requires --spy-mtm-daily or --run-spy-mtm)",
    )
    ap.add_argument(
        "--d-equity-csv",
        type=Path,
        default=DEFAULT_D6_EQUITY,
        help="Per-sleeve D### equity CSV for exit-day combine (--with-d6)",
    )
    ap.add_argument(
        "--run-lit-mtm",
        action="store_true",
        help="Re-run evaluate_theta_margin --preset lit4-vrp before --mtm combine (very slow)",
    )
    ap.add_argument(
        "--run-spy-mtm",
        action="store_true",
        help="Re-run evaluate_theta_margin --preset best-ideas-spy (lit4+D6+VRP; very slow)",
    )
    ap.add_argument(
        "--run-d6-equity",
        action="store_true",
        help="Re-run portfolio_top_strategies for D6 sleeves (exit-day combine)",
    )
    ap.add_argument(
        "--with-equity-dip",
        action="store_true",
        help=(
            "Equity dip slot: S&P 500 + Russell 3000 pct-drop (≥3%%, hold 10d; "
            "default top 5 / top 10 names), split on one $100k notional (Sharpe weights)"
        ),
    )
    ap.add_argument(
        "--sp500-dip-top-n",
        type=int,
        default=DEFAULT_SP500_DIP_TOP_N,
        help="Max new S&P 500 dip entries per day (default 5)",
    )
    ap.add_argument(
        "--russell3000-dip-top-n",
        type=int,
        default=DEFAULT_RUSSELL3000_DIP_TOP_N,
        help="Max new Russell 3000 dip entries per day (default 10)",
    )
    ap.add_argument(
        "--sp500-dip-only",
        action="store_true",
        help="Use only S&P 500 dip (full equity-dip slot); no Russell 3000",
    )
    ap.add_argument(
        "--sp500-dip-frac",
        type=float,
        default=None,
        metavar="FRAC",
        help="Fraction of equity-dip slot for S&P 500 (default: Sharpe-weighted vs Russell)",
    )
    ap.add_argument(
        "--russell3000-dip-frac",
        type=float,
        default=None,
        metavar="FRAC",
        help="Fraction of equity-dip slot for Russell 3000 (default: 1 - sp500-dip-frac)",
    )
    ap.add_argument(
        "--equity-dip-equal-weight",
        action="store_true",
        help="50/50 split instead of Sharpe-weighted (overrides default Sharpe weights)",
    )
    ap.add_argument(
        "--no-equity-dip-sharpe-weight",
        action="store_true",
        help="Same as --equity-dip-equal-weight (50/50)",
    )
    ap.add_argument(
        "--with-sector-dip",
        action="store_true",
        help="Deprecated alias for --with-equity-dip (was SPDR sector RSI dip)",
    )
    ap.add_argument(
        "--equity-dip-daily",
        type=Path,
        default=DEFAULT_EQUITY_DIP_DAILY,
        help="Equity dip daily CSV from run_sp500_dip_standard.py",
    )
    ap.add_argument(
        "--sector-dip-daily",
        type=Path,
        default=None,
        help="Deprecated; use --equity-dip-daily (legacy sector CSV path if set)",
    )
    ap.add_argument(
        "--legacy-equity-dip",
        action="store_true",
        help=(
            "With --stock-only, use legacy long-only S&P + Russell 3000 dip split "
            "instead of SPY-hedged relative dip (default for stock book)"
        ),
    )
    ap.add_argument(
        "--run-equity-dip",
        action="store_true",
        help="Regenerate equity dip CSV(s) before combine",
    )
    ap.add_argument(
        "--equity-dip-yahoo-period",
        default="max",
        help="Yahoo history for dip runners (default max; 10y is too short for 2016+ SMA200)",
    )
    ap.add_argument(
        "--refresh-dip-cache",
        action="store_true",
        help="Force re-download Yahoo panels when using --run-equity-dip (slow)",
    )
    ap.add_argument(
        "--run-sector-dip",
        action="store_true",
        help="Deprecated alias for --run-equity-dip",
    )
    ap.add_argument(
        "--with-russell3000-dip",
        action="store_true",
        help="Deprecated: Russell 3000 is included in --with-equity-dip split (ignored if set)",
    )
    ap.add_argument(
        "--russell3000-dip-daily",
        type=Path,
        default=DEFAULT_RUSSELL3000_DIP_DAILY,
        help="Russell 3000 dip daily CSV",
    )
    ap.add_argument(
        "--run-russell3000-dip",
        action="store_true",
        help="Regenerate --russell3000-dip-daily before combine (slow first run)",
    )
    ap.add_argument(
        "--with-vxx-long-call",
        action="store_true",
        help="Add VXX long OTM call tail hedge (~5%% of stacked book PnL; run_vxx_long_call_daily.py)",
    )
    ap.add_argument(
        "--vxx-long-call-book-frac",
        type=float,
        default=0.05,
        help="Target long-call share of stacked-book PnL when using --run-vxx-long-call (default 5%%)",
    )
    ap.add_argument(
        "--vxx-long-call-daily",
        type=Path,
        default=DEFAULT_VXX_LONG_CALL_DAILY,
        help="VXX long call daily CSV from run_vxx_long_call_daily.py",
    )
    ap.add_argument(
        "--run-vxx-long-call",
        action="store_true",
        help="Regenerate --vxx-long-call-daily via run_vxx_long_call_daily.py before combine",
    )
    ap.add_argument(
        "--out-prefix",
        type=Path,
        default=LOGS / "best_ideas_stack",
    )
    ap.add_argument(
        "--stock-only",
        action="store_true",
        help=(
            "ETF/stock book: 8 sleeves — tactical AW, CM dip, QS actionable-4, TSMOM, "
            "Johansen ETF, Volatility Edge S3, MA slope top-N + inverse SH (all default). "
            "Disables SPY theta, VRP, VXX, Macro AW options. Sector momentum omitted "
            "(high SPY rho); opt in with --with-sector-momentum. Implies --mtm --fund-mode. "
            "Legacy 7-sleeve (no monthly MA slope): --no-ma-slope. "
            "Legacy 8-sleeve (no intraday): --no-ma-slope-intraday. "
            "Legacy 4-sleeve (no vol edge): "
            "--no-vol-edge. Disable QS SPY sleeve: --no-qs-actionable-etf."
        ),
    )
    args = ap.parse_args()
    stock_only = bool(args.stock_only)
    with_qs_actionable_etf = bool(
        args.with_qs_actionable_etf or (stock_only and not args.no_qs_actionable_etf)
    )
    qs_actionable_preset = "actionable-4" if stock_only else "actionable-7"
    with_equity_dip = bool(args.with_equity_dip or args.with_sector_dip or stock_only)
    with_macro_aw = bool(args.with_macro_aw) and not stock_only
    with_sector_momentum = bool(args.with_sector_momentum)
    with_tactical_aw = bool(args.with_tactical_aw or stock_only)
    with_tsmom = bool(args.with_tsmom or stock_only)
    with_ride_rockets = bool(args.with_ride_rockets)
    with_orb_zarattini = bool(args.with_orb_zarattini) and not stock_only
    ma_slope_default_stock = stock_only and not args.no_ma_slope
    with_ma_slope_topn = bool(
        args.with_ma_slope_topn or args.with_ma_slope or ma_slope_default_stock
    )
    with_ma_slope_inverse = bool(
        args.with_ma_slope_inverse or args.with_ma_slope or ma_slope_default_stock
    )
    with_ma_slope_intraday = bool(
        (args.with_ma_slope_intraday or stock_only) and not args.no_ma_slope_intraday
    )
    with_johansen_etf = bool(
        (args.with_johansen_etf or stock_only) and not args.no_johansen_etf
    )
    with_vol_edge = bool(
        args.with_vol_edge or (stock_only and not args.no_vol_edge)
    )
    with_ls_equity = bool(args.with_ls_equity)
    with_spy_bear_call = bool(args.with_spy_bear_call)
    if with_vol_edge and not stock_only:
        raise SystemExit("--with-vol-edge requires --stock-only (ETF/stock book add-on)")
    if with_ls_equity and not stock_only:
        raise SystemExit("--with-ls-equity requires --stock-only (ETF/stock book add-on)")
    if with_spy_bear_call and not stock_only:
        raise SystemExit("--with-spy-bear-call requires --stock-only (Theta SPY options add-on)")
    if args.no_vol_edge and not stock_only:
        raise SystemExit("--no-vol-edge applies only with --stock-only")
    legacy_equity_dip = bool(args.legacy_equity_dip)
    if stock_only and not legacy_equity_dip:
        sp500_only = True
    else:
        sp500_only = bool(args.sp500_dip_only)
    run_equity_dip = bool(args.run_equity_dip or args.run_sector_dip)
    if args.equity_dip_daily != DEFAULT_EQUITY_DIP_DAILY:
        equity_dip_daily = args.equity_dip_daily
    elif stock_only and not legacy_equity_dip:
        equity_dip_daily = DEFAULT_CRACKING_MARKETS_DIP_DAILY
    elif args.sector_dip_daily is None:
        equity_dip_daily = args.equity_dip_daily
    else:
        equity_dip_daily = args.sector_dip_daily
    if args.qs_actionable_etf_daily != DEFAULT_QS_ACTIONABLE_ETF_DAILY:
        qs_actionable_etf_daily = args.qs_actionable_etf_daily
    elif stock_only:
        qs_actionable_etf_daily = DEFAULT_QS_ACTIONABLE_4_DAILY
    else:
        qs_actionable_etf_daily = args.qs_actionable_etf_daily
    equal_w = bool(args.equity_dip_equal_weight or args.no_equity_dip_sharpe_weight)
    w_sp, w_r3k = _resolve_equity_dip_weights(
        sp500_frac=args.sp500_dip_frac,
        russell3000_frac=args.russell3000_dip_frac,
        sharpe_weight=with_equity_dip and not sp500_only and not equal_w,
        equal_weight=equal_w,
    )
    equity_dip_weight_meta: dict[str, float] = {}
    fund_mode = bool(args.fund_mode or stock_only)
    if stock_only and not args.mtm:
        args.mtm = True
    fund_nav_rebalance = str(getattr(args, "fund_nav_rebalance", "quarterly"))
    nav_rebalance = str(args.nav_rebalance)
    if fund_mode and nav_rebalance == "quarterly":
        print(
            "Note: stack --nav-rebalance quarterly applies to non-fund combine; "
            f"fund-mode uses --fund-nav-rebalance {fund_nav_rebalance}.",
            flush=True,
        )
    if fund_mode:
        combine_mode = (
            COMBINE_MODE_FUND_NAV_Q
            if fund_nav_rebalance == "quarterly"
            else COMBINE_MODE_FUND
        )
    elif nav_rebalance == "quarterly":
        combine_mode = COMBINE_MODE_NAV_Q
    else:
        combine_mode = COMBINE_MODE
    out_suffix = _out_suffix(
        with_d6=bool(args.with_d6),
        with_equity_dip=with_equity_dip,
        equity_dip_sp500_only=sp500_only,
        with_vxx_long_call=bool(args.with_vxx_long_call),
        with_macro_aw=with_macro_aw,
        with_sector_momentum=with_sector_momentum,
        with_qs_actionable_etf=with_qs_actionable_etf,
        with_tactical_aw=with_tactical_aw,
        with_tsmom=with_tsmom,
        with_ride_rockets=with_ride_rockets,
        with_johansen_etf=with_johansen_etf,
        with_orb_zarattini=with_orb_zarattini,
        with_ma_slope_topn=with_ma_slope_topn,
        with_ma_slope_inverse=with_ma_slope_inverse,
        with_ma_slope_intraday=with_ma_slope_intraday,
        with_vol_edge=with_vol_edge,
        with_ls_equity=with_ls_equity,
        with_spy_bear_call=with_spy_bear_call,
        nav_rebalance=nav_rebalance,
        fund_mode=fund_mode,
        fund_nav_rebalance=fund_nav_rebalance,
        stock_only=stock_only,
        with_vix_dynamic_scale=bool(getattr(args, "vix_dynamic_scale", False)),
    )
    fund_weights = _resolve_fund_weights(
        spy=args.fund_weight_spy,
        vxx=args.fund_weight_vxx,
        equity_dip=args.fund_weight_equity_dip,
        vxx_long_call=args.fund_weight_vxx_long_call,
        macro_aw=args.fund_weight_macro_aw,
        sector_momentum=args.fund_weight_sector_momentum,
        qs_actionable_etf=args.fund_weight_qs_actionable_etf,
        tactical_aw=args.fund_weight_tactical_aw,
        tsmom=args.fund_weight_tsmom,
        ride_rockets=args.fund_weight_ride_rockets,
        johansen_etf=args.fund_weight_johansen_etf,
        orb_zarattini=args.fund_weight_orb_zarattini,
        ma_slope_topn=args.fund_weight_ma_slope_topn,
        ma_slope_inverse=args.fund_weight_ma_slope_inverse,
        ma_slope_intraday=args.fund_weight_ma_slope_intraday,
        vol_edge=args.fund_weight_vol_edge,
        ls_equity=args.fund_weight_ls_equity,
        spy_bear_call=args.fund_weight_spy_bear_call,
        active={
            "spy_theta": not stock_only,
            "vxx_regime": not stock_only,
            "equity_dip": with_equity_dip,
            "vxx_long_call": bool(args.with_vxx_long_call) and not stock_only,
            "macro_aw": with_macro_aw,
            "sector_momentum": with_sector_momentum,
            "qs_actionable_etf": with_qs_actionable_etf,
            "tactical_aw": with_tactical_aw,
            "tsmom": with_tsmom,
            "ride_rockets": with_ride_rockets,
            "johansen_etf": with_johansen_etf,
            "orb_zarattini": with_orb_zarattini,
            "ma_slope_topn": with_ma_slope_topn,
            "ma_slope_inverse": with_ma_slope_inverse,
            "ma_slope_intraday": with_ma_slope_intraday,
            "vol_edge": with_vol_edge,
            "ls_equity": with_ls_equity,
            "spy_bear_call": with_spy_bear_call,
        },
        stock_only=stock_only,
    )
    if fund_mode:
        fund_scale_val = float(getattr(args, "fund_scale", 1.0))
        scale_note = f"  leverage scale={fund_scale_val:.2f}x  (gross notional {fund_scale_val*100:.0f}%)" if fund_scale_val != 1.0 else ""
        print(
            "Fund-mode sleeve weights (normalized): "
            + ", ".join(f"{k}={v:.1%}" for k, v in fund_weights.items())
            + scale_note
            + f"  sizing={fund_nav_rebalance}",
            flush=True,
        )

    if fund_mode and not args.mtm:
        raise SystemExit("--fund-mode requires --mtm (single NAV on MTM sleeve returns).")

    def _run_dip_runner(
        script: str,
        out_daily: Path,
        label: str,
        *,
        top_n: int,
        extra_args: list[str] | None = None,
    ) -> None:
        import subprocess

        dip_out = out_daily.expanduser().resolve()
        cmd = [
            sys.executable,
            str(_REPO / "RenTech/strategy_stack" / script),
            "--start",
            args.start,
            "--capital",
            str(args.capital),
            "--top-n",
            str(int(top_n)),
            "--out-prefix",
            str(dip_out.with_name(dip_out.stem.replace("_daily", ""))),
            "--yahoo-period",
            str(args.equity_dip_yahoo_period),
        ]
        if extra_args:
            cmd.extend(extra_args)
        if args.refresh_dip_cache:
            cmd += ["--refresh-cache"]
        if args.end:
            cmd += ["--end", args.end]
        print(f"Running {label} (top_n={top_n}) …", flush=True)
        subprocess.run(cmd, check=True, cwd=str(_REPO))

    if run_equity_dip:
        if stock_only and not legacy_equity_dip:
            _run_dip_runner(
                "run_sp500_dip_standard.py",
                equity_dip_daily,
                "CrackingMarkets dip (SP100, limit entry + multi-rule exits)",
                top_n=10,
                extra_args=[
                    "--universe",
                    "sp100",
                    "--execution-style",
                    "cracking_markets",
                    "--max-holdings",
                    "10",
                    "--hold-days",
                    "10",
                    "--rank-by",
                    "atr_norm",
                    "--limit-atr-mult",
                    "0.9",
                    "--profit-atr-mult",
                    "0.5",
                ],
            )
        else:
            _run_dip_runner(
                "run_sp500_dip_standard.py",
                equity_dip_daily,
                "S&P 500 pct-drop dip",
                top_n=int(args.sp500_dip_top_n),
            )
            if not sp500_only:
                _run_dip_runner(
                    "run_russell3000_dip_standard.py",
                    args.russell3000_dip_daily,
                    "Russell 3000 pct-drop dip",
                    top_n=int(args.russell3000_dip_top_n),
                )

    if args.run_russell3000_dip and not run_equity_dip:
        _run_dip_runner(
            "run_russell3000_dip_standard.py",
            args.russell3000_dip_daily,
            "Russell 3000 pct-drop dip",
            top_n=int(args.russell3000_dip_top_n),
        )

    if args.run_vxx_long_call:
        import subprocess

        lc_out = args.vxx_long_call_daily.expanduser().resolve()
        cmd = [
            sys.executable,
            str(_REPO / "RenTech/strategy_stack/run_vxx_long_call_daily.py"),
            "--start",
            args.start,
            "--capital",
            str(args.capital),
            "--book-pnl-frac",
            str(args.vxx_long_call_book_frac),
            "--out-prefix",
            str(lc_out.with_name(lc_out.stem.replace("_daily", ""))),
        ]
        if args.end:
            cmd += ["--end", args.end]
        print("Running VXX long call MTM sleeve …", flush=True)
        subprocess.run(cmd, check=True, cwd=str(_REPO))

    if args.run_macro_aw:
        import subprocess

        macro_prefix = args.macro_aw_daily.expanduser().resolve()
        macro_prefix = macro_prefix.with_name(
            macro_prefix.name.replace("_daily.csv", "")
        )
        cmd = [
            sys.executable,
            str(_REPO / "RenTech/strategy_stack/macro_aw_options_portfolio.py"),
            "--allocation",
            "equal_weight",
            "--capital",
            str(args.capital),
            "--start-date",
            args.start,
            "--out-prefix",
            str(macro_prefix),
        ]
        if args.end:
            cmd += ["--end-date", args.end]
        print("Running Macro AW options portfolio (8 sleeves, equal_weight) …", flush=True)
        subprocess.run(cmd, check=True, cwd=str(_REPO))

    if args.run_sector_momentum:
        import subprocess

        sm_out = args.sector_momentum_daily.expanduser().resolve()
        sm_prefix = sm_out.with_name(sm_out.name.replace("_daily.csv", ""))
        cmd = [
            sys.executable,
            str(_REPO / "RenTech/strategy_stack/run_sector_momentum_standard.py"),
            "--start",
            args.start,
            "--capital",
            str(args.capital),
            "--top-k",
            str(args.sector_momentum_top_k),
            "--out-prefix",
            str(sm_prefix),
        ]
        if args.end:
            cmd += ["--end", args.end]
        print("Running sector momentum standard (SPDR top-k rotation) …", flush=True)
        subprocess.run(cmd, check=True, cwd=str(_REPO))

    if args.run_qs_actionable_etf or (
        with_qs_actionable_etf and not qs_actionable_etf_daily.is_file()
    ):
        import subprocess

        qs_out = qs_actionable_etf_daily.expanduser().resolve()
        qs_prefix = qs_out.with_name(qs_out.name.replace("_daily.csv", ""))
        cmd = [
            sys.executable,
            str(_REPO / "RenTech/strategy_stack/run_qs_actionable_etf_standard.py"),
            "--preset",
            qs_actionable_preset,
            "--start",
            args.start,
            "--capital",
            str(args.capital),
            "--out-prefix",
            str(qs_prefix),
        ]
        if args.end:
            cmd += ["--end", args.end]
        print(f"Running QS {qs_actionable_preset} ETF standard …", flush=True)
        subprocess.run(cmd, check=True, cwd=str(_REPO))

    if args.run_tactical_aw:
        import subprocess

        tac_out = args.tactical_aw_daily.expanduser().resolve()
        tac_prefix = tac_out.with_name(tac_out.name.replace("_daily.csv", ""))
        cmd = [
            sys.executable,
            str(_REPO / "RenTech/strategy_stack/run_tactical_all_weather_standard.py"),
            "--start",
            args.start,
            "--capital",
            str(args.capital),
            "--out-prefix",
            str(tac_prefix),
        ]
        if args.end:
            cmd += ["--end", args.end]
        print("Running tactical All Weather standard …", flush=True)
        subprocess.run(cmd, check=True, cwd=str(_REPO))

    if with_equity_dip and not equity_dip_daily.is_file():
        if stock_only and not legacy_equity_dip:
            hint = (
                "  .venv/bin/python RenTech/strategy_stack/run_sp500_dip_standard.py "
                f"--start {args.start} --relative-spy-min 0.02 --rank-by rel_underperf "
                f"--hedge-spy --max-holdings 10 --out-prefix RenTech/data/logs/sp500_relative_dip_hedged\n"
                "or pass --run-equity-dip to this script."
            )
        else:
            hint = (
                "  .venv/bin/python RenTech/strategy_stack/run_sp500_dip_standard.py "
                f"--start {args.start}\n"
                "or pass --run-equity-dip to this script."
            )
        raise SystemExit(f"Missing {equity_dip_daily}; run:\n{hint}")

    if with_equity_dip and not sp500_only and not args.russell3000_dip_daily.is_file():
        raise SystemExit(
            f"Missing {args.russell3000_dip_daily}; run:\n"
            "  .venv/bin/python RenTech/strategy_stack/run_russell3000_dip_standard.py "
            f"--start {args.start}\n"
            "or pass --run-equity-dip to this script."
        )

    if with_equity_dip:
        if sp500_only:
            print("Equity dip: S&P 500 only (100% of slot)", flush=True)
        else:
            print(
                f"Equity dip split (one ${args.capital:,.0f} slot): "
                f"S&P {w_sp:.1%} / Russell 3000 {w_r3k:.1%}",
                flush=True,
            )

    if args.with_vxx_long_call and not args.vxx_long_call_daily.is_file():
        raise SystemExit(
            f"Missing {args.vxx_long_call_daily}; run:\n"
            "  .venv/bin/python RenTech/strategy_stack/run_vxx_long_call_daily.py "
            f"--start {args.start}\n"
            "or pass --run-vxx-long-call to this script."
        )

    if with_macro_aw and not args.macro_aw_daily.is_file():
        raise SystemExit(
            f"Missing {args.macro_aw_daily}; run:\n"
            "  .venv/bin/python RenTech/strategy_stack/macro_aw_options_portfolio.py "
            f"--allocation equal_weight --capital {int(args.capital)} "
            f"--start-date {args.start} "
            f"--out-prefix RenTech/data/logs/macro_aw_options_portfolio_eq\n"
            "or pass --run-macro-aw to this script."
        )

    if with_macro_aw:
        print(
            f"Macro AW: equal-weight 8-sleeve book from {args.macro_aw_daily.name} "
            f"({args.macro_aw_equity_col})",
            flush=True,
        )

    if with_sector_momentum and not args.sector_momentum_daily.is_file():
        raise SystemExit(
            f"Missing {args.sector_momentum_daily}; run:\n"
            "  .venv/bin/python RenTech/strategy_stack/run_sector_momentum_standard.py "
            f"--start {args.start}\n"
            "or pass --run-sector-momentum to this script."
        )

    if with_sector_momentum:
        print(
            f"Sector momentum: SPDR rotation from {args.sector_momentum_daily.name}",
            flush=True,
        )

    if with_qs_actionable_etf and not qs_actionable_etf_daily.is_file():
        raise SystemExit(
            f"Missing {qs_actionable_etf_daily}; run:\n"
            f"  .venv/bin/python RenTech/strategy_stack/run_qs_actionable_etf_standard.py "
            f"--preset {qs_actionable_preset} --start {args.start}\n"
            "or pass --run-qs-actionable-etf to this script."
        )

    if with_qs_actionable_etf:
        print(
            f"QS {qs_actionable_preset}: calendar/overnight diversifiers from "
            f"{qs_actionable_etf_daily.name}",
            flush=True,
        )

    if with_tactical_aw and not args.tactical_aw_daily.is_file():
        raise SystemExit(
            f"Missing {args.tactical_aw_daily}; run:\n"
            "  .venv/bin/python RenTech/strategy_stack/run_tactical_all_weather_standard.py "
            f"--start {args.start}\n"
            "or pass --run-tactical-aw to this script."
        )

    if with_tactical_aw:
        print(
            f"Tactical AW: macro ETF gates from {args.tactical_aw_daily.name}",
            flush=True,
        )

    if args.run_tsmom:
        import subprocess

        tsmom_out = args.tsmom_daily.expanduser().resolve()
        tsmom_prefix = tsmom_out.with_name(tsmom_out.name.replace("_daily.csv", ""))
        cmd = [
            sys.executable,
            str(_REPO / "RenTech/strategy_stack/run_tsmom_managed_futures.py"),
            "--start",
            args.start,
            "--capital",
            str(args.capital),
            "--out-prefix",
            str(tsmom_prefix),
        ]
        if args.end:
            cmd += ["--end", args.end]
        print("Running TSMOM / managed futures …", flush=True)
        subprocess.run(cmd, check=True, cwd=str(_REPO))

    if with_tsmom and not args.tsmom_daily.is_file():
        raise SystemExit(
            f"Missing {args.tsmom_daily}; run:\n"
            "  .venv/bin/python RenTech/strategy_stack/run_tsmom_managed_futures.py "
            f"--start {args.start}\n"
            "or pass --run-tsmom to this script."
        )

    if with_tsmom:
        print(
            f"TSMOM / managed futures: 8-asset 3/6/12m blend from {args.tsmom_daily.name}",
            flush=True,
        )

    if args.run_ride_rockets:
        import subprocess

        rr_out = args.ride_rockets_daily.expanduser().resolve()
        rr_prefix = rr_out.with_name(rr_out.name.replace("_daily.csv", ""))
        cmd = [
            str(_REPO / ".venv/bin/python"),
            str(_REPO / "RenTech/strategy_stack/run_ride_rockets_5050_standard.py"),
            "--start",
            args.start,
            "--end",
            args.end or "2026-04-02",
            "--capital",
            str(int(args.capital)),
            "--reuse-equity-cache",
            "--out-prefix",
            str(rr_prefix),
        ]
        print("Running ride-rockets 50/50 …", flush=True)
        subprocess.check_call(cmd)

    if with_ride_rockets and not args.ride_rockets_daily.is_file():
        raise SystemExit(
            f"Missing {args.ride_rockets_daily}; run:\n"
            "  .venv/bin/python RenTech/strategy_stack/run_ride_rockets_5050_standard.py "
            "--start 2016-01-04 --end 2026-04-02 --reuse-equity-cache\n"
            "or pass --run-ride-rockets to this script."
        )

    if with_ride_rockets:
        print(
            f"Ride-rockets 50/50: near_52w_high top25 + ten_rockets top10 from {args.ride_rockets_daily.name}",
            flush=True,
        )


    if with_orb_zarattini and not args.orb_zarattini_daily.is_file():
        raise SystemExit(
            f"Missing {args.orb_zarattini_daily}; run:\n"
            "  .venv/bin/python RenTech/strategy_stack/run_orb_zarattini.py "
            f"--start {args.start}\n"
            "or pass an existing --orb-zarattini-daily CSV."
        )

    if with_orb_zarattini:
        print(
            f"ORB Zarattini 5m: Stocks in Play from {args.orb_zarattini_daily.name}",
            flush=True,
        )

    if args.run_johansen_etf:
        import subprocess

        joh_out = args.johansen_etf_daily.expanduser().resolve()
        joh_prefix = joh_out.with_name(joh_out.name.replace("_daily.csv", ""))
        cmd = [
            sys.executable,
            str(_REPO / "RenTech/strategy_stack/run_johansen_triplet_etf_portfolio.py"),
            "--start",
            args.start,
            "--end",
            args.end,
            "--capital",
            str(args.capital),
            "--out-prefix",
            str(joh_prefix),
        ]
        print("Regenerating Johansen ETF triplet portfolio …", flush=True)
        subprocess.run(cmd, check=True, cwd=str(_REPO))

    if with_johansen_etf and not args.johansen_etf_daily.is_file():
        raise SystemExit(
            f"Missing {args.johansen_etf_daily}; run:\n"
            "  .venv/bin/python RenTech/strategy_stack/run_johansen_triplet_etf_portfolio.py "
            f"--start {args.start} --end {args.end} --capital {int(args.capital)}\n"
            "or pass --run-johansen-etf to this script."
        )
    if with_johansen_etf:
        print(
            f"Johansen ETF triplets: 6-sleeve Chan book from {args.johansen_etf_daily.name}",
            flush=True,
        )

    if args.run_ma_slope_topn or (
        with_ma_slope_topn and not args.ma_slope_topn_daily.is_file()
    ):
        import subprocess

        topn_prefix = LOGS / "ma_slope_sp500_top10"
        cmd = [
            sys.executable,
            str(_REPO / "RenTech/strategy_stack/run_ma_slope_sp500_topn_standard.py"),
            "--start",
            args.start,
            "--top-n",
            "10",
            "--out-prefix",
            str(topn_prefix),
        ]
        if args.end:
            cmd += ["--end", args.end]
        print("Running MA slope S&P 500 top-10 (dual_product monthly, ATR 2×) …", flush=True)
        subprocess.run(cmd, check=True, cwd=str(_REPO))

    if with_ma_slope_topn and not args.ma_slope_topn_daily.is_file():
        raise SystemExit(
            f"Missing {args.ma_slope_topn_daily}; run:\n"
            "  .venv/bin/python RenTech/strategy_stack/run_ma_slope_sp500_topn_standard.py "
            f"--start {args.start} --top-n 10\n"
            "or pass --run-ma-slope-topn to this script."
        )
    if with_ma_slope_topn:
        print(
            f"MA slope top-N: S&P 500 dual-slope rotation + ATR 2× from "
            f"{args.ma_slope_topn_daily.name}",
            flush=True,
        )

    if args.run_ma_slope_inverse or (
        with_ma_slope_inverse and not args.ma_slope_inverse_daily.is_file()
    ):
        import subprocess

        inv_prefix = LOGS / "ma_slope_inverse_spy_balanced"
        cmd = [
            sys.executable,
            str(_REPO / "RenTech/strategy_stack/run_ma_slope_inverse_spy_standard.py"),
            "--start",
            args.start,
            "--tickers",
            "SH",
            "--out-prefix",
            str(inv_prefix),
        ]
        if args.end:
            cmd += ["--end", args.end]
        print("Running MA slope inverse SPY bear hedge (SH, balanced default) …", flush=True)
        subprocess.run(cmd, check=True, cwd=str(_REPO))

    if with_ma_slope_inverse and not args.ma_slope_inverse_daily.is_file():
        raise SystemExit(
            f"Missing {args.ma_slope_inverse_daily}; run:\n"
            "  .venv/bin/python RenTech/strategy_stack/run_ma_slope_inverse_spy_standard.py "
            f"--start {args.start} --tickers SH "
            "--out-prefix RenTech/data/logs/ma_slope_inverse_spy_balanced\n"
            "or pass --run-ma-slope-inverse to this script."
        )
    if with_ma_slope_inverse:
        print(
            f"MA slope inverse hedge: SH bear sleeve from {args.ma_slope_inverse_daily.name}",
            flush=True,
        )

    if args.run_ma_slope_intraday or (
        with_ma_slope_intraday and not args.ma_slope_intraday_daily.is_file()
    ):
        import subprocess

        cmd = [
            sys.executable,
            str(_REPO / "RenTech/strategy_stack/run_ma_slope_intraday_standard.py"),
            "--start",
            args.start,
            "--end",
            args.end if args.end else "2026-06-26",
            "--capital",
            str(int(args.capital)),
            "--out-prefix",
            str(LOGS / "ma_slope_intraday_confirm4b_top5_cap20_standard"),
        ]
        print("Running MA slope intraday standard (confirm4b top5 cap20) …", flush=True)
        subprocess.run(cmd, check=True, cwd=str(_REPO))

    if with_ma_slope_intraday and not args.ma_slope_intraday_daily.is_file():
        raise SystemExit(
            f"Missing {args.ma_slope_intraday_daily}; run:\n"
            "  .venv/bin/python RenTech/strategy_stack/run_ma_slope_intraday_standard.py "
            "--out-prefix RenTech/data/logs/ma_slope_intraday_confirm4b_top5_cap20_standard\n"
            "or pass --run-ma-slope-intraday to this script."
        )
    if with_ma_slope_intraday:
        print(
            f"MA slope intraday: confirm4b top5 cap20 from {args.ma_slope_intraday_daily.name}",
            flush=True,
        )

    if args.run_vol_edge:
        import subprocess

        ve_out = args.vol_edge_daily.expanduser().resolve()
        ve_prefix = ve_out.with_name(ve_out.name.replace("_daily.csv", ""))
        cmd = [
            sys.executable,
            str(_REPO / "RenTech/strategy_stack/run_volatility_edge_etn.py"),
            "--start",
            args.start,
            "--capital",
            str(args.capital),
            "--variant",
            str(args.vol_edge_variant),
            "--out-prefix",
            str(ve_prefix),
        ]
        if args.end:
            cmd += ["--end", args.end]
        print(f"Running Volatility Edge ETN ({args.vol_edge_variant}) …", flush=True)
        subprocess.run(cmd, check=True, cwd=str(_REPO))

    if with_vol_edge and not args.vol_edge_daily.is_file():
        raise SystemExit(
            f"Missing {args.vol_edge_daily}; run:\n"
            "  .venv/bin/python RenTech/strategy_stack/run_volatility_edge_etn.py "
            f"--variant {args.vol_edge_variant} --start {args.start}\n"
            "or pass --run-vol-edge to this script."
        )

    if with_vol_edge:
        print(
            f"Volatility Edge ETN (SSRN 5316487): {args.vol_edge_variant} from "
            f"{args.vol_edge_daily.name}",
            flush=True,
        )

    if args.run_ls_equity:
        import subprocess

        ls_out = args.ls_equity_daily.expanduser().resolve()
        ls_prefix = ls_out.with_name(ls_out.name.replace("_daily.csv", ""))
        cmd = [
            sys.executable,
            str(_REPO / "RenTech/strategy_stack/run_ls_equity_momentum_standard.py"),
            "--start",
            args.start,
            "--capital",
            str(args.capital),
            "--out-prefix",
            str(ls_prefix),
        ]
        if args.end:
            cmd += ["--end", args.end]
        print("Running market-neutral L/S equity momentum …", flush=True)
        subprocess.run(cmd, check=True, cwd=str(_REPO))

    if with_ls_equity and not args.ls_equity_daily.is_file():
        raise SystemExit(
            f"Missing {args.ls_equity_daily}; run:\n"
            "  .venv/bin/python RenTech/strategy_stack/run_ls_equity_momentum_standard.py "
            f"--start {args.start}\n"
            "or pass --run-ls-equity to this script."
        )

    if with_ls_equity:
        print(
            f"L/S equity momentum (market-neutral): {args.ls_equity_daily.name}",
            flush=True,
        )

    if args.run_spy_bear_call:
        import subprocess

        bcc_out = args.spy_bear_call_daily.expanduser().resolve()
        bcc_prefix = bcc_out.with_name(bcc_out.name.replace("_daily.csv", ""))
        cmd = [
            sys.executable,
            str(_REPO / "RenTech/strategy_stack/run_spy_bear_call_spread_standard.py"),
            "--start",
            args.start,
            "--capital",
            str(args.capital),
            "--out-prefix",
            str(bcc_prefix),
        ]
        if args.end:
            cmd += ["--end", args.end]
        print("Running SPY OTM bear-call credit spread …", flush=True)
        subprocess.run(cmd, check=True, cwd=str(_REPO))

    if with_spy_bear_call and not args.spy_bear_call_daily.is_file():
        raise SystemExit(
            f"Missing {args.spy_bear_call_daily}; run:\n"
            "  .venv/bin/python RenTech/strategy_stack/run_spy_bear_call_spread_standard.py "
            f"--start {args.start}\n"
            "or pass --run-spy-bear-call to this script."
        )

    if with_spy_bear_call:
        print(
            f"SPY bear-call credit spread: {args.spy_bear_call_daily.name}",
            flush=True,
        )

    if args.run_spy_mtm:
        import subprocess

        out_d = args.spy_mtm_daily.expanduser().resolve()
        out_t = out_d.with_name(out_d.name.replace("_daily.csv", "_trades.csv"))
        out_m = out_d.with_name(out_d.name.replace("_daily.csv", "_meta.json"))
        cmd = [
            sys.executable,
            "-m",
            "RenTech.strategy_stack.diverse_theta_strategies_v1.evaluate_theta_margin",
            "--preset",
            "best-ideas-spy",
            "--capital",
            str(args.capital),
            "--start",
            args.start,
            "--out-daily",
            str(out_d),
            "--out-trades",
            str(out_t),
            "--out-meta",
            str(out_m),
        ]
        if args.end:
            cmd += ["--end", args.end]
        print("Running best-ideas-spy margin + MTM (lit4 + D6 + VRP) …", flush=True)
        subprocess.run(cmd, check=True, cwd=str(_REPO))

    if args.run_lit_mtm and not args.with_d6:
        import subprocess

        prefix = LOGS / "lit_stack_vrp_margin"
        cmd = [
            sys.executable,
            "-m",
            "RenTech.strategy_stack.diverse_theta_strategies_v1.evaluate_theta_margin",
            "--preset",
            "lit4-vrp",
            "--capital",
            str(args.capital),
            "--start",
            args.start,
        ]
        if args.end:
            cmd += ["--end", args.end]
        cmd += ["--out-prefix", str(prefix)]
        print("Running lit4-vrp margin + MTM evaluation …", flush=True)
        subprocess.run(cmd, check=True, cwd=str(_REPO))

    if args.run_d6_equity:
        import subprocess

        out_csv = args.d_equity_csv.expanduser().resolve()
        out_json = out_csv.parent / "best_ideas_d6_portfolio_summary_100k.json"
        cmd = [
            sys.executable,
            "-m",
            "RenTech.strategy_stack.diverse_theta_strategies_v1.portfolio_top_strategies",
            "--sids",
            *D6_SIDS,
            "--capital",
            str(args.capital),
            "--start",
            args.start,
            "--out-csv",
            str(out_csv),
            "--out-json",
            str(out_json),
        ]
        if args.end:
            cmd += ["--end", args.end]
        print("Running D6 portfolio equity (exit-day) …", flush=True)
        subprocess.run(cmd, check=True, cwd=str(_REPO))

    if stock_only:
        parts = ["Stock-only book: tactical AW + CM dip + QS actionable-4 + TSMOM"]
        if with_johansen_etf:
            parts.append("Johansen ETF")
        if with_ma_slope_topn:
            parts.append("MA slope top-N")
        if with_ma_slope_inverse:
            parts.append("MA slope inverse")
        if with_sector_momentum:
            parts.append("sector momentum")
        msg = " + ".join(parts) + " (no options)"
        if with_vol_edge:
            msg += " + Volatility Edge ETN"
        if with_spy_bear_call:
            msg += " + SPY bear-call spread"
        if with_ls_equity:
            msg += " + L/S equity momentum (market-neutral)"
        print(msg, flush=True)

    if not stock_only and not args.vxx_daily.is_file():
        raise SystemExit(
            f"Missing VXX stack daily: {args.vxx_daily}\n"
            "Run: .venv/bin/python RenTech/strategy_stack/run_vxx_regime_mtm_report.py "
            "--combine-only --out-prefix RenTech/data/logs/vxx_regime_mtm_2016_2026"
        )

    if args.run_lit:
        import subprocess

        out_lit = args.lit_equity_csv.expanduser().resolve()
        out_lit.parent.mkdir(parents=True, exist_ok=True)
        cmd = [
            sys.executable,
            str(_REPO / "RenTech/strategy_stack/combine_lit_stack_sleeves.py"),
            "--sids",
            ",".join(LIT_SIDS),
            "--start",
            args.start,
            "--end",
            args.end,
            "--capital",
            str(args.capital),
            "--mode",
            "sum",
            "--vrp-pnl-csv",
            str(args.vrp_pnl_csv),
            "--vrp-pnl-col",
            args.vrp_pnl_col,
            "--out-equity-csv",
            str(out_lit),
        ]
        print("Running literature + VRP combine …", flush=True)
        subprocess.run(cmd, check=True, cwd=str(_REPO))
    elif not args.lit_equity_csv.is_file():
        raise SystemExit(f"Missing {args.lit_equity_csv}; pass --run-lit to generate.")

    vxx = pd.read_csv(args.vxx_daily, parse_dates=["date"])
    vxx["date"] = pd.to_datetime(vxx["date"]).dt.normalize()
    vxx = vxx.set_index("date").sort_index()

    if args.mtm and stock_only:
        cal_paths = [
            args.tactical_aw_daily,
            args.tsmom_daily,
            equity_dip_daily,
        ]
        if with_sector_momentum:
            cal_paths.append(args.sector_momentum_daily)
        if with_qs_actionable_etf:
            cal_paths.append(qs_actionable_etf_daily)
        if with_ride_rockets:
            cal_paths.append(args.ride_rockets_daily)
        if with_equity_dip and not sp500_only:
            cal_paths.append(args.russell3000_dip_daily)
        if with_vol_edge:
            cal_paths.append(args.vol_edge_daily)
        if with_johansen_etf:
            cal_paths.append(args.johansen_etf_daily)
        if with_ls_equity:
            cal_paths.append(args.ls_equity_daily)
        if with_ma_slope_topn:
            cal_paths.append(args.ma_slope_topn_daily)
        if with_ma_slope_inverse:
            cal_paths.append(args.ma_slope_inverse_daily)
        if with_ma_slope_intraday:
            cal_paths.append(args.ma_slope_intraday_daily)
        idx = _align_stock_calendar_idx(args.start, args.end, *cal_paths)
        panel = pd.DataFrame(index=idx)
        equity_dip_weight_meta = _add_equity_dip_to_panel(
            panel,
            idx,
            args.capital,
            sp500_daily=equity_dip_daily,
            russell3000_daily=args.russell3000_dip_daily,
            sp500_only=sp500_only,
            w_sp=w_sp,
            w_r3k=w_r3k,
        )
        if with_sector_momentum:
            panel["pnl_sector_momentum"] = _load_equity_dip_pnl(
                args.sector_momentum_daily, idx, args.capital
            )
        if with_qs_actionable_etf:
            panel["pnl_qs_actionable_etf"] = _load_equity_dip_pnl(
                qs_actionable_etf_daily, idx, args.capital
            )
        panel["pnl_tactical_aw"] = _load_equity_dip_pnl(
            args.tactical_aw_daily, idx, args.capital
        )
        panel["pnl_tsmom"] = _load_equity_dip_pnl(args.tsmom_daily, idx, args.capital)
        if with_ride_rockets:
            panel["pnl_ride_rockets"] = _load_equity_dip_pnl(
                args.ride_rockets_daily, idx, args.capital
            )
        if with_vol_edge:
            panel["pnl_vol_edge"] = _load_equity_dip_pnl(
                args.vol_edge_daily, idx, args.capital
            )
        if with_spy_bear_call:
            panel["pnl_spy_bear_call"] = _load_equity_dip_pnl(
                args.spy_bear_call_daily, idx, args.capital
            )
        if with_johansen_etf:
            panel["pnl_johansen_etf"] = _load_equity_dip_pnl(
                args.johansen_etf_daily, idx, args.capital
            )
        if with_ls_equity:
            panel["pnl_ls_equity"] = _load_equity_dip_pnl(
                args.ls_equity_daily, idx, args.capital
            )
        if with_ma_slope_topn:
            panel["pnl_ma_slope_topn"] = _load_equity_dip_pnl(
                args.ma_slope_topn_daily, idx, args.capital
            )
        if with_ma_slope_inverse:
            panel["pnl_ma_slope_inverse"] = _load_equity_dip_pnl(
                args.ma_slope_inverse_daily, idx, args.capital
            )
        if with_ma_slope_intraday:
            panel["pnl_ma_slope_intraday"] = _load_equity_dip_pnl(
                args.ma_slope_intraday_daily, idx, args.capital
            )
        if not fund_mode:
            raise SystemExit("--stock-only requires --fund-mode (enabled automatically)")
        panel = _apply_fund_mode_mtm(
            panel,
            idx,
            args.capital,
            fund_weights=fund_weights,
            fund_scale=float(getattr(args, "fund_scale", 1.0)),
            fund_nav_rebalance=fund_nav_rebalance,
            spy_equity=pd.Series(dtype=float),
            vxx_equity=None,
            with_equity_dip=with_equity_dip,
            sp500_only=sp500_only,
            equity_dip_daily=equity_dip_daily,
            russell3000_dip_daily=args.russell3000_dip_daily,
            w_sp=w_sp,
            w_r3k=w_r3k,
            with_vxx_long_call=False,
            vxx_long_call_daily=args.vxx_long_call_daily,
            with_macro_aw=False,
            macro_aw_daily=args.macro_aw_daily,
            macro_aw_equity_col=str(args.macro_aw_equity_col),
            with_sector_momentum=with_sector_momentum,
            sector_momentum_daily=args.sector_momentum_daily,
            with_qs_actionable_etf=with_qs_actionable_etf,
            qs_actionable_etf_daily=(
                qs_actionable_etf_daily if with_qs_actionable_etf else None
            ),
            with_tactical_aw=with_tactical_aw,
            tactical_aw_daily=args.tactical_aw_daily,
            with_tsmom=with_tsmom,
            with_ride_rockets=with_ride_rockets,
            tsmom_daily=args.tsmom_daily,
            ride_rockets_daily=args.ride_rockets_daily,
            with_johansen_etf=with_johansen_etf,
            johansen_etf_daily=args.johansen_etf_daily if with_johansen_etf else None,
            with_orb_zarattini=with_orb_zarattini,
            orb_zarattini_daily=args.orb_zarattini_daily if with_orb_zarattini else None,
            with_ma_slope_topn=with_ma_slope_topn,
            ma_slope_topn_daily=args.ma_slope_topn_daily if with_ma_slope_topn else None,
            with_ma_slope_inverse=with_ma_slope_inverse,
            ma_slope_inverse_daily=(
                args.ma_slope_inverse_daily if with_ma_slope_inverse else None
            ),
            with_ma_slope_intraday=with_ma_slope_intraday,
            ma_slope_intraday_daily=(
                args.ma_slope_intraday_daily if with_ma_slope_intraday else None
            ),
            with_vol_edge=with_vol_edge,
            vol_edge_daily=args.vol_edge_daily if with_vol_edge else None,
            with_ls_equity=with_ls_equity,
            ls_equity_daily=args.ls_equity_daily if with_ls_equity else None,
            with_spy_bear_call=with_spy_bear_call,
            spy_bear_call_daily=args.spy_bear_call_daily if with_spy_bear_call else None,
            lit_mtm_daily=None,
            vxx_regime_daily=None,
            margin_cap_frac=0.85,
        )
        stack_pnl_cols = _stack_pnl_columns(
            with_d6=False,
            with_equity_dip=with_equity_dip,
            sp500_only=sp500_only,
            with_vxx_long_call=False,
            with_macro_aw=False,
            with_sector_momentum=with_sector_momentum,
            with_qs_actionable_etf=with_qs_actionable_etf,
            with_tactical_aw=with_tactical_aw,
            with_tsmom=with_tsmom,
            with_ride_rockets=with_ride_rockets,
            with_johansen_etf=with_johansen_etf,
            with_orb_zarattini=with_orb_zarattini,
            with_ma_slope_topn=with_ma_slope_topn,
            with_ma_slope_inverse=with_ma_slope_inverse,
            with_ma_slope_intraday=with_ma_slope_intraday,
            with_vol_edge=with_vol_edge,
            with_ls_equity=with_ls_equity,
            with_spy_bear_call=with_spy_bear_call,
            mtm=True,
            stock_only=True,
        )
        panel["pnl_best_ideas_mtm"] = panel[stack_pnl_cols].sum(axis=1)
        panel["daily_return_mtm"] = panel["equity_mtm_usd"].pct_change().fillna(0.0)

        vix_meta: dict = {}
        panel, vix_meta = _maybe_apply_vix_dynamic_scale(
            panel,
            args,
            fund_mode=True,
            start=args.start,
            end=args.end,
            capital=args.capital,
            pnl_cols=stack_pnl_cols,
        )

        out = panel.reset_index(names="date")
        prefix = args.out_prefix.expanduser().resolve()
        prefix.parent.mkdir(parents=True, exist_ok=True)
        daily_path = Path(f"{prefix}{out_suffix}_mtm_daily.csv")
        out.to_csv(daily_path, index=False)

        stack_label = "Stock-only stack"
        if with_vol_edge or with_ls_equity or with_spy_bear_call:
            stack_label = "Stock-only multi-pod stack"
        if with_vol_edge:
            stack_label += " + Vol Edge S3"
        if with_spy_bear_call:
            stack_label += " + SPY bear-call"
        if with_johansen_etf:
            stack_label += " + Johansen ETF"
        if with_ls_equity:
            stack_label += " + L/S equity MN"
        if with_ma_slope_intraday:
            stack_label += " + MA slope intraday"
        if fund_mode:
            stack_label += " + fund mode"
            if fund_nav_rebalance == "quarterly":
                stack_label += " (quarterly sized)"
        if vix_meta:
            stack_label += " + VIX dynamic scale"
        m = _metrics_block(panel["equity_mtm_usd"], f"{stack_label} MTM")
        constituents_mtm = {
            "tactical_aw": (
                "Tactical All Weather: SPY/TLT/IEF/GLD/DBC; SMA(200)+AQR mom gates"
            ),
            "equity_dip": (
                "S&P 500 relative dip: −3% + underperf SPY ≥2%, rank underperf, SPY-hedged (β≈0)"
                if not legacy_equity_dip
                else (
                    f"S&P + Russell 3000 pct-drop dip (SP {equity_dip_weight_meta['sp500_dip_frac']:.1%}"
                    f" / R3k {equity_dip_weight_meta['russell3000_dip_frac']:.1%})"
                )
            ),
            "tsmom": "8-asset time-series momentum (ETF proxy)",
            "sector_momentum": "SPDR sector 12-1 momentum rotation",
        }
        if with_johansen_etf:
            constituents_mtm["johansen_etf"] = (
                "Johansen ETF triplet stat-arb (6 sleeves, equal-weight; Chan EWA-EWC-IGE)"
            )
        if with_ls_equity:
            constituents_mtm["ls_equity"] = (
                "Market-neutral L/S equity momentum (AQR 12-1, top/bottom 10, sector-balanced)"
            )
        if with_vol_edge:
            constituents_mtm["vol_edge"] = (
                f"Volatility Edge VIX ETNs ({args.vol_edge_variant}; SSRN 5316487)"
            )
        if with_spy_bear_call:
            constituents_mtm["spy_bear_call"] = (
                "SPY OTM bear-call credit spread: ~3% OTM short call + $5 wing; "
                "SMA200 + VIX gate; Theta 15:45 MTM"
            )
        if with_ma_slope_topn:
            constituents_mtm["ma_slope_topn"] = (
                "S&P 500 MA slope top-10: dual EMA slope rank, monthly rebalance, 2× ATR trail"
            )
        if with_ma_slope_inverse:
            constituents_mtm["ma_slope_inverse"] = (
                "Inverse SPY bear hedge: SH only, SPY bear regime + inverse momentum confirmation"
            )
        if with_ma_slope_intraday:
            constituents_mtm["ma_slope_intraday"] = (
                "MA slope intraday day-trade: confirm_entry_4b + top-5 + max 20% weight (Alpaca 5m)"
            )
        meta = {
            "name": stack_label,
            "stock_only": True,
            "with_vol_edge": with_vol_edge,
            "vol_edge_variant": args.vol_edge_variant if with_vol_edge else None,
            "vol_edge_daily": str(args.vol_edge_daily.resolve()) if with_vol_edge else None,
            "with_spy_bear_call": with_spy_bear_call,
            "spy_bear_call_daily": str(args.spy_bear_call_daily.resolve())
            if with_spy_bear_call
            else None,
            "legacy_equity_dip": legacy_equity_dip,
            "equity_dip_daily": str(equity_dip_daily.resolve()) if with_equity_dip else None,
            "hedged_relative_dip": bool(stock_only and not legacy_equity_dip),
            "with_ls_equity": with_ls_equity,
            "ls_equity_daily": str(args.ls_equity_daily.resolve()) if with_ls_equity else None,
            "with_johansen_etf": with_johansen_etf,
            "johansen_etf_daily": str(args.johansen_etf_daily.resolve())
            if with_johansen_etf
            else None,
            "with_ma_slope_topn": with_ma_slope_topn,
            "ma_slope_topn_daily": str(args.ma_slope_topn_daily.resolve())
            if with_ma_slope_topn
            else None,
            "with_ma_slope_inverse": with_ma_slope_inverse,
            "ma_slope_inverse_daily": str(args.ma_slope_inverse_daily.resolve())
            if with_ma_slope_inverse
            else None,
            "with_ma_slope_intraday": with_ma_slope_intraday,
            "ma_slope_intraday_daily": str(args.ma_slope_intraday_daily.resolve())
            if with_ma_slope_intraday
            else None,
            "combine_mode": combine_mode,
            "fund_mode": fund_mode,
            "fund_nav_rebalance": fund_nav_rebalance if fund_mode else None,
            "fund_weights": fund_weights if fund_mode else None,
            "fund_scale": float(getattr(args, "fund_scale", 1.0)),
            "metric_mode": "mtm",
            "with_equity_dip": with_equity_dip,
            "equity_dip_weights": equity_dip_weight_meta,
            "constituents": constituents_mtm,
            "capital_usd": args.capital,
            "start": str(idx[0].date()),
            "end": str(idx[-1].date()),
            "n_sessions": int(len(idx)),
            **m,
        }
        if vix_meta:
            meta.update(vix_meta)
        if fund_mode and "margin_combined_usd" in panel.columns:
            meta.update(
                {
                    "margin_combined_mean_usd": float(panel["margin_combined_usd"].mean()),
                    "margin_combined_peak_usd": float(panel["margin_combined_usd"].max()),
                    "min_account_size_usd": round(
                        float(panel["margin_combined_usd"].max()) * 1.25 / 1000
                    )
                    * 1000,
                }
            )
        meta_path = Path(f"{prefix}{out_suffix}_mtm_meta.json")
        meta_path.write_text(json.dumps(meta, indent=2))
        out_nav = out.copy()
        out_nav["pnl_fund_nav"] = out_nav["equity_mtm_usd"].astype(float).diff().fillna(0.0)
        yearly = _yearly_table(
            out_nav, args.capital, pnl_col="pnl_fund_nav", fund_mode=True
        )
        yearly_path = Path(f"{prefix}{out_suffix}_mtm_yearly.csv")
        yearly.to_csv(yearly_path, index=False)

        print(f"\n=== {stack_label} — MTM ({combine_mode}) ===", flush=True)
        print(
            f"  Window {meta['start']} → {meta['end']}  ({meta['n_sessions']} sessions)",
            flush=True,
        )
        print(
            f"  Return {m['return_pct']:+.1f}%  CAGR {m['cagr_pct']:+.1f}%  "
            f"Sharpe {m['sharpe']:.2f}  MaxDD {m['max_dd_pct']:.1f}%  "
            f"End ${m['end_equity']:,.0f}",
            flush=True,
        )
        print(
            "  Sleeve weights: "
            + ", ".join(f"{k}={v:.1%}" for k, v in fund_weights.items()),
            flush=True,
        )
        if fund_mode and "margin_combined_usd" in panel.columns:
            print(
                f"  [Margin] combined mean ${meta.get('margin_combined_mean_usd', 0):,.0f}  "
                f"peak ${meta.get('margin_combined_peak_usd', 0):,.0f}",
                flush=True,
            )
        print(
            yearly[["year", "return_pct_chained", "max_dd_pct_chained"]].to_string(
                index=False
            ),
            flush=True,
        )
        print(f"\nMTM daily → {daily_path}", flush=True)
        print(f"Meta  → {meta_path}", flush=True)
        return

    if args.mtm:
        spy_mtm_path = args.spy_mtm_daily if args.with_d6 else args.lit_mtm_daily
        if not spy_mtm_path.is_file():
            hint = (
                "  .venv/bin/python -m RenTech.strategy_stack.diverse_theta_strategies_v1.evaluate_theta_margin "
                "--preset best-ideas-spy --capital 100000 --start 2016-01-04 "
                "--out-daily RenTech/data/logs/best_ideas_spy6_margin_daily.csv "
                "--out-trades RenTech/data/logs/best_ideas_spy6_margin_trades.csv "
                "--out-meta RenTech/data/logs/best_ideas_spy6_margin_meta.json"
                if args.with_d6
                else "  .venv/bin/python -m ... evaluate_theta_margin --preset lit4-vrp ..."
            )
            raise SystemExit(f"Missing {spy_mtm_path}; run with --run-spy-mtm or:\n{hint}")
        spy_mtm = pd.read_csv(spy_mtm_path, parse_dates=["date"])
        spy_mtm["date"] = pd.to_datetime(spy_mtm["date"]).dt.normalize()
        spy_mtm = spy_mtm.set_index("date").sort_index()
        idx = spy_mtm.index.intersection(vxx.index)
        idx = idx[(idx >= pd.Timestamp(args.start)) & (idx <= pd.Timestamp(args.end))]
        if len(idx) < 100:
            raise SystemExit(f"Too few aligned sessions ({len(idx)}); check date range.")

        pnl_spy_mtm = spy_mtm["equity_mtm_usd"].diff().fillna(0.0).reindex(idx).fillna(0.0)
        panel = pd.DataFrame(index=idx)
        panel["pnl_spy_theta_mtm"] = pnl_spy_mtm
        if not args.with_d6:
            panel["pnl_lit4_vrp_mtm"] = pnl_spy_mtm
        for c in vxx.columns:
            if c.startswith("pnl_") and c != "pnl_stack":
                panel[c] = vxx[c].reindex(idx).fillna(0.0)
        panel["pnl_vxx_regime_stack"] = vxx["pnl_stack"].reindex(idx).fillna(0.0)
        if with_equity_dip:
            equity_dip_weight_meta = _add_equity_dip_to_panel(
                panel,
                idx,
                args.capital,
                sp500_daily=equity_dip_daily,
                russell3000_daily=args.russell3000_dip_daily,
                sp500_only=sp500_only,
                w_sp=w_sp,
                w_r3k=w_r3k,
            )
        if args.with_vxx_long_call:
            panel["pnl_vxx_long_call"] = _load_equity_dip_pnl(
                args.vxx_long_call_daily, idx, args.capital
            )
        if with_macro_aw:
            panel["pnl_macro_aw"] = _load_macro_aw_pnl(
                args.macro_aw_daily,
                idx,
                equity_col=str(args.macro_aw_equity_col),
            )
        if with_sector_momentum:
            panel["pnl_sector_momentum"] = _load_equity_dip_pnl(
                args.sector_momentum_daily, idx, args.capital
            )
        if with_qs_actionable_etf:
            panel["pnl_qs_actionable_etf"] = _load_equity_dip_pnl(
                qs_actionable_etf_daily, idx, args.capital
            )
        if with_tactical_aw:
            panel["pnl_tactical_aw"] = _load_equity_dip_pnl(
                args.tactical_aw_daily, idx, args.capital
            )
        if with_tsmom:
            panel["pnl_tsmom"] = _load_equity_dip_pnl(args.tsmom_daily, idx, args.capital)
        if with_ride_rockets:
            panel["pnl_ride_rockets"] = _load_equity_dip_pnl(
                args.ride_rockets_daily, idx, args.capital
            )
        if with_johansen_etf:
            panel["pnl_johansen_etf"] = _load_equity_dip_pnl(
                args.johansen_etf_daily, idx, args.capital
            )
        if with_orb_zarattini:
            panel["pnl_orb_zarattini"] = _load_equity_dip_pnl(
                args.orb_zarattini_daily, idx, args.capital
            )
        if with_ma_slope_topn:
            panel["pnl_ma_slope_topn"] = _load_equity_dip_pnl(
                args.ma_slope_topn_daily, idx, args.capital
            )
        if with_ma_slope_inverse:
            panel["pnl_ma_slope_inverse"] = _load_equity_dip_pnl(
                args.ma_slope_inverse_daily, idx, args.capital
            )
        if with_ma_slope_intraday:
            panel["pnl_ma_slope_intraday"] = _load_equity_dip_pnl(
                args.ma_slope_intraday_daily, idx, args.capital
            )
        if "unrealized_mtm_usd" in spy_mtm.columns:
            panel["spy_unrealized_mtm_usd"] = spy_mtm["unrealized_mtm_usd"].reindex(idx).fillna(0.0)
        if "equity_mtm_usd" in vxx.columns:
            panel["vxx_equity_mtm_usd"] = vxx["equity_mtm_usd"].reindex(idx).ffill()
        stack_pnl_cols = _stack_pnl_columns(
            with_d6=bool(args.with_d6),
            with_equity_dip=with_equity_dip,
            sp500_only=sp500_only,
            with_vxx_long_call=bool(args.with_vxx_long_call),
            with_macro_aw=with_macro_aw,
            with_sector_momentum=with_sector_momentum,
            with_qs_actionable_etf=with_qs_actionable_etf,
            with_tactical_aw=with_tactical_aw,
            with_tsmom=with_tsmom,
            with_ride_rockets=with_ride_rockets,
            with_johansen_etf=with_johansen_etf,
            with_orb_zarattini=with_orb_zarattini,
            with_ma_slope_topn=with_ma_slope_topn,
            with_ma_slope_inverse=with_ma_slope_inverse,
            with_ma_slope_intraday=with_ma_slope_intraday,
            mtm=True,
        )
        vxx_eq = (
            vxx["equity_mtm_usd"].reindex(idx).ffill()
            if "equity_mtm_usd" in vxx.columns
            else None
        )
        if fund_mode:
            panel = _apply_fund_mode_mtm(
                panel,
                idx,
                args.capital,
                fund_weights=fund_weights,
                fund_scale=float(getattr(args, "fund_scale", 1.0)),
                fund_nav_rebalance=fund_nav_rebalance,
                spy_equity=spy_mtm["equity_mtm_usd"],
                vxx_equity=vxx_eq,
                with_equity_dip=with_equity_dip,
                sp500_only=sp500_only,
                equity_dip_daily=equity_dip_daily,
                russell3000_dip_daily=args.russell3000_dip_daily,
                w_sp=w_sp,
                w_r3k=w_r3k,
                with_vxx_long_call=bool(args.with_vxx_long_call),
                vxx_long_call_daily=args.vxx_long_call_daily,
                with_macro_aw=with_macro_aw,
                macro_aw_daily=args.macro_aw_daily,
                macro_aw_equity_col=str(args.macro_aw_equity_col),
                with_sector_momentum=with_sector_momentum,
                sector_momentum_daily=args.sector_momentum_daily,
                with_qs_actionable_etf=with_qs_actionable_etf,
                qs_actionable_etf_daily=(
                    qs_actionable_etf_daily if with_qs_actionable_etf else None
                ),
                with_tactical_aw=with_tactical_aw,
                tactical_aw_daily=args.tactical_aw_daily,
                with_tsmom=with_tsmom,
                with_ride_rockets=with_ride_rockets,
                tsmom_daily=args.tsmom_daily,
                ride_rockets_daily=args.ride_rockets_daily,
                with_johansen_etf=with_johansen_etf,
                johansen_etf_daily=args.johansen_etf_daily if with_johansen_etf else None,
                with_orb_zarattini=with_orb_zarattini,
                orb_zarattini_daily=args.orb_zarattini_daily if with_orb_zarattini else None,
                with_ma_slope_topn=with_ma_slope_topn,
                ma_slope_topn_daily=args.ma_slope_topn_daily if with_ma_slope_topn else None,
                with_ma_slope_inverse=with_ma_slope_inverse,
                ma_slope_inverse_daily=(
                    args.ma_slope_inverse_daily if with_ma_slope_inverse else None
                ),
                with_ma_slope_intraday=with_ma_slope_intraday,
                ma_slope_intraday_daily=(
                    args.ma_slope_intraday_daily if with_ma_slope_intraday else None
                ),
                # UnifiedMarginTracker paths (Fix 3)
                lit_mtm_daily=spy_mtm_path,
                vxx_regime_daily=args.vxx_daily,
                margin_cap_frac=0.85,
            )
        elif nav_rebalance == "quarterly":
            panel = _apply_quarterly_nav_rebase(
                panel,
                stack_pnl_cols,
                args.capital,
                total_col="pnl_best_ideas_mtm",
            )
            panel["equity_mtm_usd"] = panel["nav_usd"]
        else:
            panel["pnl_best_ideas_mtm"] = panel[stack_pnl_cols].sum(axis=1)
            panel["equity_mtm_usd"] = (
                float(args.capital) + panel["pnl_best_ideas_mtm"].cumsum()
            )
        panel["daily_return_mtm"] = panel["equity_mtm_usd"].pct_change().fillna(0.0)
        if "pnl_best_ideas_mtm" not in panel.columns:
            panel["pnl_best_ideas_mtm"] = panel[stack_pnl_cols].sum(axis=1)

        vix_meta: dict = {}
        panel, vix_meta = _maybe_apply_vix_dynamic_scale(
            panel,
            args,
            fund_mode=fund_mode,
            start=args.start,
            end=args.end,
            capital=args.capital,
            pnl_cols=stack_pnl_cols,
        )

        out = panel.reset_index(names="date")
        prefix = args.out_prefix.expanduser().resolve()
        prefix.parent.mkdir(parents=True, exist_ok=True)
        daily_path = Path(f"{prefix}{out_suffix}_mtm_daily.csv")
        out.to_csv(daily_path, index=False)

        stack_label = STACK_NAME
        if args.with_d6:
            stack_label += " + D6"
        if with_equity_dip:
            if sp500_only:
                stack_label += " + S&P dip"
            else:
                stack_label += (
                    f" + equity dip (SP {equity_dip_weight_meta['sp500_dip_frac']:.0%}"
                    f" / R3k {equity_dip_weight_meta['russell3000_dip_frac']:.0%})"
                )
        if args.with_vxx_long_call:
            stack_label += " + VXX long call"
        if with_macro_aw:
            stack_label += " + Macro AW"
        if with_sector_momentum:
            stack_label += " + sector momentum"
        if with_tactical_aw:
            stack_label += " + tactical AW"
        if with_tsmom:
            stack_label += " + TSMOM"
        if with_johansen_etf:
            stack_label += " + Johansen ETF"
        if with_orb_zarattini:
            stack_label += " + ORB Zarattini"
        if with_ma_slope_topn:
            stack_label += " + MA slope top-N"
        if with_ma_slope_inverse:
            stack_label += " + MA slope inverse"
        if with_ma_slope_intraday:
            stack_label += " + MA slope intraday"
        if fund_mode:
            stack_label += " + fund mode"
            if fund_nav_rebalance == "quarterly":
                stack_label += " (quarterly sized)"
        elif nav_rebalance == "quarterly":
            stack_label += " + NAV qtr rebase"
        if vix_meta:
            stack_label += " + VIX dynamic scale"
        m = _metrics_block(panel["equity_mtm_usd"], f"{stack_label} MTM")
        _debug_log(
            hypothesis_id="H2-compare-modes",
            location="combine_best_ideas_stack.py:main:mtm",
            message="mtm_headline_metrics",
            data={
                "combine_mode": combine_mode,
                "return_pct": m.get("return_pct"),
                "max_dd_pct": m.get("max_dd_pct"),
                "sharpe": m.get("sharpe"),
                "end_equity": m.get("end_equity"),
                "fund_mode": fund_mode,
            },
        )
        spy_constituents = (
            f"S055+S057+S059+S089+{','.join(D6_SIDS)}+VRP margin simulator"
            if args.with_d6
            else "S055+S057+S059+S089+VRP margin simulator"
        )
        constituents_mtm = {
            "spy_theta_mtm": spy_constituents,
            "vxx_regime_stack": "Dynamic VXX Regime Strategy Stack (6 sleeves)",
        }
        if with_equity_dip:
            if sp500_only:
                constituents_mtm["sp500_dip"] = (
                    f"S&P 500 pct-drop dip (≥3%, hold 10d, top {int(args.sp500_dip_top_n)}, BuyTheDipSleeve)"
                )
            else:
                constituents_mtm["equity_dip"] = (
                    "S&P 500 + Russell 3000 pct-drop dip (split one $100k slot; "
                    f"weights SP {equity_dip_weight_meta['sp500_dip_frac']:.1%} / "
                    f"R3k {equity_dip_weight_meta['russell3000_dip_frac']:.1%})"
                )
        if args.with_vxx_long_call:
            constituents_mtm["vxx_long_call"] = (
                f"VXX long OTM call (10% OTM, ~{args.vxx_long_call_book_frac:.0%} book PnL, contango gate)"
            )
        if with_macro_aw:
            constituents_mtm["macro_aw"] = (
                "Macro AW options: TLT×3, USO×3, DBC put diagonal, GLD putw "
                "(equal_weight 8-sleeve book)"
            )
        if with_sector_momentum:
            constituents_mtm["sector_momentum"] = (
                "SPDR sector ETF rotation (12-1 momentum, top-k monthly equal weight)"
            )
        if with_tactical_aw:
            constituents_mtm["tactical_aw"] = (
                "Tactical All Weather: SPY/TLT/IEF/GLD/DBC baseline weights; "
                "SMA(200)+AQR mom gates; cash yield on uninvested"
            )
        if with_ride_rockets:
            constituents_mtm["ride_rockets"] = (
                "Ride-rockets 50/50 (near_52w_high top25 + ten_rockets top10)"
            )
        if with_tsmom:
            constituents_mtm["tsmom"] = (
                "Time-series momentum / managed futures: 8 ETFs (SPY,EFA,EEM,TLT,IEF,GLD,DBC,UUP), "
                "3/6/12m signal blend, vol-norm per asset, monthly rebalance"
            )
        if with_ma_slope_topn:
            constituents_mtm["ma_slope_topn"] = (
                "S&P 500 MA slope top-10: dual EMA slope rank, monthly rebalance, 2× ATR trail"
            )
        if with_ma_slope_inverse:
            constituents_mtm["ma_slope_inverse"] = (
                "Inverse SPY bear hedge: SH only, SPY bear regime + inverse momentum confirmation"
            )
        if with_ma_slope_intraday:
            constituents_mtm["ma_slope_intraday"] = (
                "MA slope intraday day-trade: confirm_entry_4b + top-5 + max 20% weight (Alpaca 5m)"
            )

        if with_johansen_etf:
            constituents_mtm["johansen_etf"] = (
                "Johansen ETF triplet stat-arb: GDXJ-IAU-SIL, GLD-UNG-USO, XLB-XLI-XLP, "
                "COP-USO-XOP, DBC-PDBC-USO, EWA-EWC-IGE (equal-weight 6 sleeves)"
            )
        if with_orb_zarattini:
            constituents_mtm["orb_zarattini"] = (
                "Zarattini 5m Opening Range Breakout on Stocks in Play "
                "(Zarattini/Barbon/Aziz); day-trade equity sleeve"
            )
        meta = {
            "name": stack_label,
            "combine_mode": combine_mode,
            "fund_mode": fund_mode,
            "fund_nav_rebalance": fund_nav_rebalance if fund_mode else None,
            "fund_weights": fund_weights if fund_mode else None,
            "nav_rebalance": nav_rebalance,
            "nav_rebase_reference_usd": args.capital,
            "metric_mode": "mtm",
            "with_d6": bool(args.with_d6),
            "with_equity_dip": with_equity_dip,
            "equity_dip_sp500_only": sp500_only,
            "with_vxx_long_call": bool(args.with_vxx_long_call),
            "with_macro_aw": with_macro_aw,
            "macro_aw_daily": str(args.macro_aw_daily.resolve()) if with_macro_aw else None,
            "macro_aw_equity_col": str(args.macro_aw_equity_col) if with_macro_aw else None,
            "with_sector_momentum": with_sector_momentum,
            "sector_momentum_daily": str(args.sector_momentum_daily.resolve())
            if with_sector_momentum
            else None,
            "with_tactical_aw": with_tactical_aw,
            "tactical_aw_daily": str(args.tactical_aw_daily.resolve())
            if with_tactical_aw
            else None,
            "with_tsmom": with_tsmom,
            "with_ride_rockets": with_ride_rockets,
            "tsmom_daily": str(args.tsmom_daily.resolve()) if with_tsmom else None,
            "ride_rockets_daily": str(args.ride_rockets_daily.resolve()) if with_ride_rockets else None,
            "with_ma_slope_topn": with_ma_slope_topn,
            "ma_slope_topn_daily": str(args.ma_slope_topn_daily.resolve())
            if with_ma_slope_topn
            else None,
            "with_ma_slope_inverse": with_ma_slope_inverse,
            "ma_slope_inverse_daily": str(args.ma_slope_inverse_daily.resolve())
            if with_ma_slope_inverse
            else None,
            "with_ma_slope_intraday": with_ma_slope_intraday,
            "ma_slope_intraday_daily": str(args.ma_slope_intraday_daily.resolve())
            if with_ma_slope_intraday
            else None,
            "with_johansen_etf": with_johansen_etf,
            "johansen_etf_daily": str(args.johansen_etf_daily.resolve())
            if with_johansen_etf
            else None,
            "with_orb_zarattini": with_orb_zarattini,
            "orb_zarattini_daily": str(args.orb_zarattini_daily.resolve())
            if with_orb_zarattini
            else None,
            **(
                {
                    "equity_dip_weights": equity_dip_weight_meta,
                    "sp500_dip_top_n": int(args.sp500_dip_top_n),
                    "russell3000_dip_top_n": int(args.russell3000_dip_top_n),
                }
                if with_equity_dip
                else {}
            ),
            "constituents": constituents_mtm,
            "diverse_sids": list(D6_SIDS) if args.with_d6 else [],
            "capital_usd": args.capital,
            "start": str(idx[0].date()),
            "end": str(idx[-1].date()),
            "n_sessions": int(len(idx)),
            "spy_mtm_daily": str(spy_mtm_path.resolve()),
            **(
                {
                    "margin_combined_mean_usd": float(panel["margin_combined_usd"].mean()),
                    "margin_combined_peak_usd": float(panel["margin_combined_usd"].max()),
                    "margin_cap_frac": 0.85,
                    "margin_capped_days": int(
                        (panel.get("effective_fund_scale", pd.Series(float(getattr(args, "fund_scale", 1.0)))) <
                         float(getattr(args, "fund_scale", 1.0)) * 0.999).sum()
                    ),
                    "min_account_size_usd": round(float(panel["margin_combined_usd"].max()) * 1.25 / 1000) * 1000,
                }
                if fund_mode and "margin_combined_usd" in panel.columns
                else {}
            ),
            "yearly_return_basis": (
                {
                    "return_pct_chained": "account return: sum(daily_pnl) / start-of-year NAV",
                    "return_pct_constant": "n/a (fund-mode PnL is NAV-sized; constant/$100k is not a return)",
                }
                if fund_mode
                else {
                    "constant_notional_usd": args.capital,
                    "return_pct_constant": "sum(daily_pnl) / constant_notional per year",
                    "return_pct_chained": "sum(daily_pnl) / start-of-year stacked equity",
                }
            ),
            **m,
        }
        if vix_meta:
            meta.update(vix_meta)
        meta_path = Path(f"{prefix}{out_suffix}_mtm_meta.json")
        meta_path.write_text(json.dumps(meta, indent=2))
        yearly = _yearly_table(
            out.rename(columns={"pnl_best_ideas_mtm": "pnl_best_ideas"}),
            args.capital,
            fund_mode=fund_mode,
        )
        yearly_path = Path(f"{prefix}{out_suffix}_mtm_yearly.csv")
        yearly.to_csv(yearly_path, index=False)

        print(f"\n=== {stack_label} — MTM daily view ({combine_mode}) ===", flush=True)
        print(
            f"  Window {meta['start']} → {meta['end']}  ({meta['n_sessions']} sessions)",
            flush=True,
        )
        print(
            f"  Return {m['return_pct']:+.1f}%  CAGR {m['cagr_pct']:+.1f}%  "
            f"Sharpe {m['sharpe']:.2f}  MaxDD {m['max_dd_pct']:.1f}%  "
            f"End ${m['end_equity']:,.0f}",
            flush=True,
        )
        print(
            f"  SPY theta MTM PnL ${panel['pnl_spy_theta_mtm'].sum():+,.0f}  "
            f"  VXX stack MTM PnL ${panel['pnl_vxx_regime_stack'].sum():+,.0f}",
            flush=True,
        )
        if fund_mode and "margin_combined_usd" in panel.columns:
            mg_mean = float(panel["margin_combined_usd"].mean())
            mg_peak = float(panel["margin_combined_usd"].max())
            cap_pct = float(getattr(args, "fund_scale", 1.0))
            capped_days = int((panel.get("effective_fund_scale", pd.Series(cap_pct)) < cap_pct * 0.999).sum())
            print(
                f"  [Margin] combined mean ${mg_mean:,.0f}  peak ${mg_peak:,.0f}"
                f"  margin-capped days: {capped_days}"
                f"  (re-run sleeve engines to populate non-theta sleeves)",
                flush=True,
            )
        if nav_rebalance == "quarterly":
            print("\n--- By year (chained NAV — use after quarterly rebase) ---", flush=True)
        else:
            print(
                f"\n--- By year (constant ${args.capital:,.0f} notional — comparable) ---",
                flush=True,
            )
            print(
                yearly[
                    ["year", "pnl_usd", "return_pct_constant", "max_dd_pct_constant"]
                ].to_string(index=False),
                flush=True,
            )
            print("\n--- By year (chained MTM equity) ---", flush=True)
        if nav_rebalance == "quarterly":
            print(
                yearly[
                    [
                        "year",
                        "pnl_usd",
                        "return_pct_chained",
                        "max_dd_pct_chained",
                        "start_equity_chained",
                        "end_equity_chained",
                    ]
                ].to_string(index=False),
                flush=True,
            )
        else:
            print(
                yearly[
                    [
                        "year",
                        "return_pct_chained",
                        "max_dd_pct_chained",
                        "start_equity_chained",
                        "end_equity_chained",
                    ]
                ].to_string(index=False),
                flush=True,
            )
        print(f"\nMTM daily (open in Excel) → {daily_path}", flush=True)
        print(f"  VXX per-sleeve MTM cols: pnl_steepcontango_* … in same file", flush=True)
        print(f"  SPY book source → {spy_mtm_path}", flush=True)
        print(f"Meta  → {meta_path}", flush=True)
        return

    lit_eq = pd.read_csv(args.lit_equity_csv, index_col=0, parse_dates=True)
    lit_eq.index = pd.to_datetime(lit_eq.index).normalize()
    idx = lit_eq.index.intersection(vxx.index)
    idx = idx[(idx >= pd.Timestamp(args.start)) & (idx <= pd.Timestamp(args.end))]
    if len(idx) < 100:
        raise SystemExit(f"Too few aligned sessions ({len(idx)}); check date range.")

    lit_daily = _load_lit_daily_from_equity_csv(args.lit_equity_csv, idx)
    pnl_vxx = vxx["pnl_stack"].reindex(idx).fillna(0.0)

    panel = pd.DataFrame(index=idx)
    panel["pnl_vxx_regime_stack"] = pnl_vxx
    for sid in LIT_SIDS:
        panel[f"pnl_{sid}"] = lit_daily[sid]
    panel["pnl_VRP"] = lit_daily["VRP"]
    if with_equity_dip:
        equity_dip_weight_meta = _add_equity_dip_to_panel(
            panel,
            idx,
            args.capital,
            sp500_daily=equity_dip_daily,
            russell3000_daily=args.russell3000_dip_daily,
            sp500_only=sp500_only,
            w_sp=w_sp,
            w_r3k=w_r3k,
        )
    if args.with_vxx_long_call:
        panel["pnl_vxx_long_call"] = _load_equity_dip_pnl(
            args.vxx_long_call_daily, idx, args.capital
        )
    if with_macro_aw:
        panel["pnl_macro_aw"] = _load_macro_aw_pnl(
            args.macro_aw_daily,
            idx,
            equity_col=str(args.macro_aw_equity_col),
        )
    if with_sector_momentum:
        panel["pnl_sector_momentum"] = _load_equity_dip_pnl(
            args.sector_momentum_daily, idx, args.capital
        )
    if with_qs_actionable_etf:
        panel["pnl_qs_actionable_etf"] = _load_equity_dip_pnl(
            qs_actionable_etf_daily, idx, args.capital
        )
    if with_tactical_aw:
        panel["pnl_tactical_aw"] = _load_equity_dip_pnl(
            args.tactical_aw_daily, idx, args.capital
        )
    if with_tsmom:
        panel["pnl_tsmom"] = _load_equity_dip_pnl(
            args.tsmom_daily, idx, args.capital
        )
    if with_ride_rockets:
        panel["pnl_ride_rockets"] = _load_equity_dip_pnl(
            args.ride_rockets_daily, idx, args.capital
        )
    if with_johansen_etf:
        panel["pnl_johansen_etf"] = _load_equity_dip_pnl(
            args.johansen_etf_daily, idx, args.capital
        )
    if with_orb_zarattini:
        panel["pnl_orb_zarattini"] = _load_equity_dip_pnl(
            args.orb_zarattini_daily, idx, args.capital
        )
    if with_ma_slope_topn:
        panel["pnl_ma_slope_topn"] = _load_equity_dip_pnl(
            args.ma_slope_topn_daily, idx, args.capital
        )
    if with_ma_slope_inverse:
        panel["pnl_ma_slope_inverse"] = _load_equity_dip_pnl(
            args.ma_slope_inverse_daily, idx, args.capital
        )
    if with_ma_slope_intraday:
        panel["pnl_ma_slope_intraday"] = _load_equity_dip_pnl(
            args.ma_slope_intraday_daily, idx, args.capital
        )
    if args.with_d6:
        if not args.d_equity_csv.is_file():
            raise SystemExit(
                f"Missing {args.d_equity_csv}; pass --run-d6-equity or run portfolio_top_strategies "
                f"with --sids {' '.join(D6_SIDS)}"
            )
        d_eq = pd.read_csv(args.d_equity_csv, index_col=0, parse_dates=True)
        d_eq.index = pd.to_datetime(d_eq.index).normalize()
        n_d = len(D6_SIDS)
        sleeve_cap = float(args.capital) / float(n_d)
        scale_full = float(args.capital) / sleeve_cap
        for sid in D6_SIDS:
            if sid not in d_eq.columns:
                raise KeyError(f"Missing column {sid} in {args.d_equity_csv}")
            panel[f"pnl_{sid}"] = (
                _daily_from_equity(d_eq[sid]).reindex(idx).fillna(0.0) * scale_full
            )
    stack_pnl_cols = _stack_pnl_columns(
        with_d6=bool(args.with_d6),
        with_equity_dip=with_equity_dip,
        sp500_only=sp500_only,
        with_vxx_long_call=bool(args.with_vxx_long_call),
        with_macro_aw=with_macro_aw,
        with_sector_momentum=with_sector_momentum,
        with_tactical_aw=with_tactical_aw,
        with_tsmom=with_tsmom,
        with_ride_rockets=with_ride_rockets,
        with_johansen_etf=with_johansen_etf,
        with_orb_zarattini=with_orb_zarattini,
        with_ma_slope_topn=with_ma_slope_topn,
        with_ma_slope_inverse=with_ma_slope_inverse,
        with_ma_slope_intraday=with_ma_slope_intraday,
        mtm=False,
    )
    if nav_rebalance == "quarterly":
        panel = _apply_quarterly_nav_rebase(
            panel,
            stack_pnl_cols,
            args.capital,
            total_col="pnl_best_ideas",
        )
        panel["equity_usd"] = panel["nav_usd"]
    else:
        panel["pnl_best_ideas"] = panel[stack_pnl_cols].sum(axis=1)
        panel["equity_usd"] = float(args.capital) + panel["pnl_best_ideas"].cumsum()

    out = panel.reset_index(names="date")
    prefix = args.out_prefix.expanduser().resolve()
    prefix.parent.mkdir(parents=True, exist_ok=True)
    daily_path = Path(f"{prefix}{out_suffix}_daily.csv")
    out.to_csv(daily_path, index=False)

    m = _metrics_block(panel["equity_usd"], STACK_NAME)
    constituents = [
        "Dynamic VXX Regime Strategy Stack",
        *list(LIT_SIDS),
        *([*D6_SIDS] if args.with_d6 else []),
        "VRP",
    ]
    if with_equity_dip:
        if sp500_only:
            constituents.append("sp500_dip (pct-drop, hold 10d, top 10)")
        else:
            constituents.append(
                "equity_dip: sp500 + russell3000 pct-drop (split one $100k slot)"
            )
    if args.with_vxx_long_call:
        constituents.append("vxx_long_call (tail hedge)")
    if with_macro_aw:
        constituents.append("macro_aw (8 ETF options, equal_weight)")
    if with_sector_momentum:
        constituents.append("sector_momentum (SPDR top-k rotation)")
    if with_tactical_aw:
        constituents.append("tactical_aw (SPY/TLT/IEF/GLD/DBC gated macro)")
    if with_tsmom:
        constituents.append("tsmom (8-asset managed futures, 3/6/12m blend)")
    if with_ride_rockets:
        constituents.append("ride_rockets (50/50 near_52w_high top25 + ten_rockets top10)")
    if with_johansen_etf:
        constituents.append("johansen_etf (6-sleeve Chan ETF triplet stat-arb)")
    if with_orb_zarattini:
        constituents.append("orb_zarattini (Zarattini 5m ORB, Stocks in Play)")
    if with_ma_slope_topn:
        constituents.append("ma_slope_topn (S&P 500 top-10 dual slope + ATR 2×)")
    if with_ma_slope_inverse:
        constituents.append("ma_slope_inverse (SH bear hedge, SPY regime gates)")
    if with_ma_slope_intraday:
        constituents.append(
            "ma_slope_intraday (confirm_entry_4b + top-5 + max 20% weight, Alpaca 5m)"
        )
    stack_label = STACK_NAME
    if args.with_d6:
        stack_label += " + D6"
    if with_equity_dip:
        if sp500_only:
            stack_label += " + S&P dip"
        else:
            stack_label += (
                f" + equity dip (SP {equity_dip_weight_meta['sp500_dip_frac']:.0%}"
                f" / R3k {equity_dip_weight_meta['russell3000_dip_frac']:.0%})"
            )
    if args.with_vxx_long_call:
        stack_label += " + VXX long call"
    if with_macro_aw:
        stack_label += " + Macro AW"
    if with_sector_momentum:
        stack_label += " + sector momentum"
    if with_tactical_aw:
        stack_label += " + tactical AW"
    if with_tsmom:
        stack_label += " + TSMOM"
    if with_ride_rockets:
        stack_label += " + Ride-rockets"
    if with_johansen_etf:
        stack_label += " + Johansen ETF"
    if with_orb_zarattini:
        stack_label += " + ORB Zarattini"
    if with_ma_slope_topn:
        stack_label += " + MA slope top-N"
    if with_ma_slope_inverse:
        stack_label += " + MA slope inverse"
    if with_ma_slope_intraday:
        stack_label += " + MA slope intraday"
    if nav_rebalance == "quarterly":
        stack_label += " + NAV qtr rebase"
    meta = {
        "name": stack_label,
        "combine_mode": combine_mode,
        "nav_rebalance": nav_rebalance,
        "nav_rebase_reference_usd": args.capital,
        "metric_mode": "exit_day_lit_combine",
        "with_d6": bool(args.with_d6),
        "with_equity_dip": with_equity_dip,
        "equity_dip_sp500_only": sp500_only,
        "with_vxx_long_call": bool(args.with_vxx_long_call),
        "with_macro_aw": with_macro_aw,
        "macro_aw_daily": str(args.macro_aw_daily.resolve()) if with_macro_aw else None,
        "with_sector_momentum": with_sector_momentum,
        "sector_momentum_daily": str(args.sector_momentum_daily.resolve())
        if with_sector_momentum
        else None,
        "with_tactical_aw": with_tactical_aw,
        "tactical_aw_daily": str(args.tactical_aw_daily.resolve()) if with_tactical_aw else None,
        "with_tsmom": with_tsmom,
            "with_ride_rockets": with_ride_rockets,
        "tsmom_daily": str(args.tsmom_daily.resolve()) if with_tsmom else None,
            "ride_rockets_daily": str(args.ride_rockets_daily.resolve()) if with_ride_rockets else None,
        "with_ma_slope_topn": with_ma_slope_topn,
        "ma_slope_topn_daily": str(args.ma_slope_topn_daily.resolve())
        if with_ma_slope_topn
        else None,
        "with_ma_slope_inverse": with_ma_slope_inverse,
        "ma_slope_inverse_daily": str(args.ma_slope_inverse_daily.resolve())
        if with_ma_slope_inverse
        else None,
        "with_ma_slope_intraday": with_ma_slope_intraday,
        "ma_slope_intraday_daily": str(args.ma_slope_intraday_daily.resolve())
        if with_ma_slope_intraday
        else None,
        "with_johansen_etf": with_johansen_etf,
        "johansen_etf_daily": str(args.johansen_etf_daily.resolve())
        if with_johansen_etf
        else None,
        "with_orb_zarattini": with_orb_zarattini,
        "orb_zarattini_daily": str(args.orb_zarattini_daily.resolve())
        if with_orb_zarattini
        else None,
        "equity_dip_daily": str(equity_dip_daily.resolve()) if with_equity_dip else None,
        "russell3000_dip_daily": str(args.russell3000_dip_daily.resolve())
        if with_equity_dip and not sp500_only
        else None,
        **(
            {
                "equity_dip_weights": equity_dip_weight_meta,
                "sp500_dip_top_n": int(args.sp500_dip_top_n),
                "russell3000_dip_top_n": int(args.russell3000_dip_top_n),
            }
            if with_equity_dip
            else {}
        ),
        "vxx_long_call_daily": str(args.vxx_long_call_daily.resolve())
        if args.with_vxx_long_call
        else None,
        "macro_aw_daily": str(args.macro_aw_daily.resolve()) if with_macro_aw else None,
        "constituents": constituents,
        "capital_usd": args.capital,
        "start": str(idx[0].date()),
        "end": str(idx[-1].date()),
        "n_sessions": int(len(idx)),
        "yearly_return_basis": {
            "constant_notional_usd": args.capital,
            "return_pct_constant": "sum(daily_pnl) / constant_notional per year",
            "return_pct_chained": "sum(daily_pnl) / start-of-year stacked equity",
        },
        **m,
    }
    meta_path = Path(f"{prefix}{out_suffix}_meta.json")
    meta_path.write_text(json.dumps(meta, indent=2))

    yearly = _yearly_table(out, args.capital)
    yearly_path = Path(f"{prefix}{out_suffix}_yearly.csv")
    yearly.to_csv(yearly_path, index=False)

    # Sub-stack comparison on same calendar
    print(f"\n=== {STACK_NAME} ({combine_mode}) ===", flush=True)
    print(
        f"  Window {meta['start']} → {meta['end']}  ({meta['n_sessions']} sessions)  "
        f"capital ${args.capital:,.0f}",
        flush=True,
    )
    print(
        f"  Return {m['return_pct']:+.1f}%  CAGR {m['cagr_pct']:+.1f}%  "
        f"Sharpe {m['sharpe']:.2f}  MaxDD {m['max_dd_pct']:.1f}%  "
        f"End ${m['end_equity']:,.0f}",
        flush=True,
    )
    print("\n--- Constituent $ PnL (same calendar) ---", flush=True)
    if nav_rebalance == "quarterly":
        print(
            f"  NAV quarterly rebase (reference ${args.capital:,.0f}; "
            "constituents are post-scale totals)",
            flush=True,
        )
    pnl_cols = stack_pnl_cols
    for col in pnl_cols:
        tot = float(panel[col].sum())
        print(f"  {col:24s}  ${tot:+,.0f}  ({100*tot/panel['pnl_best_ideas'].sum():.1f}% of total)", flush=True)

    if nav_rebalance == "quarterly":
        print("\n--- By year (chained NAV after quarterly rebase) ---", flush=True)
        print(
            yearly[
                [
                    "year",
                    "pnl_usd",
                    "return_pct_chained",
                    "max_dd_pct_chained",
                    "start_equity_chained",
                    "end_equity_chained",
                ]
            ].to_string(index=False),
            flush=True,
        )
    else:
        print(
            f"\n--- By year (constant ${args.capital:,.0f} notional — comparable) ---",
            flush=True,
        )
        print(
            yearly[["year", "pnl_usd", "return_pct_constant", "max_dd_pct_constant"]].to_string(
                index=False
            ),
            flush=True,
        )
        print("\n--- By year (chained equity — book label grows with stack) ---", flush=True)
        print(
            yearly[
                [
                    "year",
                    "return_pct_chained",
                    "max_dd_pct_chained",
                    "start_equity_chained",
                    "end_equity_chained",
                ]
            ].to_string(index=False),
            flush=True,
        )

    print(f"\nDaily → {daily_path}", flush=True)
    print(f"Meta  → {meta_path}", flush=True)
    print(f"Yearly → {yearly_path}", flush=True)


if __name__ == "__main__":
    main()
