Skip to content

KB/indicators

Health Score (19-component composite)

Last verified

Composite 0–100 measure of structural market health β€” not direction. A high score means the plumbing is intact (liquidity flowing, credit calm, institutions buying). Normal markets score 55–60.

Components (19, sum to 100)

Component Max What it measures Metric
Energy 10 Energy regime (WTI, crack spread, XLE) energy_regime
HY OAS 8 High-yield credit spreads hy_oas
Stagflation 8 Weighted breakevens (5Y 40%, 10Y 20%, 5Y5Y 20%, 5Y-10Y inversion 20%) stagflation
DIX 7 Institutional dark-pool buying activity dix
GEX 7 Options dealer gamma exposure gex
Breadth 7 % of S&P 500 above 50-day SMA breadth_50d
Liquidity 7 Fed net liquidity direction net_liquidity
Breakeven 6 5Y breakeven inflation expectations (lower is better) breakeven_5y
Vol Term 5 VIX term structure (contango/backwardation) vix_regime
SPY/ZGL 5 Price vs zero gamma level spy_vs_zgl
0DTE PCR 5 Same-day options put/call ratio zero_dte_pcr
Sectors 5 Sector breadth favorability sectors
Correlations 5 Weighted per-pair health (SPY/VIX, SPY/Oil, SPY/DXY, SPY/TNX) correlations
BTC 3 Bitcoin as liquidity proxy btc
SKEW/VIX 3 Tail-risk hedging activity skew_vix
Real Yield 3 10Y TIPS real yield level real_yield
Copper/Gold 2 Growth expectation proxy ratio copper_gold
USD/JPY 2 Carry trade unwind risk usdjpy
HYG 2 Credit ETF selling pressure confirmation hyg_confirmation

Each band-classifier maps the raw value to a per-component points award between 0 and the listed max. Logic lives in app/signals/score.py β€” pure, FastAPI-free, directly unit-testable.

Missing inputs: abstain and renormalize

A component whose input is missing (e.g. a data-source outage left GEX, 0DTE, or the gamma walls dark) does not score 0 β€” scoring it 0 would drag the composite down and read as deteriorating health when it is really just absent data. Instead the component abstains: it contributes nothing and its max leaves the denominator. The score is then:

score = 100 Γ— Ξ£ points(available) / Ξ£ max(available)

This is a no-op on a full-coverage day β€” when every component is available the denominator is 100 and the result equals the plain additive sum, so normal days are numerically identical to before. On a partial-coverage day it changes the read: an upstream data-source outage that zeroes GEX / 0DTE / SPY-ZGL no longer reads as risk-off; those components are excluded, and the score reflects only what was actually measured.

Each component carries an available flag so the dashboard can distinguish a genuine scored-0 (input present, bottom band) from a missing input (greyed out). The /api/v1/signals/score/breakdown response surfaces n_available and max_available so a consumer sees the coverage the score was computed on, plus a coverage_status of "insufficient_data" when max_available drops below 50 (too thin to trust). The Sectors component likewise abstains when sector breadth is missing β€” it no longer falls back to a silent middling β€œmixed” default that looked like a real read.

Score ranges

Score-range readings

Favorable70+Conditions support risk-taking. Most supports intact.
Mixed50 – 70Some supports intact, others not. Be selective.
Cautionary30 – 50Multiple supports weakening. Reduce risk.
Adverse< 30Structural supports broken. Defensive only.

The 50–60 band is where most days live.

Regime-conditional rebalancing

score_weights.json β†’ regime_overrides lets any regime declare scaling factors per component. Currently configured:

Adding an override: {trigger: {field, in|eq}, scales: {component: factor}}. Factors must sum-offset so the post-override max stays at 100.

Score vs regime β€” when they disagree

The dashboard’s bridge text (italicized line under the regime name) labels the divergence:

Score Regime Label Meaning
70+ RISK-ON Favorable Score and regime agree
70+ Not RISK-ON Favorable, with caution Score high but a lagging signal holds the regime back
50-70 Any Mixed Some supports intact, others not
50-70 RISK-OFF/PANIC Mixed, deteriorating Mid-range score but conditions worsening
30-50 Any Cautionary Multiple supports weakening
< 30 PANIC/RISK-OFF Adverse Structural supports broken
< 30 Other Adverse, holding Score very low but regime hasn’t caught up yet

Scale headroom

A May 2026 regime-quantile audit confirmed the 0-100 scale spans cleanly without saturation:

Regime n p5 p50 p95 max
PANIC 18 23 28 39 40
RISK-OFF 289 28 37 49 57
TRANSITIONAL 308 20 47 55 77
CAUTIOUS 467 42 50 58 74
RISK-ON 205 48 55 82 87

See also