> 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.**
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.
scripts/check-signal-variance.mjs now fails (exit 1) when a regime classis 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.
OBSERVED table now constrains the SMA 50/200ratio. 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.
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.
Type: defect correction (threshold calibration + upstream data window).
Historical labels affected: none. v1 labels remain exactly as recorded.
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:
> 0.01 bearish,< -0.005 bullish) were expressed in percent but compared against a
decimal rate. Observed 30d range: [-0.00003, 0.0001]. Unreachable.
-0.0001 / 0.0003against an observed range of [-0.000025, 0.000013].
> 1.5 or < 0.7; observed atr20/atr90 range was [0.70, 1.00], mean 0.85.
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.
bearish above 0.0001 (the 0.01%/8h exchangebaseline; above it longs pay a premium), bullish below 0 (shorts paying).
bullish below 0, bearish above 0.00001.Cross-exchange aggregates run an order of magnitude tighter than single-venue.
bullish (compression) below 0.80, bearish(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.
modelVersion is now stamped on every classification.OBSERVED inregime-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.
scripts/check-signal-variance.mjs) that failswhen any signal returns a single interpretation for N consecutive days.
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.
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.