# Plan: Add 3 Glassnode On-Chain Signals to BTC MLP

**Created:** 2026-06-24  
**Status:** ALL PHASES COMPLETE (2026-06-24/25). Committed d10e159.

## Background

The BTC MLP (55→16→8→1) has plateaued. Three zero-weight features occupy plotchar slots and
are always zero in training data (macd_bullish_norm, rsid_norm, stoch_bot_norm). These can be
replaced in-place with three confirmed-available Glassnode signals — keeping the architecture
at exactly 55→16→8→1 with no index shifting and no change to any existing feature positions.

OI, funding rate, and exchange flow were investigated and are not accessible on the current
TV plan (CryptoQuant/Coinalyze unavailable). See `docs/glassnode_tv_fields.md`.

## Signals Being Added (in-place replacements)

| Col | Removing | Adding | GL ticker | Weight param |
|---|---|---|---|---|
| 3 | `macd_bullish_norm` / `i_w_macd_bullish` | `totalvol_norm` | `GLASSNODE:XTVCBTC_TOTALVOLUMEUSD` | `i_w_totalvol` |
| 6 | `rsid_norm` / `i_w_rsid_osc` | `newaddr_norm` | `GLASSNODE:XTVCBTC_NEWADDRESSES` | `i_w_newaddr` |
| 10 | `stoch_bot_norm` / `i_w_stoch_bottoming` | `sendaddr_norm` | `GLASSNODE:XTVCBTC_SENDINGADDRESSES` | `i_w_sendaddr` |

**Why these three signals are orthogonal to the current set:**
- `totalvol_norm`: on-chain transfer volume intensity — different from SOPR (profitability), NUPL (sentiment), ACTIVE1Y (HODLer ratio), or MVRV (valuation)
- `newaddr_norm`: demand/adoption rate — new participants entering the network
- `sendaddr_norm`: spending pressure — addresses actively moving coins (distinct from SOPR which measures whether those moves are profitable)

**Normalization:** 365-bar percentile rank → [-1, +1] (consistent with other GL signals in strategy)
```pine
pct  = ta.percentrank(raw, 365)   // called unconditionally — not inside ternary
norm = pct / 50.0 - 1.0
```

---

## Punchlist

### Pre-flight checks
- [x] Run `python3 tools/check_pine.py` — pre-existing 64/64 limit in activation_scores.pine (not MLP); all 49 i_w_* params declared ✅
- [x] Run `python3 tools/sync_params.py` — ✅ all 20 params files in sync
- [ ] Confirm `GLASSNODE:XTVCBTC_SENDINGADDRESSES` resolves in TV (probe indicator or symbol search)

---

### Phase 0 — Remove zero-weight signals from Pine ✅ DONE
**Files:** `strategies/strategy_mlp_scores.pine`, `strategies/strategy_activation_scores.pine`

- [x] Add 3 new `request.security()` calls for TOTALVOLUMEUSD, NEWADDRESSES, SENDINGADDRESSES
- [x] Add percentrank normalization (totalvol_norm, newaddr_norm, sendaddr_norm)
- [x] Replace `array.set()` at indices 3/6/10 with new GL signal values
- [x] Replace 3 plotchar calls at cols 3/6/10 with new signal exports
- [x] Rename `i_w_macd_bullish/rsid_osc/stoch_bottoming` → `i_w_totalvol/newaddr/sendaddr` in activation_scores.pine
- [x] Update `max_score_poc` denominator in activation_scores.pine
- [x] `check_pine.py` — 49/49 i_w_* params declared ✅; security calls 19 slots remaining ✅
- [x] `count_pine_security_calls.py` — 19 slots remaining ✅

---

### Phase 1 — Update Python scoring engine ✅ DONE
**Files:** `strategies/strategy_activation_scores.py`, `strategies/strategy_mlp_scores.py`

- [x] `get_col('macd_bullish_norm')` → `get_col('totalvol_norm')`
- [x] `get_col('rsid_norm')` → `get_col('newaddr_norm')`
- [x] `get_col('stoch_bot_norm')` → `get_col('sendaddr_norm')`
- [x] Updated weight params, weighted sum, `max_score` denominator
- [x] Updated `_prepare_features()` cols list indices 3/6/10
- [x] Updated `FEATURE_COLS` and `FEATURE_WEIGHT_PARAMS` in `strategy_mlp_scores.py`
- [x] All 73 tests pass

---

### Phase 2 — Update config and propagate ✅ DONE
**Files:** `config.py`, 20 params JSON files

- [x] `i_w_macd_bullish` → `i_w_totalvol` in WEIGHT_COLS
- [x] `i_w_rsid_osc` → `i_w_newaddr` in WEIGHT_COLS
- [x] `i_w_stoch_bottoming` → `i_w_sendaddr` in WEIGHT_COLS
- [x] `sync_params.py --apply` — added 3 new keys to all 20 params files ✅

---

### Phase 3 — Re-export TV data and validate parity ← **YOU ARE HERE**
**Must do for all 5 BTC timeframes (4H, 6H, 8H, 12H, 1D)**

**TV steps (manual):**
1. Open `libraries/LibraryMoneySupply.pine` → Select All → Copy → paste into TV Pine Editor for LibraryMoneySupply → Publish as **version 16**
2. Open `strategy_mlp_scores.pine` → Select All → Copy
3. In TradingView: open the MLP Scores strategy → Pine Editor → Select All → Paste → Save
4. Confirm the script compiles (no red errors — security call count reduced by 4, now ≤40)
5. For each TF (4H, 6H, 8H, 12H, 1D): switch chart to that TF, then export the data table CSV

**After each re-export, run:**
```bash
python3 tools/validate_chart_data.py
python3 tools/check_mlp_parity.py --asset COINBASE_BTCUSD --tf <TF>
```
Parity delta must be < 0.01 for all 5 TFs before proceeding.

```bash
python3 tools/check_pine.py --mark-valid   # after TV paste confirms it compiles
```

- [x] Pine pasted into TV and compiles without errors (LibraryMoneySupply v16 published; KR+IN removed to stay ≤40 security calls)
- [x] 4H re-exported — new GL cols present, 20405 non-null values through 2026-06-25
- [x] 6H re-exported — 16352 non-null values through 2026-06-25
- [x] 8H re-exported — 12175 non-null values through 2026-06-25
- [x] 12H re-exported — 8004 non-null values through 2026-06-25
- [x] 1D re-exported — 3825 non-null values through 2026-06-25
- Note: parity check against OLD winners is structurally impossible after column rename; data presence + validate_chart_data.py (0 errors) is the correctness gate here

---

### Phase 4 — Retrain and promote ✅ DONE
- [x] Adaptive training: 8 rounds, 8H improved Calmar 0.744→0.810 (bull regime), others converged
- [x] 4H/6H/12H/1D: force-promoted best new-column seed (old winners incompatible after column rename)
- [x] `/mlp-results` run — 8H is clear winner: IS Calmar highest, lowest Max DD, 70% win rate, OOS positive
- [x] Committed d10e159 with full results table

---

## Invariants to preserve

- **Do NOT change col indices 0-2, 4-5, 7-9, 11-54** — replacements are strictly in-place
- **Do NOT touch `stoch_peak_norm`** — it is a condition gate for `longExitCondition`, not a score weight
- **Architecture stays 55→16→8→1** — no Phase 2 dimension change required
- **Non-BTC assets (ETH/SOL/LINK) are out of scope** — still at 50-feature arch; separate task

## Files modified

- `strategies/strategy_mlp_scores.pine`
- `strategies/strategy_activation_scores.py`
- `config.py`
- (auto) all 20 `strategies/params/mlp/mlp_weights_*.json` via `sync_params.py --apply`
