# Roadmap: Regime Filter + Dual-Weight System
**Created:** 2026-03-16
**Updated:** 2026-03-18
**Status:** Phase 1, 2 & 3 complete — Phase 4 not started
**Goal:** Make the strategy all-weather instead of bull-market-only.
The 2025 OOS results (all 20 combos negative or too few trades) revealed the core issue: the optimizer fit two major crypto bull markets (2020–21, 2023–24); 2025's choppy drawdown exposed the gap.

---

## Progress Summary (as of 2026-03-16)

| Phase | Status | Notes |
|---|---|---|
| 1 — Regime filter (Python) | ✅ Done | `i_regime_window`, `i_regime_entry_min_score` live in all 20 param files |
| 1 — Regime filter (Pine Script) | ✅ Done | `i_regime_window`, `i_regime_entry_min_score` added 2026-03-18 |
| 2 — On-chain signals (Pine + Python) | ✅ Done | MVRV Z-Score + NUPL added; free via Glassnode/CoinMetrics on TV |
| 3 — TV re-export | ✅ Done | All 20 CSVs (4 assets × 5 TFs) re-exported with mvrv_zscore_value + nupl_norm. Note: `zscore` (continuous) only in BTC CSVs — defaults to neutral (50) for other assets. |
| 4 — Dual-weight system | ⬜ Not started | See backlog |

**First optimization results with new signals (BTC 1D, 1hr run 2026-03-16):**
- IS: P&L/DD=922, Sortino=3.55, P&L=32,119%, 36 trades, DD=-34.8%
- OOS: Sortino=-0.85, 11 trades (-34.6%) — 2025 bear market, not strategy failure
- Optimizer found meaningful weights: `i_w_mvrv=-18` (tops suppress entries), `i_w_nupl=+10`, `i_regime_window=14`

---

## Context: Why This Order

1. **Trend filter first** — regime label needed before splitting optimization by regime; also has standalone value.
2. **On-chain data second** — add all new TV columns in one pass to avoid multiple re-exports.
3. **Dual weights third** — once regime labels exist, optimize bull vs. bear param sets separately.
4. **Single re-export per asset** — re-export covers regime filter + on-chain columns together.

---

## Phase 1 — Trend Filter (Regime Gate)

> Detailed spec: [`spec_multi_timeframe_regime_filter.md`](spec_multi_timeframe_regime_filter.md)

### Python ✅
- [x] `strategy_activation_scores.py` — rolling mean gate on entry: `entry_raw = entry_raw & (regime_score > i_regime_entry_min_score)`
- [x] GPU kernel — regime filter skipped in kernel (Option C); comment added
- [x] All 20 asset/TF param JSON files updated with `i_regime_window` and `i_regime_entry_min_score`
- [x] `auto_optimize_loop.py` — `PARAM_META`, `_PARAM_COLS`, `_WEIGHT_TO_CSV_COLS`, DB schema all updated

### Pine Script ✅
- [x] Add `i_regime_window` and `i_regime_entry_min_score` inputs (group: "Regime Filter")
- [x] Compute `regime_score = i_regime_window > 0 ? ta.sma(activation_score_poc, i_regime_window) : -1000.0`
- [x] Gate `longCondition := longCondition and (i_regime_window == 0 or regime_score > i_regime_entry_min_score)`

### Validation ✅ (partial)
- [x] `i_regime_window=0` produces identical results to baseline (confirmed)
- [x] `i_regime_window=7, i_regime_entry_min_score=200` suppresses entries (226 → 71 trades confirmed)
- [ ] Pine/Python parity test on specific bear-market bar (pending Pine Script change)

---

## Phase 2 — On-Chain / External Data Columns

**Source confirmed:** Checkmate (`@_Checkmatey_` on X) via Glassnode/CoinMetrics on TradingView.
**Data confirmed available free** on standard TV plan (Glassnode BTC market cap + CoinMetrics realized cap).

### Pine Script ✅
- [x] MVRV Z-Score block added (adapted from `indicators/IndicatorMVRV_Z_Score.pine`)
  - Exports `mvrv_zscore_value` — discrete {-1, -0.5, -0.25, 0.25, 0.5, 1}, updates at crossovers
  - Also exports raw `Zscore` (0–100 continuous) — available for future use
- [x] Realized Price NUPL added — VWMA(close, timeframe-scaled 365 bars), exports `nupl_norm` clipped [-1, +1]
- [x] `i_w_mvrv` and `i_w_nupl` inputs added to "On-Chain Regime Signals" group
- [x] Both added to `activation_score_poc` and `max_score_poc`

### Python ✅
- [x] `generate_signals()` reads `mvrv_zscore_value` and `nupl_norm` columns; adds `i_w_mvrv`, `i_w_nupl` to score + max_score
- [x] `_prepare_features()` — cols 25 and 26 added; GPU column mapping comment updated
- [x] All 20 param JSON files — `i_w_mvrv` range [-40, +20] step 2; `i_w_nupl` range [-20, +20] step 2

### TV Re-export Status ✅
- [x] All 20 asset/TF CSVs re-exported with `mvrv_zscore_value` and `nupl_norm` columns
  - BTC: all 5 TFs have `mvrv_zscore_value`, `nupl_norm`, and raw `zscore` (continuous)
  - ETH/SOL/LINK: all 15 TFs have `mvrv_zscore_value` and `nupl_norm`; `zscore` absent → defaults to neutral (50.0) in Python, so `i_w_mvrv_cont` ≈ 0 for non-BTC assets
  - SOL starts 2020-04-10; SCORE_START=2018 has no effect on SOL

---

## Phase 3 — TradingView Re-Export (all assets) ✅ Complete (2026-03-17)

- [x] All 4 assets × 5 timeframes re-exported with mvrv_zscore_value + nupl_norm
- [x] Column count and order in `_prepare_features()` verified (28 cols)
- [x] Add regime filter Pine inputs (i_regime_window, i_regime_entry_min_score) — done 2026-03-18
- [ ] Re-run `tools/debug_gpu_cpu_discrepancy.py` if new columns added

---

## Phase 4 — Dual-Weight System

### Concept
Train two param sets per asset/TF combo:
- **Bull params** — optimized on IS bars where `regime_score > bull_threshold`
- **Bear/neutral params** — optimized on IS bars where `regime_score <= bull_threshold`

At runtime, select the active param set based on the current bar's regime score.

### Design decisions
- Option A preferred: pre-filter CSV rows by regime before calling `optimize_strategy.py` (no optimizer changes)
- Store as `results/optimization_winner_<ASSET>_<TF>_bull.csv` and `_bear.csv`
- `validate_strategy.py` switches param set each bar based on regime label
- `oos_dashboard.py` evaluates regime-switching strategy end-to-end

### Tasks
- [ ] Implement `--dual-regime` flag in `auto_optimize_loop.py`
- [ ] Store bull/bear winner CSVs
- [ ] Update `validate_strategy.py` for regime-switching param sets
- [ ] Update `oos_dashboard.py` to evaluate regime-switching strategy
- [ ] Pine Script: dual input groups + `var` regime state variable for runtime switch

---

## Overnight Run Plan (2026-03-16)

**Scope:** BTC 1D only, `--hours 10`
**Why BTC 1D only:** Only CSV with new MVRV/NUPL columns. Other assets still have zeros for those columns — would waste search budget on signals that can't contribute.
**Expected outcome:** Deeper exploration of regime filter + MVRV/NUPL weight space. `i_w_mvrv` range widened to [-40, +20] ahead of this run since winner hit -18 near old boundary.
**After run:** Re-export remaining 19 CSVs, then run full 20-combo batch.

---

## Success Criteria (Overall)

- [ ] At least 5 of 20 asset/TF combos move from ❌/⚠ to ✅ ACCEPT on OOS dashboard
- [ ] BTC 1D OOS Sortino > 0 (11 trades at -0.85 after 1hr run — bear market problem, not strategy)
- [ ] OOS trade count ≥ 15 for 1D timeframes after regime filter tuning
- [ ] Pine/Python parity maintained through all changes

---

## Open Questions

1. **Pine Script regime filter inputs** — needed before next TV re-export. Low effort, do at Phase 3 time.
2. **Additional signals before long run?** — candidates: raw `Zscore` continuous (already in BTC 1D CSV), Puell Multiple, STH/LTH realized price. Each requires a new TV export if added — decide before committing to overnight run.
3. **OOS window fairness** — 14 months of a bear market is a tough test. Consider walk-forward splits in `oos_dashboard.py` for a more balanced evaluation once dual weights are in.
4. **Short signals for bear regime** — natural Phase 4+ extension: when regime is bearish, enter short instead of suppressing entirely.
5. **Dual-weight runtime switching in Pine** — 25+ inputs × 2 = 50+ weight inputs. Consider a `i_regime_mode` toggle (bull/bear/auto) to manage UI complexity.
