247a7889b9bcbb5ca83b3c050b5bef43d8294446
13
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
43ee619412 |
fix(research): require the v2 reproduction, and correct the P1-cap denominator
Two review findings, plus a lost-edit repair. v2_reconstruction is now a required variant. It carries every published figure the reproduction rests on (avg, p80, max, P3-pegged, W1-live), so a run without it could emit a confident, non-provisional recommendation having checked nothing against v2 at all -- while the methodology doc claims v2 and v3 are reproduced first. The default invocation is now derived from REQUIRED_VARIANTS so the two cannot drift, and a test asserts the default satisfies its own requirement. The doc and the P1_TREND_BREAK_ANCHORS comment still justified skipping the P1_SCORE_CAP with 17/408 = 4.2%, which is the all-session share and does not evaluate the rule. The rule names sessions with State >= 40: 47 of them, P1 sole argmax on 17 = 36.2%, against P2's 16 and P3's 14. Conclusion unchanged -- well under the 80% trigger -- but the published rationale now states the metric that actually decided it. Root cause of that survival: the earlier correction WAS made, but in a script that applied several substitutions and wrote the file once at the end. A later substitution raised, so the successful edits were discarded with it. The "Unlike P3 and V1 ... P3's do not" fix was lost the same way and is restored. Also adds tests for the refusal paths themselves -- missing required variant, unknown variant, custom window with no calendar anchor. They were verified by hand last round but left unpinned, which is the same shape of problem as the optional gates they exist to enforce. All return before any network call. Deliberately not done, as not load-bearing: recording the oas400 variant's missing-credit session count (the truncation conclusion rests on the distribution mismatch, which is already recorded), and generalising _pipeline_gates for arbitrary --end/--sessions windows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3143477a62 |
feat(regime): cut the risk monitor to v4 — desaturate VIX and the trend break
Two sensors saturated in exactly the range where resolution matters, and the top State band had no headroom. Calibrated with scripts/run_regime_monitor_calibration.py over the 408 sessions ending 2026-07-24; the shipped code reproduces that run's band shares exactly (78.9 / 13.0 / 4.7 / 3.4). V1 read VIX 30, 50 and 82 as an identical 100 — the same defect v3 had just removed from P3, left in place one sensor over. In the window it flattened five distinct April-2025 prints (52.33, 46.98, 45.31, 40.72, 38.57) into one value. Now an anchor table reaching full scale at 55, not at 2020's ~82: anchoring the top at a once-in-a-generation print would make VIX 50 read only ~70. Pegged on 14 of 408 sessions before; none now. _under_200 returned a bare 0/100, so P1 printed 100 the moment SMH and QQQ were both under their average — and since the price pillar takes max(P1, P2, P3), that pinned the pillar and stopped P3's ladder resolving for the whole of a selloff. Now graded by depth below the 200-DMA, with a deliberate floor of 20 at the crossing: the break is a genuine binary event, only its depth is graded. Pegged on 46 of 408 sessions before; none now. A 2% break reads ~30, not 100. max() was KEPT — the defect was the step function feeding it, not the vote, and v3's "one capped vote for correlated reads" rationale still holds. P1 is the sole price argmax on 17 of 408 sessions (4.2%), so the P1_SCORE_CAP fallback drafted during design was measured as unnecessary and not shipped. STATE_BANDS breaking 80 -> 65, and only that threshold. Credit returns 0.0 (not None) when calm, so it holds its 20 points pinned at zero and price + breadth + volatility at literal maximum summed to exactly 80.0 — v3's threshold to the decimal, with nothing above it. The sensor is deliberately unchanged: a calm-credit selloff genuinely is less stressed. What was stale is the band, fit on v2 while credit's since-removed percentile leg still contributed. A 2022-style AI/tech drawdown with calm credit computes to 70.3 (no death cross) or 74.0 (with one); 70 would have left 0.33 points of headroom, reproducing the defect. Chosen by scenario arithmetic, and the realized breaking share then lands on 3.4% — the same as v3's, arrived at independently. "v4" added to CATEGORICAL_FUNDAMENTAL_METHODOLOGIES in this same commit, which is load-bearing: that set is checked against the STORED blob, so bumping without it discards the collected observation on first write, leaving fetched_at null and locked false — and update_regime_monitor then fires a paid LLM refresh on every run, forever. Now guarded by a test parametrised over v2 and v3 stored blobs. SENSOR_REVISION deliberately stays 2: a METHODOLOGY change already forces a full reseed via _parse_snapshot, and bumping both would imply the reseed was revision-driven. QUADRANT_STATE_DIVIDER stays 50 because only breaking moved, so alert_service, RegimeChart and the quadrant tests need no change. A new test enforces divider == band boundary on both axes, which nothing did before. Doc renamed to regime-monitor-v4.md with a tombstone at the old path (commit messages cite it), the three open questions converted to resolved with the reasoning that closed them, and indexed in docs/research/README.md for the first time. The P2 limit is stated honestly: _death_cross pegs at a -5% MA gap, so a deep selloff still reaches 100 via P2 — v4 repairs the shallow-to-moderate break, not "the price pillar no longer pegs". DEPLOY: the first run reseeds ~464 sessions. Expect one phantom quadrant alert (the dedup key carries basket_hash, not methodology) and re-run the Event Study manually — its cached report self-invalidates but does not self-regenerate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7fdcac3b55 |
docs: carry the risk-monitor wording through docs, comments and logs
Follows
|
||
|
|
3483797e75 |
fix(regime): reseed stored history on a sensor change, and stop faking an observation
Two review findings on
|
||
|
|
46ace501a2 |
refactor(regime): collapse the monitor page, fix the OAS rebuild window
The page had twelve stacked blocks, several of them different views of the same numbers. The quadrant plot and the score-history chart drew the same two series from the same query key, which read as two datasets; they are now one card with a Time | Path toggle. The two pillar disclosures become one grouped table, and three prose blocks (data quality, basket, coverage) become one provenance chip strip. Page text is now limited to what changes how the reader interprets today's number; the rest moved to the methodology doc. Removes three stale-threshold bugs of one class. The quadrant fell back to v2's 60/60 dividers when quadrant_config was absent -- the real values are 50/40 and they feed alert_service, so the chart could disagree with what actually fires. The gauge fell back to v2's 30/60/80 band ticks, and drew a divider line that always landed on its own "elevated" tick. The time series' reference lines were at 30/60/80, which correspond to nothing in v3; they are now per-axis dashed lines read from the same quadrant_config. Rendering also surfaced a live clipping bug inherited from the old chart: margin.left -18 against YAxis width 28 left ~10px for a 3-digit label, so every Y tick was cut off. HY_OAS_WINDOW_DAYS was 400 *calendar* days while a rebuild replays REBUILD_SESSIONS = 400 *trading* sessions (~579 calendar days), so the oldest ~180 days of any rebuild got no OAS at all and both credit sensors returned None. State then lands at 80% coverage and Warning at exactly MIN_COVERAGE, so both still publish bands -- a series that looks homogeneous while its oldest rows were scored without credit. Widened to 700. This needs no methodology bump: C1 reads [-1] and W3 reads [-21], both from the end, so widening only prepends and every live score is bit-identical. Sequenced deliberately, since acting on the open findings below bumps METHODOLOGY and fires the rebuild. A just-collected fundamental observation was hidden until its effective date -- one day, three over a weekend -- because the live reading called the point-in-time function, so refreshing appeared to do nothing. That was the opposite of what the doc claimed. fundamental_overlay stays the gated record (it runs for every replayed date during a rebuild); current_observation is the live reading and reports the effective date instead of blanking the content. Nothing in the overlay is scored, so showing it early cannot reach a published number. Documents four calculation findings. Three are not implemented, since each changes a published score and so requires a v4 cut: State's top band is a credit-event band (credit returns 0.0 rather than None below the 3.5 anchor, so it is pinned at zero at weight 20 -- with everything else pegged State computes to exactly 80.0, the breaking threshold); V1 saturates at VIX 30; and the deliberate max(P1,P2,P3) defeats P3's anchoring because P1 is binary. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
019ca1342a |
Rewrite Regime Monitor as v3: fundamentals off the score, desaturate P3
The LLM-sourced capex/earnings observations carried 12+8 of 100 Warning points, so both pegged at 100 produced a Warning of 20.0 -- below the event study's 25.3 alarm threshold and still inside the "stable" band. The reading was arithmetically incapable of changing anything on screen, which is why refreshing it appeared to do nothing. They are now a qualitative overlay reported beside the scores rather than diluted into them. Calibrated against the 408 v2 sessions to 2026-07-24, reproduced offline from Alpaca + FRED; the harness matched the stored prod distribution exactly before any parameter was changed. State: - P3 used dd_pct * 5, reaching 100 at a 20% drawdown -- the 90th percentile of the observed distribution -- so 39/408 sessions sat at exactly 100 with no resolution left during the part of a selloff that matters most. Replaced with anchored breakpoints keeping headroom past the observed 36% maximum, blended 2:1 like P1/P2 instead of max(). P3's realized share of State falls from 65% to 40%, matching its nominal weight. - Credit level is now anchors-only. ICE capped FRED's BAMLH0A0HYM2 at a rolling 3-year window in April 2026, silently turning the 10-year percentile leg into a 3-year one that scored 20 points of stress at an OAS of 3.5 -- the level its own anchors call "mild". The anchors already encode the long-run distribution. Warning: - Added HY OAS 20-session widening (25%). The level is pinned at zero below the 3.5 anchor; its rate of change is not. - Divergence tapers to a 0.35 floor instead of a hard price_ret >= 0 gate, which zeroed the sensor through every decline: on 2026-07-24 the basket shed 10 points of participation in 20 sessions and Warning printed exactly 0. - The event study and the live monitor now share one sensor definition, so they cannot silently drift apart. Bands are per axis (State 20/50/80, Warning 20/40/60) with quadrant dividers at 50/40; v2 Warning never exceeded 64.9 against a shared 60, leaving that half of the quadrant unreachable. Realized shares: State 73/15/8/3%, Warning 69/20/8/3%. Snapshots now record credit_history_days and vix_history_days -- the percentile defect went unnoticed for months because nothing asserted the window the code claimed. Cutover: the first run rebuilds 400 sessions automatically; the Event Study job must be re-run, as its cached report self-invalidates on the methodology check. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f714782fa4 | fix: use categorical regime fundamentals | ||
|
|
81c6f5fe2f | fix: refresh latest regime trading session | ||
|
|
1d5b1489be | feat: replace regime monitor with v2 methodology | ||
|
|
66444af65c |
feat: score-history chart on the regime tab
Plots the index, early-warning, and combined scores over time beneath the live gauges, with a 1M/3M/6M/All range toggle and band reference lines — so the trend and any divergence between the scores is visible, not just today's snapshot. - Backend: GET /regime/history + get_regime_history (the three scores per snapshot date from regime_snapshots). - Frontend: recharts line chart, lazy-loaded so recharts ships in its own regime-tab chunk instead of nearly doubling the main bundle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
02b8df58f0 |
fix: populate early-warning/combined on the latest snapshot + recent history
The early-warning score showed n/a because it required an exact date match between the live benchmark (Alpaca, may have today's bar) and the stored universe breadth (DB, often a day behind), which blanked the newest snapshot — the one the UI displays. - Look up the divergence as-of the snapshot date (newest value within a 7-day lag) instead of requiring an exact match. - Backfill early_warning + combined onto recent existing snapshots (the index history predates this signal) so the 7/30-day trends populate on the first run rather than only filling in over the coming weeks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
613fc756ec |
feat: separate live early-warning + combined score on the regime tab
The event study showed the breadth-divergence signal genuinely leads (warned before 7/11 drawdowns, ~6 weeks median, where the coincident baseline almost never did). Surface it live to observe before deciding how to embed it — kept separate from the index, not folded into its weights. - regime_monitor daily job now computes breadth-divergence live and attaches a separate early_warning score plus a combined blend (weighted mean, default 0.6/0.4, configurable via combined_weights) to each snapshot, including the backfill so the 7/30-day trends populate immediately. Stored in breakdown_json — no schema change. Best-effort: a breadth failure can't break the index. - get_regime_monitor returns the index, early_warning, and combined scores each with 7/30-day deltas. - Regime tab shows three gauges (generalized ScoreGauge): coincident index, early warning, and a compact combined blend. Stale snapshots render "—". Note: the daily regime job now also does a universe-wide breadth scan. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
ebff19940b |
feat: add standalone AI/Tech regime-change monitor tab
A new /regime tab scoring how far the AI/Tech bull regime has deteriorated toward a re-rating as a single 0-100 index with per-signal breakdown and a 7/30-day trend. Intentionally decoupled: nothing reads its output to gate or score trades — the daily-pipeline membership is scheduling only. - regime_monitor_service: price sub-scores (P1-P6 via Alpaca, like market_regime), VIX + HY credit spreads via a small FRED helper, weighted aggregation over available signals (missing source -> n/a, dropped from the denominator), one snapshot row/day, and a ~90-day history backfill by replaying the already-fetched series as-of each past day. - F1/F3 fundamentals proposed by the configured grounded LLM (reuses sentiment_provider_service config resolution), with a manual override + lock. - regime_snapshots table (migration 011); endpoints on the existing market router; admin-editable weights/threshold; standalone /regime page. Data needs: prices via Alpaca, VIX/credit via FRED (optional key — signals show n/a without it). No LLM needed for history. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |