KB / framework
Drawdown-Risk Lens (20-day, transparent z-score composite)
Last verified
A probabilistic drawdown-risk gauge over the seven credit / inflation / rate LEVELS the full-history study flagged for the platform’s multi-week drawdown lane. Renders as the “Drawdown-Risk Lens · 20d” block in the regime card with a 0–100 score, a low / elevated / high band, and the top contributing legs.
This is not a directional predictor, not a timing trigger, and not the validated logistic from the research. It is a transparent equal-weight composite, chosen deliberately for explainability and graceful degradation over fitted accuracy.
What it measures
Each of the seven levels is standardized against its trailing baseline (default ~2 trading years), oriented by economic risk-direction, and averaged into a single number. Higher = more macro stress relative to the recent norm; lower = calmer.
Why this shape, not a fitted model
The full-history study (.research/SYNTHESIS.md) validated the multi-week drawdown signal as a parsimonious L2 logistic over the same seven features. That model is sklearn-based, and the platform’s prod image deliberately ships no machine-learning runtime — scikit-learn and its peers live in requirements-dev.txt and the no-Dockerfile-reference lint (verify-docs.sh:27) blocks them from ever being COPYed into a production container.
The deployable shape is therefore the transparent composite. Trade-offs, on the record:
- Transparent + auditable. Every contribution is a z-score the operator can recompute by hand. The card surfaces the top drivers so it never moves without showing why.
- Equal weights, not fitted. The validated logistic weighted the seven features differently. Equal weighting gives up some accuracy in exchange for not encoding an opaque fit calibrated to a single bear.
- Degrades gracefully. A missing feature is just dropped from the average; below four available legs the gauge suppresses itself (
status: insufficient_data) rather than report off a thin set.
The seven inputs + risk-orientation
The orientation table lives in app/signals/drawdown_risk.py:_FEATURE_ORIENTATION and is documented inline with the rationale. Summary:
| Feature | Orientation | Reading |
|---|---|---|
hy_oas | +1 | High-yield credit-spread widening = credit stress |
ebp | +1 | Excess bond premium = above-fundamentals default-risk compensation |
nfci | +1 | Positive NFCI = tighter-than-average financial conditions |
real_yield_10y | +1 | Higher real yields = tighter policy / valuation pressure |
t10y3m_spread | −1 | Curve inversion (low/negative) precedes recessions |
breakeven_5y | +1 | Elevated inflation expectations → tightening risk (regime-specific) |
breakeven_10y | +1 | Same — long-end inflation expectations |
A +1 feature contributes +1·z to the mean (positive z = up = more risk). A −1 feature contributes −1·z (so a deeply inverted curve, where z is very negative, still pushes risk up).
How the gauge is computed
For each available feature with a non-zero baseline standard deviation:
z = (level − trailing_mean) / trailing_stdoriented_z = z × orientation
Then:
mean_z = average(oriented_z over available legs)score = 100 / (1 + exp(−mean_z))— a logistic squash with steepness1.0, somean_z = 0maps to50,+1σto ~73,+2σto ~88,−1σto ~27.- Band: low under
+0.5σ, elevated between+0.5σand+1.25σ, high at or above+1.25σ.
If fewer than four legs are populated the gauge returns status: insufficient_data and the dashboard suppresses the block.
Caveats — read these before you act on it
- One bear in-sample. The live daily history covers ~2021–present, so the only validated drawdown regime is 2022. The composite is calibrated to that world. It is unvalidated in any other kind of bear and the bear-onset research wave is meant to escape this constraint by studying long-history macro leaders.
- Equal-weight is a deliberate downgrade in accuracy. The fitted logistic moved the AUC; the composite does not. Treat the gauge as situational awareness, not a quantitative probability.
- The breakeven sign is regime-specific. 2022 was an inflation-driven drawdown; elevated breakevens read as risk here. A deflationary bear would invert that. The orientation is the load-bearing assumption of the gauge — flag the gauge as suspect if the regime story changes.
- It does not contribute to the health score. This is a separate risk-side read; the 19-component health score is unchanged.
Where it lives in the code
- Pure helper:
app/signals/drawdown_risk.py(no DB, no network, no ML). - Route:
GET /api/v1/signals/drawdown-risk(computes trailing baselines + current levels fromdaily_signals, calls the helper). Schema-versioned byDRAWDOWN_RISK_SCHEMA_VERSION. - UI: rendered in the regime card on
/directly below the regime line, above the historical base-rate block. - Tests:
tests/test_drawdown_risk.pypins orientation, banding, the insufficient-data fallback, and the route’s history-driven baseline computation.