KB / framework
Cross-asset correlations basket
Last verified
The correlations category tracks 20-day rolling Pearson correlations between SPY and four anchor assets: VIX, the US Dollar Index, the 10-year Treasury yield, and WTI crude. Each pair carries a healthy historical range; drift outside that range is the structural break the basket flags. The category fuses into one alignment leg and one health-score component — the same four pair classifications drive both.
The four-pair basket
Pair weights — from app/signals/config/correlation_scoring.json:pair_weights
SPY/VIX and SPY/Oil carry full weight because their relationships are the structural backbone — VIX is the canonical inverse anchor (when SPY rises VIX falls, by construction of how vol prices fear), and SPY/Oil is the primary transmission channel for energy shocks into equity pricing. SPY/DXY and SPY/TNX get half weight because their relationships are real but secondary in information content for equity direction.
Per-pair classification
Each pair’s current 20-day rolling correlation is classified into one of four levels via thresholds in correlation_scoring.json. The thresholds are pair-specific because healthy ranges differ — SPY/VIX is structurally negative, SPY/Oil structurally near-zero, SPY/DXY mildly negative, SPY/TNX mildly positive.
Per-pair healthy bands (from correlation_scoring.json + correlations.py:PAIR_CONFIG)
Bands stack outward through elevated → stretched → extreme per PAIR_CONFIG in app/signals/correlations.py. Cross-pair, the structural break direction varies — SPY/VIX extreme is correlation rising past -0.30 (the inverse anchor cracking), SPY/Oil extreme is correlation past +0.75 or below -0.80 (energy directly repricing equity), and so on.
Level-quality scoring
level_quality maps each classification to a quality factor used by the score component math:
level_quality — from correlation_scoring.json
The score-layer formula at runtime: weighted average of pair qualities × max_points. The Correlations component max is 5 points by default (doubles to 10 under the energy_shock regime override). A pair at normal contributes its full share of points; a pair at extreme contributes nothing.
Worked example. SPY/VIX normal (1.0), SPY/Oil elevated (0.7), SPY/DXY normal (1.0), SPY/TNX stretched (0.3). Weighted average = (1.0×1.0 + 0.7×1.0 + 1.0×0.5 + 0.3×0.5) / (1.0 + 1.0 + 0.5 + 0.5) = 2.35 / 3.0 = 0.783. Component points = 0.783 × 5 ≈ 3.9.
Fusion into the alignment category
The alignment layer maps each pair’s classification level to a directional implication via _CORR_LEVEL_IMPLICATION in implications.py:
normal→ BULLISH (structural relationship intact)elevated→ NEUTRAL (soft drift)stretched→ BEARISH (notable break)extreme→ BEARISH (regime break)
Each pair becomes a leg; legs combine through the shared _fuse_legs helper using the alignment-layer basket weights (0.50/0.20/0.15/0.15). The fused verdict + per-leg breakdown surfaces on /api/v1/signals/alignment under categories.correlations.
Legacy single-pair fallback
When only corr_spy_oil is available — the pre-B2 contract that some backfilled rows still match — the fuse degrades to the legacy single-pair thresholds in implications.py:
corr_spy_oil > -0.30→ BULLISH (oil and equities decoupled)corr_spy_oil < -0.60→ BEARISH (oil shock transmitting directly)- between → NEUTRAL
This preserves byte-identical behaviour for pre-B2 callers and fixture rows. The 4-pair fuse takes over the moment any second pair is also populated. No silent degradation.
Where it surfaces
- API —
GET /api/v1/signals/correlationsreturns the four pair values + per-pair classifications./api/v1/signals/score/breakdownreturns the per-pair quality + weight under the Correlations component./api/v1/signals/alignmentreturns the fused verdict + per-leg legs undercategories.correlations. - Alignment card — one dot in the coincident cluster, lit by the fused implication; click for the per-leg drill-down.
- Dashboard correlations table — bespoke composite card showing all four pair values with their dot tints.
- History —
daily_signals.corr_spy_vix,corr_spy_oil,corr_spy_dxy,corr_spy_tnxcolumns persist per cycle; sparklines feed off these.
Regime-override interaction
The energy_shock regime override doubles the Correlations score-component max from 5 to 10. Under energy stress, correlation breakdowns become the dominant transmission channel — equities, oil, dollar, and rates all start moving together as the macro tape is repriced wholesale. Doubling the weight reflects that empirical observation. See regime overrides for the override schema.
See also
- Five-level signal scale — the dot levels each pair classification ultimately resolves to.
- Leading vs lagging — correlations sits in the coincident cluster.
- Regime overrides — how
energy_shockre-weights this category. - Implementation:
app/signals/correlations.py(computation +PAIR_CONFIG),app/signals/config/correlation_scoring.json(weights + level_quality),app/signals/implications.py(_correlations,_CORR_BASKET_WEIGHTS,_CORR_LEVEL_IMPLICATION).