← Regime

Regime Classifier — Model Changelog

> The point-in-time guarantee. Regime records its bull/bear/chop label forward

> in time and never restates a published row. That guarantee is only meaningful

> if the model producing the labels is equally auditable — a silent change to a

> threshold shifts the meaning of the series just as surely as rewriting a row.

>

> Every label therefore carries a modelVersion. This file is the complete

> version history. It is published alongside the data sample. **No historical

> label has ever been altered or backfilled, including by the corrections below.**


v2.1.0 — 2026-08-20 — BTC Dominance: absolute level → 7-day trend

What changed. The BTC Dominance signal now votes on the direction of

dominance over a 7-day lookback (> +0.3pp bearish / < −0.3pp bullish / else

neutral) instead of its absolute level (> 55 bearish / < 45 bullish). Its

value field is now the 7-day delta in percentage points, not the level. Weight

is unchanged at 0.10.

Why. The signal was documented as a trend ("rising = risk-off, falling =

risk-on") but implemented as a level test. BTC dominance has sat in the 55–59

band for the entire life of this dataset (30d observed range 55.71–58.83), so the

level rule returned bearish in 7,961 of 7,961 samples — a permanent −0.10

bear vote that was never a market observation.

Impact on the label series. Combined with a legitimate 0.20-weight death

cross (BTC SMA 50/200 bearish in 7,967 of 7,967 samples), the permanent

dominance vote held the bear floor at 0.30/0.90 and made bull

(netScore > +0.20) unreachable: the maximum netScore actually observed

across 7,967 v2.0.0 snapshots was exactly 0.0000. This is the same defect

class the v2.0.0 post-mortem describes — v2.0.0 raised the ceiling from −0.20 to

0.0, which unpinned chop, not bull.

Threshold calibration. ±0.3pp was chosen against the real 7-day-delta

distribution (1,708 hourly samples, range −0.72pp to +2.48pp), producing a

genuine three-state split of ~20% bullish / 25% bearish / 55% neutral. The

±0.5pp alternative was tested and rejected as neutral-heavy (8/23/70). Thresholds

are calibrated against observed distributions, never chosen to produce a desired

label.

What did NOT change, deliberately. The bull/bear thresholds remain symmetric

at ±0.20 and BTC SMA 50/200 keeps its 0.20 weight. Replaying 13 days of history

with the dominance fix applied raises the maximum netScore from 0.0000 to

0.2000 — still at, not above, the bull threshold, so bull remains unprinted

while BTC is in a sustained death cross. **That is a disclosed design property,

not a defect**: a model that weights a 50/200 death cross most heavily should be

reluctant to print bull during one. It has NOT been "fixed" by lowering the

threshold or re-weighting SMA, because tuning a classifier until the sales

artifact looks discriminating would convert a fixable bug into a

misrepresentation — the precise failure the point-in-time warranty above is sold

against.

No historical row was altered. v2.0.0 labels remain exactly as published;

v2.1.0 applies forward only, and the version stamp marks the boundary.

Controls added with this change.

is never observed in the window. It already detected and printed this exact

condition ("classes never observed in window: bull") — but excluded it from the

pass/fail verdict, so it exited 0 for 13 days. A warning nobody must act on is

not a control.

ratio. It previously omitted the model's heaviest input, which let the

"bull must be REACHABLE" test pass using a golden cross — an input production

has never produced.

market_snapshots, so a restart no longer silently drops the signal from the

roster for 7 days.


Canonical signal roster (SSoT)

Never publish a signal COUNT. The roster is version-scoped and the live set

varies legitimately — a signal is omitted when its data is unavailable or its

warm-up window has not elapsed, which is correct behaviour, not a defect. Any

hardcoded "N-signal classifier" claim is stale the moment that happens. It is

also a checkable claim, and we sell integrity to people who check.

The authoritative live roster is the signals[] array on

GET /api/v1/market/regime. Describe the model qualitatively and point at that.

pnpm check:signal-claims enforces this.

| Signal | Weight | v1 | v2 |

|---|---|---|---|

| BTC SMA 50/200 | 0.20 | ✅ | ✅ |

| BTC Funding Rate | 0.10 | ⚠️ pinned neutral | ✅ recalibrated |

| Fear & Greed | 0.10 | ✅ | ✅ |

| BTC Dominance | 0.10 | ⚠️ level, pinned bearish | ✅ v2.1.0 — 7d trend (omitted during warm-up) |

| Stablecoin Supply | 0.10 | ⚠️ pinned neutral | ✅ 7d lookback (omitted during warm-up) |

| Volume Profile | 0.10 | ✅ | ✅ |

| Volatility Regime | 0.10 | ⚠️ pinned neutral | ✅ recalibrated |

| Agg Funding Rate | 0.10 | ⚠️ pinned neutral | ✅ recalibrated |

| DXY (Dollar Index) | 0.10 | ✅ | ✅ |

| Liquidation Imbalance | 0.10 | ❌ retired 2026-04-09 | ❌ retired |

Liquidation Imbalance is RETIRED. Its upstream (CoinGlass) lapsed on

2026-04-09 and the signal silently vanished from the input set. Per

FINANCE-RULING-2026-08-04-001 the dependency is not being restored: funding

and open interest are still served without it, the model already self-normalizes

over the signals actually present (no confidence deflation), and re-adding a

voting input after v2.0.0 would force a v2.1.0 and restart the 7-day history

window ahead of a time-sensitive sale. It may return in a future version only if

a customer names it in writing as a purchase condition.


v2.0.0 — 2026-08-03

Type: defect correction (threshold calibration + upstream data window).

Historical labels affected: none. v1 labels remain exactly as recorded.

What was wrong with v1

v1 could not emit a bull label. Not "rarely" — mathematically could not, given

the values the market actually produced.

Across 2026-03-02 → 2026-08-03 (156 days, 68,098 snapshots) the v1 series

contains zero bull labels: 63,913 bear (93.9%) and 4,186 chop (6.1%).

chop itself fell to 8 observations in June and 0 in July and August. During

that same window BTC traded from a March low of $65,191 to a May high of

$82,782 (+27%) while the model printed bear throughout.

Root cause — the arithmetic. Signal weights are BTC SMA 50/200 = 0.20, all

others 0.10. netScore = bullScore − bearScore, normalized over the total weight

of present signals, and bull requires netScore > +0.20. In the trailing 30

days of v1:

| Bucket | Signals | Weight |

|---|---|---|

| Permanently bearish | BTC SMA 50/200, Fear & Greed, BTC Dominance | 0.40 |

| Frozen at neutral (dead weight in the denominator) | BTC Funding Rate, Agg Funding Rate, Stablecoin Supply, Volatility Regime | 0.40 |

| Actually variable | DXY, Volume Profile | 0.20 |

Maximum attainable netScore was 0.20 − 0.40 = −0.20. The ceiling sat exactly

on the bear/chop boundary, which is also why chop disappeared.

Why four signals were frozen:

  • BTC Funding Rate — unit mismatch (~100×). Thresholds (> 0.01 bearish,
  • < -0.005 bullish) were expressed in percent but compared against a

    decimal rate. Observed 30d range: [-0.00003, 0.0001]. Unreachable.

  • Agg Funding Rate — scale mismatch (~10–20×). Bounds -0.0001 / 0.0003
  • against an observed range of [-0.000025, 0.000013].

  • Volatility Regime — bounds outside the distribution. Fired on
  • > 1.5 or < 0.7; observed atr20/atr90 range was [0.70, 1.00], mean 0.85.

  • Stablecoin Supply — wrong comparison window. The ±0.5% threshold was
  • written for a multi-day move, but the caller supplied the previous poll

    (~2 minutes earlier), so the delta was always ≈0%.

    Contributing: the 10th signal, Liquidation Imbalance, disappeared from the

    input set when its upstream data dependency lapsed (2026-04-09). The classifier

    degraded from 10 signals to 9 silently — no error, no alarm, HTTP 200

    throughout.

    Why it stayed invisible: the unit test suite asserted bull was reachable

    using inputs that cannot occur — fundingRate: -0.01 (≈300× beyond the observed

    minimum), dxy: 95 (observed floor 99.49), btcDominance: 40 (observed floor

    55.43). The tests passed against a market that does not exist.

    What changed in v2.0.0

    baseline; above it longs pay a premium), bullish below 0 (shorts paying).

    Cross-exchange aggregates run an order of magnitude tighter than single-venue.

    (expansion) above 0.95 — recalibrated inside the real distribution.

    fixed. The service now keeps a rolling buffer and compares against the reading

    ~7 days prior. Until the buffer spans a week it returns 0 and the signal is

    omitted entirely rather than emitting a fabricated neutral.

    Guardrails added so this cannot recur silently

    regime-detector.test.ts) with two assertions that would have caught this in

    April: bull must be reachable using only values the market has printed, and

    no signal may read neutral at both ends of its real range.

    when any signal returns a single interpretation for N consecutive days.

    What was explicitly NOT done

    included. The correct record of a broken sensor is the broken reading plus a

    dated disclosure — not a quietly improved history.

    reporting real conditions (a genuine death cross; F&G 20–33; dominance 55–60).

    Only demonstrably broken signals were touched. BTC Dominance's > 55 threshold

    sits at the floor of its observed range and is a known open question for a

    future version — deliberately not changed here, to keep this release scoped to

    defects rather than opinions.


    v1.0.0 — 2026-03-02 → 2026-08-03

    Initial 10-signal weighted classifier. Superseded. Contains the defect

    described above: bull unreachable; chop unreachable from June onward;

    9 signals from 2026-04-09 after Liquidation Imbalance dropped out.

    Retained verbatim as the point-in-time historical record.