﻿# Does the General Index actually predict anything?

*Validation run 2026-08-29. All model files (`data/index.cjs`, `data/scorers/*`, `data/model.db`, `data/config.json`, `index.html`, `api/*`) were treated as read-only inputs and were not modified. Everything below is reproducible from the scripts in this folder.*

---

## What we tested and why

The site publishes a single number — the General Index — built from nine weighted indicators, and it has never been checked against what Bitcoin actually did afterwards. We rebuilt the index day by day back to November 2017 using the model's own scorer code, then asked the only question that matters: **when the index was high, did BTC go up more over the next 30/60/90 days than when it was low?** We then stress-tested that answer against luck, against the weights, and against the possibility that the nine indicators are really only two or three.

---

## Headline answer

**No. As published, the index is backwards — and even backwards, it is not statistically distinguishable from luck.**

Three things, in order of how much they should bother you:

1. **The sign is inverted.** Over nearly nine years, a *higher* General Index reliably preceded *lower* forward returns. When the index read >= +0.50 ("Bullish Zone"), the median next-90-day return was **-23.5%**. When it read between -0.50 and -0.15 ("bearish"), the median was **+11.6%**. At 90 days the ranking is monotone in the wrong direction.

2. **But that inversion is not proven either.** Against a null that respects how slowly the index actually moves, the backwards effect has a p-value of about **0.28** — well inside the range of pure chance. Nine years of daily readings look like 3,207 data points but contain roughly **35 independent 90-day windows**. There is not enough history here to establish *any* effect, in either direction.

3. **The weights are false precision.** Jiggle the nine weights by +/-30% and today's published *band label* changes **50.3% of the time**. The difference between "22%" and "18%" is not carrying real information; the label the site shows today is effectively a coin flip.

A negative result is the honest result. The index is currently a *description* of where price sits relative to its own trend, presented as a *forecast*. It is not a forecast.

---

## Results

### 0. What we could actually rebuild

The scorers were run over `model.db` unmodified. Five of the nine indicators have enough history to reconstruct; four do not.

| Indicator | Weight | History in model.db | Rebuilt? |
|---|---|---|---|
| Power Law | 22% | daily from 2016-05-06 | yes |
| 50v200 DMA | 18% | daily from 2016-10-20 | yes |
| Monthly RSI | 18% | month-ends from 2016-08-31 (RSI defined from 2017-10) | yes |
| MVRV | 12% | daily from 2015-01-01 | yes |
| 14D RSI | 5% | only 352 days in DB — extended from the model's own close series | yes, with a caveat |
| Macro | 10% | **5 point-in-time rows, no history** | **no** |
| ETF Flow | 8% | **9 distinct dates** | **no** |
| Stablecoin Supply | 6% | starts 2024-06 (24 monthly points) | **no** |
| Perps and Funding | 1% | snapshot only | **no** |

So the backtested composite covers **70% of the model's weight** (75% including 14D RSI), renormalised to sum to 1, over **3,207 weekday observations, 2017-11-01 to 2026-08-28**.

**Reconstruction is verified.** On the final date every rebuilt indicator reproduces the live canonical value exactly (Power Law -0.5, DMA -0.25, Monthly RSI 0, MVRV 0.025, 14D RSI 0.5), and `node index.cjs` still returns the canonical composite **-0.05 / "Neutral Zone"**, unchanged.

Scripts: `01_reconstruct.cjs`, output `composite_history.csv`

---

### 1. Backtest — the core result

Forward returns by index bucket, **90 calendar days**, 2017-11 to 2026-08. "Hit" = share of windows with a positive return. The all-period average 90-day return was **+12.8%**, so that is the bar every bucket should be compared against.

| Index bucket | n | mean 90d | median 90d | hit rate |
|---|---:|---:|---:|---:|
| <= -0.50 | **0** | - | - | - |
| -0.50 to -0.15 | 538 | **+22.0%** | +11.6% | 57% |
| -0.15 to +0.15 | 1571 | +15.3% | +7.0% | 59% |
| +0.15 to +0.50 | 672 | +9.6% | -4.4% | 44% |
| >= +0.50 | 336 | **-6.9%** | -23.5% | 34% |

**What this means.** Read down the mean column: it falls steadily as the index rises. That is the exact opposite of the intended behaviour. The most bullish bucket is the *only* one that lost money on average, and it was positive only 34% of the time against a 59% base rate.

Shorter horizons point the same way, more weakly:

| horizon | rank correlation (index vs forward return) | direction |
|---|---:|---|
| 30 days | -0.065 | wrong |
| 60 days | -0.088 | wrong |
| 90 days | -0.145 | wrong |

Per indicator, 90-day rank correlation (positive = works as intended):

| Indicator | corr. | reading |
|---|---:|---|
| Power Law | **-0.318** | strongly backwards — the single biggest source of the inversion |
| Monthly RSI | -0.122 | backwards |
| 50v200 DMA | -0.017 | no signal at all |
| MVRV | +0.139 | right direction, weak |
| 14D RSI | +0.129 | right direction, weak |

**Two structural problems visible in this table.**

- *The <= -0.50 bucket has never occurred.* In nine years the 70%-weight composite never printed below -0.407. The most bearish band on the site is unreachable in practice.
- *Monthly RSI has never been negative.* Across 3,207 days its score was 0 (54.6%), +0.5 (27.5%) or +1.0 (17.9%) — never below zero. At 18% weight that is a permanent **+0.317 average bullish tilt** baked into every reading, not an indicator.

Scripts: `02_backtest.cjs`, output `backtest.json`

---

### 2. Permutation test — is even the backwards effect real?

Statistic: mean 90-day return in the top bucket minus the bottom populated bucket. Real value: **-28.9 percentage points**. We shuffled 5,000 times, three different ways.

| Null model | what it assumes | null s.d. | p (two-sided) |
|---|---|---:|---:|
| A. plain shuffle of dates | every day is independent evidence | 3.2 pts | 0.0002 |
| B. circular rotation | index and returns each keep their own persistence | 30.7 pts | **0.287** |
| B-exact. all 3,116 rotations | as above, exhaustive | 30.0 pts | **0.279** |
| C. block bootstrap (90-day blocks) | as above, resampled | 25.1 pts | **0.233** |

**What this means, plainly.** Null A is the one that makes the result look spectacular, and it is the wrong test. It pretends the index is a fresh independent reading every day. It is not — it drifts slowly, stays in a regime for months, and the 90-day return windows overlap almost completely. Once you build a null that preserves that (B and C), the yardstick of "normal luck" grows from 3 points to **~30 points** — which is the size of the effect itself. The real -28.9 sits at roughly the 12th-15th percentile of chance. Unremarkable.

The same conclusion holds for the rank correlation directly (`07_spearman_pvals.cjs`):

| signal | 90d corr. | honest p (rotation) | naive p (shuffle) |
|---|---:|---:|---:|
| composite, as published | -0.145 | 0.473 | 0.0002 |
| composite, Power Law + Monthly RSI sign-flipped | +0.337 | 0.088 | 0.0002 |
| composite, whole trend trio sign-flipped | +0.161 | 0.459 | 0.0002 |
| Power Law alone | -0.318 | **0.037** | 0.0002 |
| 50v200 DMA alone | -0.017 | 0.915 | 0.328 |
| MVRV alone | +0.139 | 0.674 | 0.0002 |
| 14D RSI alone | +0.129 | 0.252 | 0.0002 |

The only reading that survives an honest null is **Power Law alone, with its sign reversed** — i.e. "stretched far above the power-law line" has genuinely preceded weak 90-day returns, which is the mean-reversion story the power law was always telling. The model currently scores that same condition as **bullish**.

Scripts: `03_permutation.cjs`, `06_signfix.cjs`, `07_spearman_pvals.cjs`

---

### 3. Weight sensitivity — is 22/18/18 meaningful?

5,000 draws, each weight multiplied by a random factor in [0.70, 1.30], renormalised to 100%, applied to **today's live nine indicator scores**.

| measure | result |
|---|---|
| base composite | -0.05, label "Neutral Zone" |
| composite range across 5,000 draws | -0.113 to +0.016 |
| 5th-95th percentile | -0.086 to -0.010 |
| **sign flips** | **1.5%** |
| **published band label flips** | **50.3%** |
| label distribution | "Neutral Zone" 49.7% / "Neutral Zone with Bearish Bias" 50.3% |

**What this means.** The sign is robust — the index is genuinely slightly negative today. But the *words on the site* are not. Today's reading sits within a rounding error of the -0.05 band edge, so half of all defensible weightings produce a different published label. The precision implied by "22 / 18 / 18 / 12 / 10 / 8 / 6 / 5 / 1" is not supported by anything.

On the historical series the directional verdict is at least stable: the sign of the correlation with forward returns flipped in **0 of 5,000** draws (5th-95th percentile: -0.181 to -0.110). The weights are not what makes the model backwards — the sign conventions are.

Scripts: `04_weight_sensitivity.cjs`, output `weight_sensitivity.json`

---

### 4. Collinearity — how many independent signals are there really?

Correlation of the *underlying quantities* (not the coarse scores), 3,207 days:

| | PL sigma | DMA spread | Monthly RSI | MVRV ratio | 14D RSI |
|---|---:|---:|---:|---:|---:|
| Power Law sigma | 1.00 | 0.56 | 0.80 | 0.70 | 0.13 |
| DMA spread | 0.56 | 1.00 | 0.79 | 0.77 | 0.19 |
| Monthly RSI | 0.80 | 0.79 | 1.00 | 0.87 | 0.11 |
| MVRV ratio | 0.70 | 0.77 | 0.87 | 1.00 | 0.42 |
| 14D RSI | 0.13 | 0.19 | 0.11 | 0.42 | 1.00 |

Principal components:

| set | weight covered | PC1 variance | effective independent signals |
|---|---:|---:|---:|
| Power Law + DMA + Monthly RSI (raw drivers) | 58% | **81.1%** | **1.47** |
| Power Law + DMA + Monthly RSI (scores) | 58% | 70.4% | 1.83 |
| all five raw drivers | 75% | 66.6% | **2.04** |
| all five scores | 75% | 59.7% | 2.42 |

("Effective independent signals" is the participation ratio of the eigenvalues: 5 truly independent inputs would score 5; one input measured five times would score 1.)

**The hypothesis is confirmed.** Power Law, 50v200 DMA and Monthly RSI are one factor wearing three hats — a single "how stretched is price above its own trend" measure, 81% of one component, worth about **1.5 independent signals between them** while carrying 58% of the weight. MVRV belongs to the same factor too (it correlates +0.70 to +0.87 with all three in raw form). Only the 14D RSI is close to independent (0.11 to 0.42).

**And the sign conventions partly cancel.** In *score* space MVRV correlates -0.63 to -0.78 with the trio, because MVRV is scored contrarian (high ratio -> bearish) while the other three are scored momentum-style (high -> bullish). So the model takes one underlying quantity, measures it four times, signs it three ways up and one way down, and adds them together. That is why the DMA has a standalone correlation with forward returns of -0.017: near-perfect nothing.

Across the five rebuilt indicators (75% of weight) there are about **two** independent signals, not five.

Scripts: `05_collinearity.cjs`, output `collinearity.json`

---

### 5. Look-ahead bias found

| Where | What it is | Impact |
|---|---|---|
| **50v200 DMA** (18%) | `meta.dma_cutoffs` are percentiles of the **entire** price history. A 2018 reading is bucketed using 2026 data. This forces the score distribution to be exactly 15/15/15/10/15/15/13% by construction. | Rebuilding cutoffs from an expanding window changes **30.3%** of historical days. Conclusions hold either way (90d corr. -0.098 vs -0.145). |
| **Monthly RSI** (18%) | Live mode scores using the *in-progress* month's close. For a historical date that is future information. We used the last completed month instead. | Differs on **21.7%** of days. No effect on today's reading (both 0). |
| **Stablecoin Supply** (6%) | Bands are mu +/- k*sigma computed over the **whole** stored series, so every historical score peeks at its own future. | Untestable — the series is too short to backtest at all. |
| Power Law (22%) | Clean. Constants are asserted (Santostasi 1.0117e-17 * d^5.82) and the sigma unit is fixed at 0.18, so nothing is fitted to the future. | none |
| MVRV (12%) | Clean. Fixed piecewise thresholds; reproduces every stored score exactly (max error 0 across 4,258 rows). | none |
| 14D RSI (5%) | Clean as a formula. But only 352 days exist in the DB, so history had to be extended from the model's own close series. | flagged, not fatal |

---

## What is broken — ranked by severity

**1. Three indicators are signed backwards relative to what they predict (58% of weight).** Power Law, Monthly RSI and (nominally) the DMA all score "price is stretched high" as *bullish*. Historically that condition preceded the worst returns. The Power Law scorer's own header comments even note that the source sheet's legend says "> 70 = Extremely Bearish, Possible Bullish Reversal" while the score cell is momentum-signed — the contradiction was documented and then implemented verbatim.

**2. The index is one factor pretending to be nine.** 58% of the weight buys 1.5 independent signals; 75% of the weight buys about 2. The apparent breadth is decorative.

**3. Nothing here is statistically established, in either direction.** About 35 independent 90-day windows. Honest p-values 0.23 to 0.47 for the composite. Any future claim of predictive power from this dataset alone will be unfalsifiable.

**4. The weights are false precision.** A +/-30% jiggle flips today's published label half the time.

**5. Monthly RSI has never printed a negative score in nine years** — it is an 18%-weight constant bullish offset, not a signal. Relatedly, the "Strongly Bearish" composite band has never occurred.

**6. Look-ahead in the DMA cutoffs (18%) and the stablecoin bands (6%).** The DMA percentile buckets are guaranteed to look well-calibrated in-sample because they are defined from the sample.

**7. A quarter of the model cannot be validated at all.** Macro (10%), ETF Flow (8%), Stablecoin (6%) and Perps (1%) have no usable history in `model.db`.

---

## What fix is needed for each

| # | Fix | Effort | Needs new data? |
|---|---|---|---|
| 1 | **Decide, per indicator, whether it is momentum or mean-reversion, and write the sign down.** Concretely: flip Power Law and Monthly RSI to contrarian (stretched = bearish), which is what MVRV already does and what the power law narrative implies. One-line change in each scorer band map, plus the site legend. Do *not* claim the flipped version is validated — its honest p is 0.088, still not significant. | ~4 hours | no |
| 2 | **Collapse Power Law + DMA + Monthly RSI + MVRV into one "trend stretch" factor** carrying a single weight, and give the freed weight to inputs that are actually independent (14D RSI, ETF flow, stablecoin, macro). Simplest defensible version: z-score the four raw drivers, average them into one factor, weight it ~40%, spread the rest across the independent inputs. | 1-2 days | no |
| 3 | **Stop presenting the index as forward-looking.** Relabel the bands as *state* descriptions ("price is 1.9 sigma below the power-law line") rather than *outlook* ("Bullish Zone"). This is the only change fully supported by the evidence, and it costs nothing but copy. | ~2 hours | no |
| 4 | **Re-weight by constrained regression on forward returns** (non-negative weights summing to 1, fitted on the collapsed factors from #2, evaluated walk-forward with block-bootstrap error bars). Worth doing only *after* #2 — regressing nine collinear inputs on 35 independent windows will overfit spectacularly. Expect the honest answer to be "indistinguishable from equal-weight". | 2-3 days | no |
| 5 | **Give Monthly RSI a symmetric definition, or drop it.** If a score has never gone negative in nine years, either the bands are wrong for BTC or the indicator has no bearish state. Recalibrate the 40/60/70 thresholds on BTC's own monthly-RSI distribution (e.g. quintile cuts) so all five levels are reachable. Same audit for the composite "Strongly Bearish" band. | ~4 hours | no |
| 6 | **Make the DMA cutoffs expanding-window** (percentiles of data strictly before each date, minimum 500 days) and do the same for the stablecoin mu/sigma. `01_reconstruct.cjs` already contains a working expanding-window DMA implementation to copy. Note it changes the live score on ~30% of days, so it is a real product change, not a refactor. | ~1 day | no |
| 7 | **Backfill history for Macro, ETF Flow and Stablecoin, or stop weighting them.** ETF flows are recoverable from public ETF-flow data back to Jan-2024; DXY and VIX from any market data source back decades; Fear and Greed from alternative.me back to 2018; stablecoin supply from DefiLlama back to 2018. Until then 25% of the index is unauditable and should be labelled as such. The STRC/SATA par values look manually entered and may not be backfillable at all. | 2-4 days of ingest work | **yes** |
| 8 | **Re-run this validation after each change, and pre-register the test.** The scripts here run end to end in under a minute. Decide the success criterion *before* looking (e.g. "rotation-null p < 0.05 at 90 days"). | ~1 hour to wire up | no |

---

## What we could NOT test, and why

- **Macro (10%), ETF Flow (8%), Stablecoin (6%), Perps (1%) — 25% of the model.** No usable history exists in `model.db`: `macro_current` is five point-in-time rows, `etf_flows` has nine distinct dates, stablecoin starts mid-2024. Nothing at all can be said about a quarter of the index. It is entirely possible those four are the good ones.
- **Whether the *full nine-indicator* composite behaves like the 70% subset.** Today the two differ a lot: the published composite is -0.05, the 70% subset is -0.217, because ETF Flow (+0.9) and Macro (+0.05) are missing. The inversion we measured is a property of the trend block; the missing quarter could in principle offset it.
- **Whether any effect is real at all.** With ~35 independent 90-day windows this dataset cannot establish or refute an edge of realistic size. Absence of evidence here is genuinely not evidence of absence — but it is also not permission to publish the index as a forecast.
- **Data revisions.** The two price series in the DB disagree by up to 13.5% on some dates (`mvrv.price` vs `dma.close`, e.g. 2017-07-20). We used `dma.close` as the price spine throughout. If the stored MVRV series was ever restated, its history is contaminated in a way we cannot detect from inside the DB.
- **Transaction costs, slippage, or any actual trading rule.** These are unconditional buy-and-hold forward returns bucketed by the index, not a strategy backtest.
- **The 14D RSI real history.** The DB holds 352 days; we recomputed it from the model weekday close series — the same formula on a slightly different calendar than the live table.
- **Regime stability.** We did not split 2017-2021 vs 2022-2026. With 35 independent windows in total, halving the sample would make every number meaningless.

---

## Files

| file | what it is |
|---|---|
| `lib.cjs` | shared helpers: CSV load, forward returns, buckets, rank stats, seeded RNG |
| `01_reconstruct.cjs` -> `composite_history.csv` | daily indicator scores + composite, 2017-11 to 2026-08 |
| `02_backtest.cjs` -> `backtest.json` | forward-return buckets, all horizons and variants |
| `03_permutation.cjs` -> `permutation.json` | 5,000-draw nulls (shuffle / rotation / block bootstrap) |
| `04_weight_sensitivity.cjs` -> `weight_sensitivity.json` | 5,000-draw +/-30% weight perturbation |
| `05_collinearity.cjs` -> `collinearity.json` | correlation matrices and PCA |
| `06_signfix.cjs` -> `signfix.json` | sign-convention variants |
| `07_spearman_pvals.cjs` -> `spearman_pvals.json` | honest p-values for the rank correlations |

All scripts are deterministic (seeded). Run in order with `node 0N_*.cjs` from this directory. No canonical model file was modified.
