ID: S031
Slug: cross-asset-rs-rotation-v1
Run date: 2026-07-01
Outcome: FAILED
Headline metric: best-cell Sharpe 0.59 vs equal-weight baseline 0.78 (and max-DD 30% vs 20%)
Fail reason: The rotation adds drawdown and volatility without adding return. No lookback beats the equal-weight sleeve baseline on a risk-adjusted basis; the best (12-month) beats it only on raw CAGR while running a deeper drawdown (30% vs 20%), a lower Sharpe (0.59 vs 0.78), and an insignificant excess (Bonferroni p = 1.0). And the whole cross-asset premise loses to simply owning SPY. The "less correlated" thesis does not survive honest costs and a tail-aware gate.
What we tested
A frozen 26-sleeve ETF universe (9 sectors · 5 commodities · 4 bonds · 2 currencies · 6 countries, all with full 2010→2026 history), ranked monthly by trailing L-month total return, holding the top 3 equal-weight, rebalanced monthly, 30 bps on turnover. Lookback sweep L ∈ {4, 6, 9, 12} months — four pre-registered cells. Baseline: equal-weight buy-and-hold of all 26 sleeves; secondary sanity: SPY. Best-cell significance deflated (Bonferroni) over the four cells; S004's prior counts in the C1 family.
What we found
| CAGR | max-DD | Sharpe | excess/yr vs base | p (raw) | |
|---|---|---|---|---|---|
| Equal-weight baseline | 8.1% | 20% | 0.78 | — | — |
| SPY buy-and-hold | 14.7% | 24% | 1.03 | — | — |
| Rotation L=4mo | 4.9% | 33% | 0.39 | −2.7% | 0.69 |
| Rotation L=6mo | 6.6% | 25% | 0.47 | −0.7% | 0.54 |
| Rotation L=9mo | 5.8% | 33% | 0.45 | −1.1% | 0.59 |
| Rotation L=12mo (best) | 8.9% | 30% | 0.59 | +2.4% | 0.32 |
Gate (best cell, L=12): return beats baseline PASS (8.9% > 8.1%) — but drawdown FAIL (30% > 25% ceiling and > the 20% baseline), Sharpe FAIL (0.59 < 0.78), deflated significance FAIL (Bonferroni p = 1.0). Cut.
Why this matters
Three canonical swing edges — short-term reversion (B1), trend-continuation (A1), cross-asset RS rotation (C1) — each mechanised honestly, run survivorship-free with real costs and a tail-aware gate, and each cut on its own structural wall. C1's wall is the one its own sales pitch glosses: cross-asset momentum is a single factor that re-correlates in a crash, so concentrating on it buys drawdown, not diversification. This is exactly why the pitch must be sold honestly — and here it isn't sold at all, because it doesn't clear the gate.
What this doesn't tell us yet
- The result is sample-dependent (2010→2026 was a strong US-equity regime that penalised cross-asset diversification); a different regime could differ — but that is precisely the un-robustness a walk-forward would punish, and Stage 1 already fails.
- It condemns this top-3 momentum rotation, not every cross-asset idea — a dual-momentum (absolute filter), a risk-parity weighting, or a wider-hold variant are separate
S-NNNs. But the burden is now high: the base rotation lost to buy-and-hold on every risk-adjusted measure.
What happens next
No Stage 2. With B1 and A1 closed and C1 cut on its first configuration, the swing pillar's three a-priori families are all answered no at Stage 1 under honest gates. The disciplined reading is to treat the swing pillar as substantially answered — a transparent fail record across the canonical edges — and to redirect research budget to the live pillars (0DTE forward collection, the iron-fly drift line), unless the owner wants to spend a trial on a specific C1 variant (dual-momentum / risk-parity) with eyes open to the high bar.
S031 is recorded failed; C1 cumulative family trials = 4 cells + S004.
What we tested — the recipe
Slice & dice
For the specialist — methodology details (click to expand)
- Baseline equal-weight 26 sleeves: CAGR 8.1%, max-DD 20%, Sharpe 0.78, 185 months. SPY: 14.7% / 24% / 1.03.
- Best cell L=12mo: CAGR 8.9%, max-DD 30%, Sharpe 0.59, excess +2.4%/yr, bootstrap p 0.32 → Bonferroni (×4) 1.0.
- Total-return (dividend-adjusted) prices; ROC through prior month-end, positions set on the first trading day; 30 bps on realised turnover; long-only, no leverage.
- Artifacts:
ab_summary.json,charts.json. Runner:lab/quickkill/cross-asset-rs-rotation-v1/run.py.