13a984a84d1d9fa2fd01983f15a769baef1cdb03
3
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> |
||
|
|
c3ae5ad949 |
feat(research): commit the regime-monitor replay harness, and reproduce v2/v3
v3 was calibrated by replaying the series offline, but that harness was never
committed -- so its published numbers could not be re-derived, and a v4 cut would
have had to choose anchors by argument rather than measurement. This is that
harness, and it reproduces the published figures.
scripts/run_regime_monitor_calibration.py replays State/Warning session by
session from the same inputs the live job uses (Alpaca for all 33 symbols, FRED
for VIX and HY OAS), with no database: breadth and divergence come from
breadth_service's pure helpers. It never reimplements an unchanged live sensor --
_compute_index, _score_pillars, P2, P4 and the Warning sensors are imported and
called. Only candidate formulas (proposed v4) and retired ones (v2, gone from the
codebase) are defined here and patched onto the module for a variant's duration.
Reproduction of the 408 sessions ending 2026-07-24, against the figures in
docs/research/regime-monitor-v3.md:
v2 State avg 22.6 -> 22.68
v2 State p80 35.1 -> 35.1 exact
v2 State max 91.2 -> 91.2 exact
v2 P3 pegged 39 -> 39 exact
v2 W1 live 108 -> 108 exact
v3 State max 87.4 -> 87.4 exact
v3 band shares 73.3/15.0/8.3/3.4 -> 73.0/15.4/8.1/3.4
Three things the harness had to get right to reach that, each of which was
initially wrong and caught by a gate rather than by inspection:
- "W1 live 108" counts NONZERO sessions, not non-null ones. v2's divergence
gate returned 0.0 during any decline (v3 tapers instead), so the retired
divergence formula had to be reconstructed too.
- v2 sliced HY_OAS_REFERENCE_YEARS = 10.0 per session, not v3's 700 days. The
percentile leg ranks against that window, so replaying it short shifted the
middle of the distribution while leaving the max exact.
- The published v2 numbers correspond to FULL OAS coverage. Replaying v2 with
the 400-calendar-day fetch it shipped with yields max 100.0 and 133
credit-less sessions -- so that truncation was not in force when the figures
were taken. Recorded rather than assumed.
The script refuses to emit a band recommendation unless every hard gate passes
(33 symbols fetched, per-symbol warm-up and final bar, full basket on every
session, calendar anchors, 100% coverage, and a row-wise state_v4 <= state_v3
invariant), and exits non-zero. It is meant to be structurally impossible to read
a calibration result out of a run whose pipeline did not validate. No v4 code
ships in this commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|