fix(research): make the calibration artifact live up to its refusal guarantees

Review of the v4 evidence path. The shipped sensors, bands, methodology bump and
categorical allowlist were found sound; these are gaps in the harness that
produced the evidence for them.

The recommendation gates were optional, so they were not gates. The calendar
anchor lived behind --expected-first-session, which defaulted to None -- so the
committed artifact had no first-date check at all, leaving only a session COUNT
that is tautological (the harness slices the tail of the price series to whatever
was asked for). And the state_v4 <= state_v3 invariant was appended only when
both variants were present, so `--methodology v3` alone could still emit a v4
recommendation having never evaluated v4. The anchor is now a published constant
asserted unconditionally, required explicitly whenever --end/--sessions are
overridden, and v3+v4 are mandatory. Both refusals exit 2.

The P1_SCORE_CAP decision was taken on the wrong population. The agreed rule was
"sole price argmax on >80% of sessions with State >= 40"; the harness reported
only all-session counts and the doc concluded from 17/408 = 4.2%. Measured on the
actual population: 47 qualifying sessions, P1 sole argmax on 17 = **36.2%** (P2
16, P3 14). Still well under 80, so the conclusion holds -- but it was reached
from a denominator that did not test the rule, and 36.2% is a materially
different number to have on the page.

Provenance did not identify the code that produced the artifact. It recorded
git_rev c3ae5ad while the live v4 variant depended on app changes that were still
uncommitted, so checking out that revision would not reproduce it. Now records
git_dirty plus sha256 of regime_monitor_service, breadth_service and the script
itself, and this artifact is regenerated from a clean tree.

The 400- vs 700-day OAS question was described as settled but was not
reproducible: the artifact carried only oas_fetch_days 4748, and
v2_reconstruction patches the per-session window to 3653 regardless, so
--oas-window-days 400 could not simulate it. Patching a window cannot stand in
for data that was simply absent, so v2_reconstruction_oas400 truncates the OAS
SOURCE series instead: avg 26.54, p80 42.52, max 100.00 against published
22.6 / 35.1 / 91.2. Full coverage reproduces all three, so the published figures
predate the truncation. Now recorded in the doc.

v4-vix-only and v4-p1-only had become no-ops: after the cutover the shipped
sensors ARE v4, so patching one candidate in left the other shipped and both
variants evaluated full v4. Each now restores the other sensor to its v3 formula,
and they separate properly (v3 18.13, v4-vix-only 16.64, v4-p1-only 16.28,
v4 14.78 -- each fix contributing about half the move).

Docs: the copy-paste invocation was mangled by a backslash-escaping bug and is
now a fenced, forward-slash command; "Unlike P3 and V1 ... P3's do not" corrected
to "Unlike P1 and V1"; the point-in-time section updated from 400 sessions to the
672-calendar-day / ~464-session window production actually replays; the exercised
52.33 VIX print recorded so the top anchors are not merely asserted.

Tests: band_for now pinned at 64.9/65 from both sides so a silent revert to 80
cannot pass, and the categorical carry-forward test stores locked=True and
asserts it survives -- losing it is half the failure mode, since
update_regime_monitor only auto-refreshes when locked is false.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-08 22:46:10 +02:00
co-authored by Claude Opus 5
parent 3143477a62
commit ec1b0acfad
5 changed files with 142 additions and 1510 deletions
+31 -6
View File
@@ -10,9 +10,14 @@ gate entries, exits, position size, ranking, or alerts about individual setups.
**v4 supersedes v3** (2026-08-08). Unlike v3, whose calibration was ad-hoc and
never landed, every number below is reproducible:
.\.venv\Scripts\python.exe scripts
un_regime_monitor_calibration.py ^
--methodology v2_reconstruction,v3,v4 --cache-dir .calib-cache
```
.venv/Scripts/python.exe scripts/run_regime_monitor_calibration.py --methodology v2_reconstruction,v2_reconstruction_oas400,v3,v4,v4-vix-only,v4-p1-only --cache-dir .calib-cache
```
`v3` and `v4` are mandatory — the row-wise `state_v4 <= state_v3` invariant is
a hard gate and needs both — and the replayed **start** date is asserted
against the published window. The session *count* alone proves nothing, since
the harness slices the tail of the price series to whatever was asked for.
The harness replays the 408 sessions ending 2026-07-24 from the live inputs
(Alpaca for all 33 symbols, FRED for VIX and HY OAS) with no database, and
@@ -244,11 +249,31 @@ Every distribution above inherits that bias. It is the same bias v2 carried, so
the v2/v3 comparison is like-for-like, but the absolute band shares are
optimistic.
**Which OAS window the published v2 figures used.** v2 requested 13 years of HY
OAS and sliced `HY_OAS_REFERENCE_YEARS = 10.0` per session; ICE serves only ~3
years (778 observations from 2023-08-08), so the effective window was that. But
production v2 also fetched only 400 *calendar* days at one point — the bug fixed
2026-08-07 — and whether the published numbers predate that was not recoverable
from the text. Settled by replay rather than assumed: the
`v2_reconstruction_oas400` variant truncates the OAS **source series** to 400
days (patching the per-session window cannot simulate data that was simply
absent) and yields avg 26.54, p80 42.52, max **100.00**, against published
22.6 / 35.1 / 91.2. Full coverage reproduces all three. So the published figures
correspond to the untruncated fetch.
**The top VIX anchors are exercised, not just asserted.** The window contains a
52.33 close (2025-04-08), so the 40 → 80 → 55 → 100 segment is fed by real data
rather than justified from long-run history alone.
## Point-in-time record
The first run under a new `METHODOLOGY` rebuilds the latest 400 trading sessions
with sufficient sensor warm-up; routine runs thereafter insert/update only the
latest trading date. The history API and main chart show only snapshots matching
The first run under a new `METHODOLOGY` rebuilds every session inside
`REBUILD_LOOKBACK_DAYS` — 672 calendar days, roughly 464 trading sessions;
routine runs thereafter insert/update only the latest trading date. The bound is
in calendar days rather than a session count because the binding constraint is
the OAS fetch: each replayed row needs W3's lookback inside
`HY_OAS_WINDOW_DAYS`, so replaying further back would recreate the credit gap a
reseed exists to close. The history API and main chart show only snapshots matching
the current methodology, so a bump reseeds the series rather than splicing two
formulas into one line.