Verification Report  ·  commit f26ebd0

Engine verification
report.

Result
Pass
Checks
339 / 339
Commit
f26ebd0
Run (UTC)
2026-11-02

Generated by the test suite in the repository, executed by GitHub Actions against the commit named above, and reproduced below in full and unedited.

Put-call parity 5/5
Dividend applied once 3/3
CRR convergence 2/2
American ≥ European 5/5
One-sigma band accuracy 6/6
Band breach probability 2/2
Volatility lookback 1/1
Greeks sanity 4/4
Monte Carlo memory 1/1
Causality — no look-ahead 3/3
POP from the seller's side 4/4
Implied volatility solver 11/11
Scope

What these tests establish, and what they do not.

Every check is a closed-form identity or a convergence property: a fact about the mathematics that is true or false independently of any market view. Put-call parity either holds or it does not. A binomial lattice either converges to the closed-form price or it does not. No market data, no API credentials and no network access are involved, which is why the same commit produces the same report on any machine.

They make no claim about trading results and none should be read into them. Verifying that an option is priced correctly says nothing about whether selling it is profitable. What is established here is that the arithmetic underneath the terminal is right.

Spot
100.00
Strike
100.00
Tenor
30 DTE
Risk-free
4.00%
Volatility
30.0%
Dividend q
0 / 2 / 5%
Lattice steps
800
MC paths
50,000
Same series, same day, five input lengthsidentical price history throughout
v30.5.2v30.5.3+601262525041000bars of history handed to the estimatorannualised vol
The old estimator had no lookback window — it measured whatever DataFrame it was handed, so the volatility feeding the expected-move bands changed with the caller's fetch depth. A signal that does not return the same answer twice cannot be validated at all.
One-sigma band calibration, before and afterterminal breach probability of the lower band
theory 15.87%16.93%v30.5.215.87%v30.5.3+30 DTE · σ 30%21.54%v30.5.215.86%v30.5.3+6 months · σ 60%
The band omitted the Ito drift term, so it was not a lognormal quantile and sat too close to spot. Every put strike taken from it was slightly more aggressive than its label implied. Corrected, it lands on theory at both tenors.
Put-call parity residualsthree dividend yields · build f26ebd0
tolerance 2e-4q = 0%9.51e-07q = 2%1.30e-07q = 5%0.00e+00residual of C − P − (S·e^−qT − K·e^−rT), log scale
Residuals sit at the rounding floor of the six decimal places the engine returns — the q=5% case is exact to every digit reported, which is why its bar has no length on a log scale. Parity is not approximately satisfied here; it is satisfied to the precision the output is quoted at. The suite also checks delta parity, which the published v30.5.3 report did not. Parity is not approximately satisfied here; it is satisfied to the precision the output is quoted at.
Early-exercise premium by case800-step lattice · American less European
zero — American may never price below EuropeanPut, q = 0%+0.96336Put, q = 3%+0.26194Call, q = 6%+0.41371
A negative value here would mean the American tree had priced an option below its European equivalent, which is arbitrage and a guaranteed sign of a broken lattice. The premium is measured against the Black-Scholes price rather than the raw lattice, and both returned prices sit on that same basis, so the invariant holds by construction rather than by luck. The suite checks it across 60 American/European pairs, not the three shown.
Monte Carlo memory at maximum paths50,000 paths · measured peak
309 MB measured peak500 MB ceilingpath matrix is bounded, not proportional to a user-selectable slider
The maximum path count is the case that matters: if memory is going to break, it breaks at the top of the range, on someone else's session, in the middle of a scan.
Independent Verification

How to check this yourself.

The suite exits non-zero on any failure, so a passing run cannot be a silent one: a broken build turns the check red in public rather than reporting success by default. The report header names the engine version it certifies alongside the commit it was generated from. Where that version matches the one shown in the terminal sidebar, the software you are using is the software this report describes.

Full Report

Unedited output.

VRP QUANT — ENGINE VERIFICATION REPORT
======================================================================
Engine:     vrp_quant_engine_v30_5_37.py
Engine parts: 17 files in engine_v30_5_37/
Commit:     f26ebd038fc051abb23d2dc5fc4ab4c83e3165ea
Branch:     main
Engine SHA-256: e50297ded701141e54d8a09404ff48c003338c102e1bd944febac45bd1b1c82b
Packages:   numpy 2.1.3, pandas 2.3.3, scipy 1.16.3, arch 7.2.0, yfinance 0.2.66, streamlit 1.64.0, plotly 6.9.0, supabase 2.31.0, bcrypt 4.3.0, requests 2.34.2
Run UTC:    2026-11-02 04:10:00
Result:     PASS  (339 passed, 0 failed)
======================================================================

Every check below is a closed-form identity or a convergence property.
No market data, no API keys, no network access. These verify that the
pricing mathematics is correct — they make no claim about trading results.


=== 1. PUT-CALL PARITY ===
  [PASS] parity at q=0.0  — resid -9.51e-07
  [PASS] parity at q=0.02  — resid +1.30e-07
  [PASS] parity at q=0.05  — resid +0.00e+00
  [PASS] delta parity at q=0.0  — resid +0.00e+00
  [PASS] delta parity at q=0.03  — resid -1.19e-05

=== 2. DIVIDEND APPLIED EXACTLY ONCE ===
  [PASS] q enters S_adj or drift, not both  — max diff +4.91e-07 at (0.03, 'put')
  [PASS] CRR carries the same q as Black-Scholes  — max 0.0000%
  [PASS] Monte Carlo drifts at (r - q), not r  — E[S_T] 102.06 vs 102.02 (0.04%)

=== 3. CRR CONVERGENCE TO BLACK-SCHOLES ===
  [PASS] CRR European put across 320 vol/moneyness/tenor cases  — 320/320 within 0.1% or $0.005; worst absolute $0.01545 at (545.1, 599.6, 1.0, 0.721)
  [PASS] CRR European call across 320 vol/moneyness/tenor cases  — 320/320 within 0.1% or $0.005; worst absolute $0.01545 at (545.1, 599.6, 1.0, 0.721)

=== 4. AMERICAN >= EUROPEAN, PREMIUM NON-NEGATIVE ===
  [PASS] American put >= European (q=0.0)  — early-ex +0.96336
  [PASS] American put >= European (q=0.03)  — early-ex +0.26194
  [PASS] American call >= European (q=0.06)  — early-ex +0.41371
  [PASS] invariant holds across 60 American/European pairs  — 60/60 with American >= European and premium >= 0
  [PASS] American price within 0.15% of a 4000-step reference  — worst 0.1286% at (366.2, 256.3, 0.451)

=== 5. ONE-SIGMA BAND VS TRUE LOGNORMAL QUANTILE ===
  [PASS] lower_1s drift-corrected (T=0.082, vol=30%)  — band sits +0.00% of spot from the true 1SD quantile
  [PASS] upper_1s drift-corrected (T=0.082, vol=30%)  — band sits +0.00% of spot from the true 1SD quantile
  [PASS] lower_1s drift-corrected (T=0.123, vol=45%)  — band sits -0.00% of spot from the true 1SD quantile
  [PASS] upper_1s drift-corrected (T=0.123, vol=45%)  — band sits +0.00% of spot from the true 1SD quantile
  [PASS] lower_1s drift-corrected (T=0.5, vol=60%)  — band sits -0.00% of spot from the true 1SD quantile
  [PASS] upper_1s drift-corrected (T=0.5, vol=60%)  — band sits -0.00% of spot from the true 1SD quantile

=== 6. IMPLIED BREACH PROBABILITY OF THE 1SD BAND ===
  [PASS] lower_1s terminal breach prob ~15.9% (T=0.082, vol=30%)  — actual 15.87%
  [PASS] lower_1s terminal breach prob ~15.9% (T=0.5, vol=60%)  — actual 15.86%

=== 7. VOLATILITY ESTIMATOR: LOOKBACK STABILITY ===
  [PASS] calculate_vol has a fixed lookback  — vol by input length: 60d=16.6%, 126d=16.7%, 252d=16.7%, 504d=16.7%, 1000d=16.7%

=== 8. GREEKS SANITY ===
  [PASS] put delta in [-1,0]  — -0.4371
  [PASS] gamma > 0  — 0.02627
  [PASS] vega > 0  — 0.1970
  [PASS] long put theta < 0 (time decay)  — -0.0257/day

=== 9. MONTE CARLO MEMORY BOUND AT MAX PATHS ===
  [PASS] MC peak memory under 500 MB at 50k paths  — 282 MB, path matrix [50000, 30]
  [PASS] MC peak memory under 500 MB at 50k paths, one-year tenor  — 364 MB, path matrix [39682, 252]

=== 10. CAUSALITY — NO LOOK-AHEAD IN THE BACKTEST ===
  [PASS] Kalman centreline is prefix-stable  — max drift 0.00e+00 over 600 bars
  [PASS] causal GARCH is prefix-stable  — max drift 0.00e+00 over 693 bars
  [PASS] prefix-stability test can detect look-ahead (negative control)  — non-causal apply_garch drifts 0.0065 on the same prefix

=== 11. POP FROM THE SHORT SELLER'S PERSPECTIVE ===
  [PASS] POP equals P(premium - payoff > 0)  — 0.9474
  [PASS] POP with premium >= bare P(expiring OTM)  — 0.9474 vs 0.9450
  [PASS] short put breakeven = strike - premium  — $296.4755
  [PASS] expected P&L ~ 0 under the risk-neutral drift  — $+0.0369

=== 12. IMPLIED VOLATILITY SOLVER ===
  [PASS] IV round-trips to the pricing vol across 5488 cases  — worst error 1.90e-05 in vol at S=425.0 K=446.25 7d vol=10% call q=3%
  [PASS] options this engine priced are solvable by this engine  — 374/5488 (6.8%) declined, all with a stated reason: {'non_positive_price': 91, 'below_intrinsic': 42, 'vol_not_identified': 241}
  [PASS] at-the-money band solves without exception  — 1440 cases (0.98-1.02 moneyness, 7-90 DTE, 8%-180% vol), 0 declined, worst error 4.00e-06
  [PASS] call IV equals put IV at the same strike and tenor  — max disagreement 0.00e+00 across 6 vol/dividend combinations
  [PASS] IV recovers the pricing vol on a dividend payer  — q=3% chain, true 30.00% -> 30.00% with q, 31.35% if q is ignored
  [PASS] degenerate and no-arbitrage inputs return None, never raise  — 8 cases: expiry, zero spot, zero strike, non-positive/NaN price, sub-intrinsic price, price above the discounted strike
  [PASS] negative control reproduces the v30.5.6 seed-return  — prior solver returns its own 0.20 seed on a deep-ITM put priced at 22%
  [PASS] current solver declines the case the prior one got wrong  — declined with status 'below_intrinsic' — the option is worth its intrinsic value to within a rounding error at every volatility in the bracket, so no volatility is identified and none is reported
  [PASS] far-out-of-the-money strike is declined, not seeded  — 14-day put 38% out of the money priced at $0.000000: status 'non_positive_price'
  [PASS] a market too wide to identify a volatility returns None  — 5c-wide ATM quote solves to 40.00%; the same price quoted $12 wide is declined
  [PASS] ATM IV acceptance band is tighter than the solver bracket  — solver searches [0.5%, 500%]; only [1%, 300%] is allowed to reach the Greeks, the trees and the Monte Carlo

=== 13. GARCH CAUSALITY UNDER FUTURE PERTURBATION ===
  [PASS] no earlier forecast moves when the future changes (incl. inside refit blocks)  — max drift 0.00e+00 over 3 seeds x 2 lengths, cuts before/at/after refits
  [PASS] causal with missing observations  — max drift 0.00e+00
  [PASS] forecast for session t ignores the return of session t, uses it at t+1  — a 30% shock at t leaves vol[t] unchanged and moves vol[t+1]
  [PASS] every estimate carries a status; unavailable rows are NaN, not back-filled  — garch, unavailable, warmup_ewma
  [PASS] failed fits are reported and stay causal  — refit failure keeps prior params; no fit ever -> EWMA, flagged
  [PASS] sequential recursion matches arch's fixed-parameter recursion  — max variance diff 2.22e-16
  [PASS] perturbation test detects the pre-fix GARCH leak (negative control)  — pre-fix estimator drifts 0.0111 (annualised vol) on the same test

=== 14. UNDERLYING SIGNAL BACKTEST INTEGRITY ===
  [PASS] reported OOS window starts after the first training window  — OOS from 2023-12-20, 268 sessions
  [PASS] positions act only after their signals are available  — prices changed from session 400: positions through 400 and returns before 400 unchanged
  [PASS] flat exposure earns no strategy return
  [PASS] transaction costs occur exactly on position changes  — 4 changes, all charged
  [PASS] missing history cannot produce a zero-return, zero-risk result  — status insufficient_history; legacy tuple is NaN, not 0.0
  [PASS] drawdown counts a loss on the first session  — -10.00% for returns [-10%, +5%]

=== 15. POSITION GREEKS ===
  [PASS] short put: delta +, gamma -, theta +, vega -, premium received  — delta +0.2481, gamma -0.03680, theta +0.0423
  [PASS] reversing the position reverses every Greek and the cash flow
  [PASS] doubling contracts doubles exposure
  [PASS] equal and opposite positions net to zero
  [PASS] a two-leg vertical equals the sum of its legs  — 3x 95/90 short put spread delta +45.87
  [PASS] position Greek = instrument Greek x signed contracts x 100
  [PASS] side-aware P&L: long = -short; short POP equals the seller-side POP  — short POP 0.7748, long POP 0.2252

=== 16. SUBSCRIPTION ENTITLEMENTS ===
  [PASS] Analyst receives every feature its plan description lists  — Market Scanner (sector universes), Deep Dive, Monte Carlo to 10,000 paths
  [PASS] Analyst cannot exceed its path limit or run Apex-only models  — 50,000 requested -> 10,000 run
  [PASS] Apex Ultimate receives every listed feature; 50,000-path ceiling holds
  [PASS] missing or unknown tier values get the least-privileged plan
  [PASS] terminal enforces the entitlement table; login page carries no prices or checkout  — backend id GOD_MODE preserved; no PayPal link read; research-and-education notice shown

=== 17. MONTE CARLO VALUATION CONSISTENCY ===
  [PASS] zero jump intensity recovers Black-Scholes; jump prices obey parity  — |Merton(λ=0) - BS| 0.0e+00, parity residual 6.4e-14
  [PASS] discounted simulated payoffs match the matching model price  — worst |z| 1.47 across diffusion/jump, puts/calls (limit 4)
  [PASS] model-value premium gives zero expected P&L within error; long = -short  — short 5x: -2.66 ± 11.22
  [PASS] seller POP counts premium; breakeven = strike - premium carried to expiry  — POP 0.8318 > P(OTM) 0.8045, breakeven 328.90
  [PASS] higher draw counts reduce sampling error  — SE ratio 50k/800k = 3.99 (theory 4.0)
  [PASS] fixed-seed sign reversal from v30.5.9 is corrected  — true +48.31; old fixed-seed method -28.05; now +43.39 ± 11.19
  [PASS] analytics sample memory stays bounded  — 1,000,000 draws, peak 15 MB (limit 40)

=== 18. DATA CONTRACT, QUOTES, DIVIDENDS, STRUCTURES, SCANNER ===
  [PASS] Polygon quote maps bid and ask the right way round  — bid 99.9, ask 100.1
  [PASS] quote freshness classified from the market timestamp  — 7 cases incl. crypto clock and missing time
  [PASS] future-dated provider timestamps cannot be labelled live
  [PASS] cached quote freshness is recomputed from its market timestamp
  [PASS] equal turnover across share, base-asset and USD-notional volume; zero/missing = unknown  — $50M each way; notional is not multiplied by price again
  [PASS] liquidity gate reports a reason for every outcome
  [PASS] stale trades cannot pose as fresh prices; last trades carry real uncertainty  — mid 1.05; fresh last ±0.10; 9-day-old last refused
  [PASS] one dividend input reaches Greeks, Merton and simulation identically  — BS 2.0130, sim 2.0131, forward ratio 0.99999 (q=3%)
  [PASS] confirmed zero dividends distinguished from unavailable  — crypto -> confirmed_zero
  [PASS] every structure's delta matches its direction; rich premium -> credit; no directives  — Short put vertical, Long call vertical, Short call vertical, Long put vertical, Iron condor, No structure fit
  [PASS] scanner counts every exclusion, ranks as stated, and keeps rows coherent  — 8 requested -> 3 excluded (1 each: no data, short history, thin) -> 5 scored -> top 3 by VRP [7.3, 3.0, 1.0]
  [PASS] momentum lookback is calendar time for stocks and crypto alike  — 28-day lookback price: stock 109.331 vs crypto 109.331 on the same path
  [PASS] structure premium is priced at implied, not realised, volatility  — Short put vertical: shown model credit $0.60, legs at IV $0.60 (at RV it would be $0.31)
  [PASS] bars with a missing or zero price are removed before any model  — empty final Close and a zero Low dropped; support/resistance finite; last-bar date corrected
  [PASS] the build fingerprint shown in the terminal identifies this exact engine file  — e50297ded701 · v30.5.37

=== 19. SCHWAB MARKET-DATA ADAPTER (SIMULATED RESPONSES, NO NETWORK) ===
  [PASS] Schwab stays off unless the deployment is declared personal  — credentials and a live token present, switch off -> not configured, not used, not listed
  [PASS] account and order endpoints are unreachable from the client  — a /trader/v1/ path raises before any request is made
  [PASS] Schwab callback rejects a foreign address and mismatched OAuth state
  [PASS] sign-in: percent-encoded code decoded, rejected code explained  — C0DE%40abc -> C0DE@abc accepted; a refused code tells the user it expires in ~30 s
  [PASS] token refresh is serialised across scanner workers  — 8 concurrent requests on an expired token -> 1 refresh; a 401 -> 1 refresh + 1 retry
  [PASS] a sign-in older than 7 days is not used  — falls back to the free sources instead of sending a dead token
  [PASS] app key, secret and tokens are masked in status text  — k ***REDACTED*** s ***REDACTED*** t ***REDACTED***
  [PASS] request pacer holds the per-minute cap  — cap 5 -> 5 of 7 admitted inside one minute (live cap 110; Schwab's 120)
  [PASS] quote fields mapped; each daily bar lands on its own trading day  — quote 101.25 (101.2/101.3); 520 bars stamped at midnight Central all dated correctly
  [PASS] Schwab chain: nearest expiry, adjusted contracts excluded, IV solved not copied  — expiry 2026-11-29; solved 31.99% vs true 32.00% (Schwab's own field said 55%)
  [PASS] scanner IV uses the chain's own stock price; a 25%+ disagreement is refused  — stale spot 97 vs chain 100: with snapshot 31.99%, without it the chain is rejected (call/put gap); chain at 150 vs history 100 -> no IV
  [PASS] scanner VRP = solved ATM IV - 21-day realised, with its basis stated  — -4.9 pts vs 32.0 - 36.9 = -4.9; IV Basis 'ATM IV (Schwab, 28d; parity carry approximate)'
  [PASS] disconnect drops the sign-in and every cached Schwab result

=== 20. INDEPENDENT-AUDIT FINDINGS (V30.5.14) ===
  [PASS] Yang-Zhang uses one window for all three components  — 80 series x windows: worst gap to the textbook estimator 0.00e+00 vol pts (v30.5.13: up to 2.9 pts at 21 days)
  [PASS] zero volatility returns the exact limit; invalid inputs raise a clear error  — sigma=0 vs sigma=1e-6: worst gap 0.0e+00; 5/5 invalid inputs refused
  [PASS] Sharpe annualises on 365 days for crypto, 252 for equities  — 365-day Sharpe -1.75
  [PASS] 1-day VaR: positive loss, last 252 returns, applied to the live price  — 4.81% = $5.92 on $123; under 60 returns -> not shown
  [PASS] reversal RSI is Wilder's (platform-standard) smoothing  — last RSI 34.2548 vs reference 34.2548
  [PASS] a refresh lost to a network error is 'failed', not 'sign-in needed'  — the sign-in stays valid and the next request retries

=== 21. SCAN JOURNAL (BUILD, SCORE, INDEPENDENT STATISTICS) ===
  [PASS] journal reference: 16-delta short put, exact model POP, expiry-based horizon  — delta -0.1600; POP 0.8417 (exact 0.8417); solved horizon 28d, proxy 30d
  [PASS] journal scoring: waits for the target to close, weekend target uses Friday, outcomes exact  — realised 41.14% vs implied 32%; outcome close 95.2224
  [PASS] independent subset has no overlapping windows; Wilson interval exact  — 120 daily entries -> 6 independent; 80/100 -> 0.7112–0.8666
  [PASS] solved-IV and proxy entries are summarised separately
  [PASS] end to end: simulated at the implied vol, realised POP matches model POP  — model 83.6% vs realised 83.7% (95% 80.5–86.4%), n = 600
  [PASS] every scored name is journaled, not only the rows shown  — 4 scored, 1 shown, 4 journaled

=== 22. ACCESS CONTROL AND CREDENTIAL HYGIENE ===
  [PASS] synthetic calendars hold exactly n sessions on every weekday  — 7/7 anchors Monday-Sunday; pandas 2.3.3
  [PASS] database key read correctly; only service_role and secret keys count as server keys  — anon->anon, service->service_role, auth->authenticated, secret->secret, publishable->publishable, stub->unknown, empty->unknown, bad_jwt->unknown
  [PASS] password hashes recognised by full format, including pgcrypto's $2a$  — $2b$ and $2a$ recognised and verifiable; '$2b$'+text is not a hash
  [PASS] 6 failures in 15 minutes lock the account for 15 minutes, then release  — after 5: 0s; after 6: 900s; 901 s later: 0s (username case/space-insensitive)
  [PASS] the lockout survives an app restart when the store is available  — fresh process still locks for 900s
  [PASS] a failing store degrades to the in-memory lockout, never to no lockout  — 7 store errors absorbed; account still locked
  [PASS] usernames are stored only as one-way tags; a successful login clears the count  — stored keys look like 81b637d8fcd2c6da
  [PASS] existing and non-existent usernames lock identically (no account discovery)
  [PASS] SQL audit catches the v30.5.15 open journal policy (negative control)  — 2 problems found: open policy and anonymous grant
  [PASS] no SQL file in the repo opens a table to the anonymous, signed-in or public role  — 3 files clean: model_readings_setup.sql, reading_outcomes_setup.sql, security_hardening.sql
  [PASS] hardening script closes subscribers, drops the open policy, creates the lockout table and rehashes plain-text passwords at bcrypt cost 12  — security_hardening.sql present
  [PASS] terminal login: named columns only, constant-time branches, durable lockout, key check, full sign-out, no stack traces  — 3 decoy bcrypt checks on the no-account and non-hash branches; no plain-text comparison or password rewrite
  [PASS] no credential committed to the repo; secrets.toml is not in the repo  — scanner catches a planted key (control); repo clean

=== 23. SCAN JOURNAL STATISTICS AND TERMINAL HYGIENE (V30.5.17) ===
  [PASS] effective sample size: 20 correlated months count as about 20, independent rows as about 400  — correlated n_eff 19.0 (deff 21.1); independent n_eff 400 (deff 1.00)
  [PASS] journal interval is the Wilson interval on the effective sample, wider than the row-count one  — 75.7–99.1% on n_eff 19.0 vs 92.4–96.7% on n 400: 5.4x wider
  [PASS] unmeasurable correlation counts clusters (one month = 1; all wins = months)  — one month of 10 -> 1; 2 months all wins -> 2; empty -> 0
  [PASS] reference put carries the dividend yield in strike, premium and model POP  — q 3%: K 93.49, premium 0.6239 (independent 0.6239), POP 84.50% (independent 84.50%); unusable q priced at 0
  [PASS] forward volatility uses Yang-Zhang like the entry reading, close-to-close only without OHLC  — Yang-Zhang 35.14% vs close-to-close 31.09% on the same 25 sessions
  [PASS] the daily scorer rotates, reaching every due name within ceil(100/40) = 3 days  — 100/100 names reached over 3 days, 40 per day
  [PASS] terminal: remaining raw-HTML text escaped, 72-byte password cut, journal fallback  — tooltip escaped, provenance escaped, structure box escaped, 72-byte cut in hash and check, journal write fallback, effective n shown
  [PASS] a password longer than 72 bytes still logs in once cut to 72 (bcrypt 4 and 5 alike)  — 86-byte password, bcrypt 4.3.0

=== 24. AUDIT FOLLOW-UP (V30.5.18) ===
  [PASS] v30.5.18 engine interfaces present  — tolerance constants, lattice_check, Polygon budget, log masking
  [PASS] lattice tolerance defined once: 0.1% or $0.005  — rel 0.1%, abs $0.005
  [PASS] lattice_check: cheap wing within on dollars, real miss outside, NaN never passes  — 0.179%/$0.0002 in; 0.200%/$0.0200 out
  [PASS] 800-step lattice inside the tolerance on the independent 298-contract grid  — 298/298 within; worst relative 0.177% at ('call', 130, 30, 0.45, 0.00022)
  [PASS] negative control: a relative-only 0.1% reading does fail on cheap wings (why the published claim must state the dollar floor)  — 6/298 exceed 0.1% relative, all within $0.005
  [PASS] engine no longer claims a relative-only 640/640 result  — [FIX-31] note states 0.1% OR half a cent
  [PASS] Polygon: 5 requests per minute sent, the rest refused locally without waiting  — 8 asked, 5 sent, state 'paced', 0 ms total
  [PASS] Polygon: the budget refills after 60 seconds  — one request sent at t+60.5 s
  [PASS] Polygon: after an HTTP 429 nothing is sent for 60 seconds, then requests resume  — t+30 s held back, t+61 s sent
  [PASS] a paced request is shown orange (fell through), never as served  — 🟠 paced (5/min budget in use)
  [PASS] server log masks keys in web addresses and every configured credential  — 4 messages, 4 masks, leaks: none
  [PASS] log masking leaves ordinary messages untouched
  [PASS] Monte Carlo reports when a long tenor trims the chart's path matrix  — 1-year: 39,682 of 50,000; 30-day: 50,000 of 50,000
  [PASS] terminal: tolerance-aware lattice line, paced state, trim notice, no plain-text login  — caption uses lattice_check, dollar gap printed, paced shown as fell-through, trim caption, no plain-text login path

=== 25. RE-AUDIT FOLLOW-UP (V30.5.19) ===
  [PASS] log masking installed process-wide, exactly once  — record factory wrapped; base is the original factory
  [PASS] terminal logger, library logger and traceback text all masked (re-audit N-02)  — 3 probes, 4 masks, leaks: none
  [PASS] process-wide masking leaves ordinary log lines untouched
  [PASS] engine and terminal headers open on current releases  — engine header 147 lines, terminal header 53 lines
  [PASS] CHANGELOG.md holds the moved history (oldest engine and terminal tags present)  — [FIX-09], [FEAT-13], [DOC-23], [UX-24], v30.5.14
  [PASS] terminal imports the engine before creating its logger (masking active from line one)
  [PASS] site publisher present beside the suite  — /home/runner/work/HQTA-V21/HQTA-V21/publish_audit.py
  [PASS] publisher has the [FIX-156] run-date stamping and captured-date guard  — present
  [PASS] publisher: captured-data dates kept, run date written only to its stamps  — captures stay 2026-09-23 when the old run date was 2026-09-23; version, count and stamps updated
  [PASS] negative control: the old blanket date replace is refused and nothing is written

=== 26. SIGNAL LAYER, HONEST SCORE, AV QUOTA, HERMETIC RUN (V30.5.20) ===
  [PASS] v30.5.20 signal interfaces present  — kalman_gain, kalman_band_factor, trend_state, calculate_score_detail
  [PASS] Kalman level tracks price, not the expanding mean (negative control on v30.5.19's defect)  — max gap to expanding mean 28.5% of price; final level within 6.9% of the close
  [PASS] band factor (1-k)/sqrt(k(2-k)) matches the spread of price around the level  — theory 3.733, simulated 3.646 (k = 0.0341, half-life 20)
  [PASS] dip in an uptrend reaches the bullish branch (score >= 75, 'Strong bullish' possible)  — score 100, setup dip_in_uptrend, trend t +3.00
  [PASS] rally in a downtrend reaches the bearish branch (score <= 20)  — score 0, setup rally_in_downtrend
  [PASS] +40%/yr names read Bullish in the majority (v30.5.19: 96.5% Bearish)  — Bullish 88, Neutral 21, Bearish 11 of 120
  [PASS] -30%/yr names read Bearish in the majority  — Bullish 13, Neutral 33, Bearish 74 of 120
  [PASS] zero-drift names split symmetrically between Bullish and Bearish  — Bullish 38, Neutral 51, Bearish 31 of 120
  [PASS] a score that cannot be computed is None with a reason, never a stand-in 50  — 40 bars -> insufficient_history; bad data -> error
  [PASS] structure bias treats a missing score as neutral, not as 50-and-scored
  [PASS] scanner row without a score: '—', ranked last, no setup, not journaled  — Alpha Score '—', no Setup column, journal none
  [PASS] Alpha Vantage: after a daily-limit notice nothing is sent until 00:00 ET, then requests resume  — first reply throttled; 0 sent while resting; state 'resting'; resumed after reset: True
  [PASS] Alpha Vantage: a per-minute notice rests 60 seconds, not a day  — 2 requests sent across t, t+0, t+61 s
  [PASS] a resting source is shown orange (fell through), never as served  — 🟠 resting (provider quota used)
  [PASS] Alpha Vantage documentation states the 25/day free quota and its real rung
  [PASS] underlying backtest calls the causal GARCH only (in-sample apply_garch is live-reading only)
  [PASS] terminal: trend reading, honest missing score, resting state  — imports the engine it is verified with, Deep Dive uses calculate_score_detail, missing score shown as a dash, trend reading shown

=== 27. SIGNAL LAYER AND MAINTAINABILITY (V30.5.21) ===
  [PASS] blend normaliser is the sd of two overlapping t-statistics  — sqrt((2 + 2*sqrt(63/126))/4) = 0.9239
  [PASS] trend statistic has mean 0 and unit variance when there is no drift  — mean +0.094, sd 1.055 over 300 paths
  [PASS] +40%/yr names: more read Bullish than under v30.5.20, wrong-direction calls under 15%  — Bullish 115/160 (v30.5.20 method 102/160); Bearish 12/160
  [PASS] trend reversal (+40%/yr to -40%/yr) is recognised sooner than under v30.5.20  — median 58 sessions vs 90
  [PASS] with fewer than 132 bars the trend uses 63 sessions and says so  — basis 63, label Bullish
  [PASS] band setups fire in labelled trends (dip -> score >= 75, rally -> <= 20)  — dip: 100, t +6.03; rally: 0, t -1.31
  [PASS] pullbacks in trends trigger setups, and a setup never sits beside a disagreeing trend label (audit N-02)  — 32 of 60 trend-plus-pullback paths set up, 0 disagreements
  [PASS] scanner Trend cell shows the statistic  — Bullish (t +1.42)
  [PASS] setup rule stands alone and behaves as documented, including no-score and Risk-Off
  [PASS] scanner row function kept small (cyclomatic complexity under 25; was 38)  — complexity 10
  [PASS] scan metadata counts names without an Alpha Score, and none of them is journaled  — scored 2, unscored 2, journaled 0
  [PASS] removed dead code stays removed and the terminal calls none of it  — 9 functions gone
  [PASS] headers carry the current release and a file map; v30.5.15-v30.5.20 history is in CHANGELOG.md  — engine header 147 lines (was 242), terminal 53 (was 142); moved tags present: 7/7

=== 28. COMPLEXITY BUDGET (V30.5.22) ===
  [PASS] no engine function above complexity 20 (no radon D, E or F)  — 468 functions; highest: AlphaVantageClient.get_history 20, QuantMath.black_scholes 19, SchwabClient.complete_login 17
  [PASS] scanner, signal, journal and chain path: every function at complexity 10 or less (radon A/B)  — 92 functions checked
  [PASS] every function between 11 and 20 is on the reviewed list with its reason  — 14 such functions, all reviewed
  [PASS] equal scores rank the same way every run (ticker breaks ties)  — AAA > MMM > ZZZ
  [PASS] terminal names its build from the engine, never from a typed-in version  — title, product panel and sidebar read BUILD_INFO
  [PASS] engine average complexity at radon grade A (5 or less)  — average 4.59; 454 of 468 at 10 or less

=== 29. ALPHA SCORE SCALE (V30.5.23) ===
  [PASS] trend points keep rising with trend strength (v30.5.22 capped at t = 0.75)  — t0.25:+5.2 t0.5:+10.0 t0.75:+14.0 t1:+17.0 t1.5:+20.9 t2:+22.7 t2.5:+23.4 t3:+23.8 t4:+24.0
  [PASS] score 60 still corresponds to Trend = Bullish at t = +0.5 (and 40 to Bearish)  — points at t = +/-0.5: +10.00 / -10.00
  [PASS] without a band setup a score stays within 26-74; 75+ is reserved for setups  — largest trend points 24.000
  [PASS] in a broad rally, scores spread instead of piling up at one value  — 40 names, 23 distinct scores, most common value held by 7
  [PASS] scanner colours Alpha Score, Trend and VRP by value, still escapes every cell  — green/red/grey spans on 3 test rows; HTML in a ticker stays text
  [PASS] equal displayed scores rank by the unrounded reading, not alphabetically  — BBB > CCC > AAA

=== 30. EARNINGS-AWARE VRP, NOISE BAND, NO-CHAIN ROWS (V30.5.24) ===
  [PASS] VRP noise band is one standard error of 21-day realised vol; rich/cheap only outside it  — RV 30% -> ±4.63 pts; +4.6 flat, +4.7 rich, -4.7 cheap, event wins
  [PASS] no structure is fitted on a VRP inside the noise band or on an IV spanning a report  — +2.0 in band -> No structure fit; no band -> Short put vertical; +6.0 -> Short put vertical; Deep Dive with report in expiry -> No structure fit
  [PASS] realised leg without the report reaction matches a hand Yang-Zhang over the sessions kept  — plain 43.37% (hand 43.37%); ex-report 18.8520% vs hand 18.8520%; the 12% gap alone added 24.5 pts
  [PASS] a report's day and the next session are removed only when they fall in the 21-session window  — report 2026-10-19 -> ['2026-10-19', '2026-10-20']; 40 sessions back -> none
  [PASS] report timing: after-close on expiry day is outside; before-open is inside; a morning report today is past  — AMC on expiry -> out; BMO on expiry -> in, last usable expiry the day before; crypto -> n/a; no calendar -> unchecked, never assumed
  [PASS] pre-report expiry: the nearest to 30 days that settles before the report, at 7 days or more  — unbounded -> 31d; cap at 14d -> 14d; cap at 5d with 7-day floor -> none
  [PASS] VIX-proxy scaling for crypto uses 365 sessions at sqrt(365) (was 252 at sqrt(252))  — ratio 4.189 vs old 3.375 (+24.1%)
  [PASS] earnings calendar: CSV read; quota, plan and key notices (HTTP 200) are refusals, not data  — 2 names parsed; daily-limit notice -> throttled and resting; premium -> not_entitled; error -> bad_key; header-only CSV -> no calendar
  [PASS] report inside the 30-day expiry: VRP uses the pre-report IV; the term structure recovers the priced jump  — 31d IV 38.43% spans the report; row uses 14d IV 30.1% (base 30.00%); implied move 7.0% vs the 7.0% built into the chain
  [PASS] Schwab's last report date takes the reaction out of the realised leg  — RV 21d 18.9% = hand ex-report 18.85% (with it: 43.37%)
  [PASS] on every row with a VRP, the IV column minus the RV 21d column equals the VRP shown  — EVT: 30.1% − 18.9% = +11.2 pts; CLN: 30.0% − 30.2% = -0.2 pts; SOON: 38.4% − 34.8% = +3.6 pts (incl. earnings)
  [PASS] report too close for a pre-report expiry: shown '(incl. earnings)', grey, not ranked, no structure  — SOON +3.6 pts (incl. earnings); rank order EVT > CLN > BTC-USD > SOON
  [PASS] no option chain: no VRP reading (the VIX proxy prices the structure only), ranked after every reading  — BTC-USD: n/a (no chain), basis 'No permitted usable option-chain source (pricing only)'
  [PASS] journal unchanged: it logs the full-expiry IV and plain 21-day RV (scored against the same expiry)  — EVT journal IV 38.4422% (full 31d), RV 43.37%
  [PASS] request cost: one chain per name (pre-report IV reuses it), one earnings-date batch, one calendar per 12 h  — two scans of 3 optionable names -> 3 chain, 1 fundamentals, 1 calendar requests; in-window 2, re-read 1, event 1, RV adjusted 1

=== 31. SMILE PRICING, DELTA STRIKES, SKEW, OPTION LIQUIDITY, VRP RANK (V30.5.25) ===
  [PASS] chain smile: each strike's IV solved from its own quote matches the true smile  — 34 of 41 strikes pinned (range 150-232.5), median error 0.022, worst 0.283 vol pts at penny-wide quotes; sub-$0.05 bids left out
  [PASS] smile between strikes is linear; beyond the chain it is held flat and flagged; no chain -> flat  — 196.25 -> 30.96% (linear 30.95%); 120 -> edge value, outside
  [PASS] 20-delta strikes found on the smile carry 20 delta at their own implied vol  — put 185.63 (Δ 0.2000), call 214.77 (Δ 0.2000)
  [PASS] Put Skew 25Δ = 25-delta put IV minus ATM IV; blank when that strike is outside the chain  — +2.86 pts vs hand +2.83 (strike 188.99); outside the chain -> blank
  [PASS] scanner strikes: 20-delta shorts snapped to listed strikes, wings about 5% of spot beyond  — long put 175 / short put 185 / short call 215 / long call 225
  [PASS] structure priced leg by leg at each strike's IV: model credit equals a hand Black-Scholes sum  — −P185.00 / +P175.00: credit $1.24 vs hand $1.240; flat ATM vol would say $1.182
  [PASS] option market wider than 10% of mid or under 100 contracts at the money: 'thin', no structure  — normal spread 0.3% · OI 1.0k; wide thin · spread 43.1% · OI 1.0k; shallow thin · spread 0.3% · OI 40
  [PASS] Deep Dive structure (no chain) unchanged: 300 random inputs reproduce the frozen fingerprint  — sha256 b7dc39de2c9f37d2… vs frozen b7dc39de2c9f37d2… (v30.5.25, 2026-09-30)
  [PASS] ranking by VRP ÷ noise: +5 pts at 25% vol outranks +8 pts at 60% vol; no reading last  — by points HIVOL > LOVOL > NOCHAIN; by significance LOVOL > HIVOL > NOCHAIN (z 1.30 vs 0.86)
  [PASS] a +30 pt '(incl. earnings)' VRP never reaches the structure fit or the ranking (v30.5.24 gap closed)  — state event, structure 'No structure fit'
  [PASS] scanner row carries skew, option market and VRP ÷ noise from ONE chain request  — 1 chain request; skew +2.9 pts; spread 0.3% · OI 1.0k; VRP +13.6 pts ÷ ±2.53 = +5.4; Short call vertical −C215.00 / +C225.00

=== 32. SCHWAB SIGN-IN LINK LIFETIME (TERMINAL V30.5.25.1) ===
  [PASS] Schwab sign-in: a link is honoured for 15 minutes from issue, across the submit refresh  — finished at +7 min -> accepted (v30.5.25 logic: rejected); +16 min -> expired; after a reload -> refused with the reason; forged state -> refused

=== 33. FORWARD-IMPLIED SMILE, NOISE-BAND SESSIONS, PROB_OTM KEYS, SKIP STATUS (V30.5.26) ===
  [PASS] forward read from the chain by put-call parity: implied carry equals the true dividend / borrow  — true 0% / 2% / 25% -> implied -0.01% / 1.99% / 24.98% (3 two-sided pairs each)
  [PASS] true flat 30% smile solves flat at any carry; no put/call seam at the forward  — worst error 0% 0.24 / 2% 0.18 / 25% 0.15 vol pts (penny quotes); seam at 2% 0.00 pt (v30.5.25 carry-0 solve: 1.38 pt)
  [PASS] Put Skew 25Δ of a flat smile reads 0 at any carry (v30.5.25 read dividends and borrow as skew)  — now -0.00 / +0.01 / -0.01 pts at 0% / 2% / 25%; v30.5.25 method: +0.47 at 2%, +5.94 at 25% borrow
  [PASS] a genuine put skew reads the same on a 2% dividend payer as without the dividend  — no dividend +2.86 pts, 2% dividend +2.91 pts
  [PASS] structure legs priced at the chain's implied carry reprice to the market mids  — −P185.00 / +P175.00: model credit $1.27 vs mid credit $1.270; short leg delta 0.196 (20Δ target, snapped to a listed strike)
  [PASS] no two-sided call/put pair, or a forward >15% from spot -> carry 0, and the snapshot says so  — carry 0 assumed (no two-sided call/put pair); carry 0 assumed (parity forward implausible)
  [PASS] noise band uses the sessions the realised leg was measured on (21 normally; fewer on short history)  — 2 report sessions excluded on 300 bars -> 21 sessions (band unchanged); 16-bar history -> 15 sessions, band 5.64 = RV/√30
  [PASS] lattice and path engine report P(OTM) as prob_otm, never as 'pop'  — P(OTM) closed form 0.7229; paths 0.7213; lattice 0.7240

=== 34. MARKET-IMPLIED ODDS FROM THE OPTION CHAIN (V30.5.31) ===
  [PASS] flat 30% smile on a 2% dividend payer: market-implied odds equal the lognormal  — 11 levels 175-225, worst 0.22 pts (penny quotes)
  [PASS] skewed smile: market-implied odds equal the closed-form skew-adjusted probability  — 11 levels, worst 0.31 pts
  [PASS] negative control: the flat lognormal understates the 2σ downside the chain prices  — P(below 167.6): chain 4.62% vs flat lognormal 2.27%
  [PASS] with mids anywhere inside a 10-cent quote, the shown range holds the true probability  — 100/100 readings
  [PASS] seller POP = P(finish above strike - premium carried); long POP is its complement  — short POP 76.69% (lognormal 76.89%) > P(OTM) 71.15%; chain value $2.84
  [PASS] ±1σ band breach odds read 15.9% each side on a flat smile  — below 16.06%, above 16.00%
  [PASS] no reading beyond quoted strikes, without measured carry, on wide quotes, or across an arbitrage break  — beyond the quoted strikes; chain carry not measurable; bid-ask too wide to read a probability; chain prices fail the no-arbitrage check here
  [PASS] terminal: market-implied panel wired, refuses a different expiry, labelled not a forecast

=== 35. FORWARD VOLATILITY FORECAST, READINGS LOG, REFERENCE BANDS (V30.5.32) ===
  [PASS] variance proxy = overnight log return² + Rogers-Satchell; each session uses only itself and earlier sessions  — hand bar 7.729e-04; prefix-stable at 5 cut points; zero-range day floored at 3.04e-05 (5% of the prior 22-session median)
  [PASS] constant true volatility: the proxy averages to the true daily variance and the forecast recovers the vol  — proxy mean / true variance 0.957 (2,000-step days); forecast 25.25% vs true 25.65% over 23 sessions
  [PASS] forecast equals a textbook log-HAR built from the same bars; every training target ends on or before the last bar  — engine/textbook at 3 cut dates: 27.374/27.374, 30.836/30.836, 42.531/42.531; training rows = sessions − 21 − h
  [PASS] recovers the daily, weekly and monthly coefficients of a simulated log-HAR process  — fitted [0.362, 0.2728, 0.2544] vs true [0.35, 0.3, 0.27] (5,900 sessions)
  [PASS] longer horizons forecast mean variance closer to the long-run level; sessions counted to expiry  — after a spike: 156.3 > 126.7 > 105.5 > 86.6 (long-run 55.9); Fri 9 Oct to Fri 20 Nov = 30 sessions (42 calendar days for crypto)
  [PASS] no forecast on short history, stale history, beyond 126 sessions, without OHLC or for a past expiry  — too little history (77 of 120 training rows); expiry outside the forecast range (1-126 sessions); open/high/low not available; price history ends 2026-10-16, too old to forecast from; no expiry -> 30 calendar days
  [PASS] forecast is shadow-only: ranking, VRP, colours, structures and journal identical with the forecast set to 250%  — order EVT > CLN > BTC-USD > SOON both runs; EVT forecast over its 14-day pre-report expiry 17.9% (14d); CLN 30.0% − 35.4% (31d) = -5.4 pts; BTC-USD (no chain) 59.1% (30d)
  [PASS] every forecast in a scan becomes one model-readings record whose fields are columns of the SQL table  — 4 records; fields ['computed_at', 'detail', 'expiry', 'horizon_sessions', 'model', 'model_version', 'session_date', 'source', 'ticker', 'unit', 'value']
  [PASS] readings are appended, never updated: duplicate-skipping insert, no update/delete path, blocked in the database  — insert of 4 records -> upsert ignore_duplicates; database down -> logged, page unaffected; SQL: RLS on, no public grants, UPDATE/DELETE revoked and trigger-blocked
  [PASS] market-implied panel: grey lognormal on the page's spot, expiry and yield reads 15.9% beyond each ±1σ band  — below 15.86%, above 15.87%; negative control on a chain basis 0.4% off spot, no carry: 14.43% / 17.39%
  [PASS] regime label describes the measurement (no forecast wording); setups identical under old and new label  — VIX9D 19.5 vs VIX 16.0 -> 'Front-End Stress (VIX9D above VIX)'; load banner reads ENGINE_VERSION
  [PASS] forecast row: says the forecast carries an average of past report moves; a report in expiry marks both spreads  — caption corrected in Deep Dive and scanner; 'incl. earnings' on both spreads when a report falls inside

=== 36. ENGINE SPLIT INTO PART FILES, NEWSAPI BADGE, AUDIT TITLE (V30.5.33.2) ===
  [PASS] engine loads from its own versioned folder; every part listed, none unlisted, each small enough to read whole  — 17 parts in engine_v30_5_37/, largest 43.6 KB, loader 12.1 KB; numbered names cannot be imported on their own
  [PASS] parts 01-13 are v30.5.32's code character for character, apart from five tagged lines  — 13 parts, 6,656 lines; with the 5 listed lines put back, SHA-256 2042a9a106dca5e8… = v30.5.32's 2042a9a106dca5e8… (frozen 2026-10-29)
  [PASS] negative control: the identity test sees the changed lines and the order of the parts  — without putting the 5 lines back -> differs; with parts 3 and 4 swapped -> differs
  [PASS] one namespace: a patch on the engine reaches code in every part; classes belong to the engine module  — market_today patched -> 7 days counted in OptionsDataEngine; error tracebacks name the real file (13_scanner.py); no part imports the engine module
  [PASS] the fingerprint covers every engine file: a change to any part changes it  — one line added to the last part changes the fingerprint; negative control: the v30.5.32 method (hash of the loaded file alone) would not have moved
  [PASS] NewsAPI: a rejected request turns its badge orange, with the HTTP status in the tooltip (audit N-06)  — 200 -> ok; 429 -> throttled; 401 -> failed; negative control: the v30.5.32 key leaves the badge 'ok' after a 429
  [PASS] every source outcome the engine records by name is one a sidebar badge reads  — 13 named tags recorded, all read (Schwab sign-in is internal by design); negative control: v30.5.32's 'newsapi' would be caught
  [PASS] audit page title is written from the verified version on every publish; four-part versions read  — stale 'v30.5.9' title -> 'Verification Report — VRP Quant v30.5.33.2 · build abcdef0'; negative control: the old swap leaves 'v30.5.9'; v30.5.33 is never matched inside v30.5.33.2; chart labels naming older builds kept

=== 37. PHYSICAL-MEASURE TAIL RISK: FILTERED HISTORICAL SIMULATION, EVT, ES, KUPIEC (V30.5.34) ===
  [PASS] the engine exposes PhysicalTail and fetch_long_history  — part 14_tailrisk.py loaded
  [PASS] GARCH filter: known parameters recovered, residuals have mean 0 and variance 1, today's variance by hand  — alpha 0.072 (0.08), beta 0.909 (0.90), z mean +0.006, var 1.001; next-session variance = omega + alpha*e^2 + beta*s2 exactly
  [PASS] EVT: GPD fit recovers known tail shapes; quantiles equal scipy's; no-finite-mean and thin samples refused  — xi 0.25 -> 0.266, xi 0 -> +0.010; xi 1.3 refused (Expected Shortfall undefined)
  [PASS] Hill index: Pareto tail index recovered; the 'normal would read' figure matches 2 million normal draws  — Pareto 1/3 -> 0.332; normal reference 0.290 vs simulated 0.291
  [PASS] EVT sampler: 10% in each fitted tail, body draws are recorded residuals, tails reach past the worst day  — below worst recorded residual: 0.004% of draws; negative control: without the GPD tails, never
  [PASS] simulation: with constant vol and normal shocks P(S_T < X) matches the lognormal; mean = spot x e^(-qT)  — 5 strikes, worst gap 1.38 standard errors; zero expected total return exactly
  [PASS] simulation: variance to expiry follows the GARCH term structure from today's volatility  — simulated 0.01687 vs closed form 0.01700 (from 3.0% today towards 1.4% long-run)
  [PASS] simulation: same inputs, same paths; memory at 50,000 paths does not grow with the horizon  — peak 2.4 MB at 5 sessions, 2.4 MB at 126; seed is a hash of the inputs
  [PASS] P&L: seller and buyer POP net of premium, ES = mean of the worst 5%, max-loss regions of every structure  — short put on [80,95,99,100,120] @ 2: POP 60% (buyer 40%), ES 1,800/contract; vertical below 95; condor below 95 and above 115; uncovered call has no maximum
  [PASS] structure cash is the credit the Deep Dive structure panel shows  — Short put vertical: model credit $0.61 vs 0.6069
  [PASS] Kupiec proportion-of-failures: LR statistic and p-value match the textbook formula  — 10/1000 -> LR 0, p 1; 25/1000 -> LR 16.043, p 0.0001; 0 breaches handled
  [PASS] coverage walk-forward is causal: changing returns after a date leaves every earlier loss line unchanged  — returns from session 2000 on multiplied by 4: lines 0-1999 identical; negative control: a quantile fitted on all residuals (look-ahead) moves
  [PASS] coverage: the EVT loss line passes on fat-tailed GARCH data; a normal tail on the same data fails  — EVT 17 of 1600 (expected 16, p 0.80); normal 30 (p 0.0017)
  [PASS] long history: Schwab asked for ten years when signed in, else yfinance; Alpha Vantage and Polygon never  — requests made: [('yfinance', '10y'), ('schwab', '10y', 3653)]
  [PASS] long history refused when shorter than 5 years, unverified, stale or missing -- each with its reason  — 5.4 years fresh: used; 4.6 years, failed split check, 30 days stale, empty: refused
  [PASS] reading: horizon counted as the forecast row counts it, reproducible, structure included; out-of-range refused  — 22 sessions, physical POP 91.6%, P(max loss) 1.82%; 400 days and a past expiry refused
  [PASS] log row: plain JSON, every field a model_readings column, physical POP with what it was compared to  — value 0.9157, unit 'probability (physical POP, net of premium)', 23 detail fields
  [PASS] shadow only: no pricing, scanner or structure code reads the physical model; the terminal shows and logs it  — 8 terminal lines use it, all in its panel; engine parts 01-13 never mention it

=== 38. FORWARD JOURNAL CALIBRATION: SHADOW READINGS SCORED AGAINST OUTCOMES (V30.5.35) ===
  [PASS] part 14 is v30.5.34's code character for character apart from the [FEAT-292] line  — with that line taken out, SHA-256 f20270428c8c0be9… = v30.5.34's (frozen 2026-10-30); negative control: with it left in -> differs
  [PASS] physical reading row now logs its breakeven; POP, model and version unchanged  — breakeven 266.8659 = K − premium·e^(rT)
  [PASS] win = P&L above zero at expiry for the side held: short put above breakeven, long put below it  — short at 93.01 wins, at 93.00 does not; long at 92.99 wins, at 93.00 does not (the simulation counts P&L > 0)
  [PASS] v30.5.34 rows (no breakeven logged): scored only where every rate in 0-10% gives the same answer  — bracket 92.9846–93.0000; inside -> unscoreable, above -> win, below -> loss
  [PASS] maturity and data rules: not before the expiry has passed; last close on or before it; refusals named  — expiry day itself -> waits; history short of it -> waits; Saturday expiry -> Friday's close; no session bar, split after the reading, spot 50% off the close, unknown side -> unscoreable; split on the reading's own session (before it) -> scored
  [PASS] realised vol: close to close, annualised at 252, dividend added back on its ex-date  — 31.25% = textbook; with a $1.50 dividend 31.25%; negative control ignoring it 30.64%; nine missing sessions -> unscoreable
  [PASS] outcome prices: requested without dividend adjustment, with actions; the badge records the real outcome  — auto_adjust=False, actions=True; success -> 'ok', a failed request -> 'failed', switched off -> not asked
  [PASS] scoring pass: one download per name from 10 days before its first reading; a failed name waits; rows fit reading_outcomes  — 4 names, 5 readings -> 4 downloads, 4 outcomes (ZZX failed, not stored); budget 2 -> 2 downloads; 16 table columns
  [PASS] first reading of the day per spec and name; other specs kept apart; unusable rows dropped  — of two same-day FHS-1 readings the 14:00 one is kept; FHS-2 separate; other model, NaN, no id -> dropped
  [PASS] independent subset: a name's next reading counts only once the last kept one's expiry has passed  — 5 readings -> 3 kept, no shared future days; negative control: the full set overlaps
  [PASS] cluster-robust mean (CR1, t on G−1), design effect, delta-method ratio and QLIKE match hand calculation  — mean 3.3333, se 1.01379, t(2) 4.303; ratio 1.7500 ± 0.3014; QLIKE 0 at RV = F; one cluster -> no interval
  [PASS] physical POP verdicts: calibrated data meets the pass mark; POP 10 points too high or too low is caught  — calibrated: mean error -1.4 pts (480 independent, 12 months); +10 pts -> READS HIGH; −10 -> READS LOW; 96 readings or 5 months -> ACCUMULATING; another method's outcomes never mix in
  [PASS] P3 detects a POP whose Brier score is worse than the lognormal's; verdict order is the pre-registered one  — noisy physical POP: Brier difference +0.0607, one-sided p 0.0000; 5 bands on calibrated data, all inside their intervals
  [PASS] price of risk: physical minus market-implied POP, its monthly stability and the gap realised  — built at +6.0 pts -> +6.0 pts, same sign in every month; realised gap +4.6 pts
  [PASS] forecast verdicts: an accurate unbiased forecast meets the pass mark; a 16% level bias or a noisier forecast is caught  — level 0.955 (needs 0.90–1.10), QLIKE vs RV21 -2.086 (p 3.6e-07); ×1.157 variance -> level 1.105, FIX LEVEL; noisier than RV21 -> RETAIN INCUMBENT
  [PASS] maturity schedule counts readings by the week they mature; past-due readings are counted as waiting to score  — week of 2 Mar: 2 readings (the scored one left out); next maturity 6 Mar
  [PASS] measurement only: no engine or terminal code but the calibration section reads it; outcomes append-only  — 13 terminal lines, all in its section; outcomes read and inserted (duplicates skipped), never updated; table: server key only, update/delete refused
  [PASS] the engine runs the pass marks written in CALIBRATION_PREREG_v30_5_35.md  — method CAL-1; gate 100 readings / 6 months; level 0.90–1.10; bands and n_eff 20; legacy bracket 0–10%; basis 15%

=== 39. REGIME STABILITY: THE LIVE RULE OVER TEN YEARS, SPELLS, BOUNDARIES (V30.5.36) ===
  [PASS] the engine exposes RegimeHistory and fetch_level_history with the live rule's thresholds  — MA 50/200, stress VIX9D > VIX + 2, Risk-On VIX < 20, Risk-Off VIX > 25
  [PASS] every past session is labelled by the live rule itself: same label as RegimeEngine on every scenario  — 255/255 agree, all four labels reached {'Neutral': 139, 'Risk-Off': 33, 'Risk-On': 30, 'Front-End Stress (VIX9D above VIX)': 53}; equality cases included (VIX = 20, VIX = 25, spread = 2, no VIX9D); negative control: '>=' on the lines differs on 9
  [PASS] causal: cutting the history at any session leaves every earlier label and average unchanged  — 60 cut points, labels and 50/200-day averages identical; negative control: a centred 50-day average changes the SPY-above-average test on 676 sessions
  [PASS] spells worked by hand: first and current spells left out of lengths, exits counted, overlap reported  — A3 B2 C1 A4 B1 -> completed B2, C1, A4 (A3 and B1 censored); 4 switches in 10 steps; of spells reaching 8 sessions, 3 of 5 lasted 21 more; 4 such spells -> no share; B -> C
  [PASS] negative control: counting the censored spells would change the lengths  — Neutral would read [3, 4] with the first spell; dropping the last session ends on the A4 spell
  [PASS] distance to each threshold, in buffers: the binding condition named; 'recent', 'near a boundary', 'established'  — Risk-On at VIX 19.4 -> near (0.6 buffers); at VIX 15 the binding one is SPY 3% over its 50-day (3.0); Neutral at VIX 20.5 -> 0.5 from Risk-On; stress spread 2.3 -> 0.6; at exactly 20 / 2 the live rule's side
  [PASS] reading bookkeeping: sessions after the 200-day warm-up, shares sum to 1, every session in exactly one spell  — 2,401 sessions from 2017-08-18; 18.9 changes a year; same inputs, same reading; history ten sessions old -> 9 full sessions behind
  [PASS] too little history or a missing series: a plain reason, no figures  — 401 classified sessions -> refused (504 needed); no SPY -> refused
  [PASS] VIX history kept whole: the +116% session of 5 Feb 2018 is served, Schwab first when signed in, cached  — 10 years asked; badge 'ok'; negative control: the price waterfall's split screen says '+115.6% single-day move on 2018-02-05 — …'
  [PASS] index level at or below zero: refused and the badge says suspect; a failed request says failed, type only  — zero close -> suspect; ConnectionError -> ('failed', 'ConnectionError')
  [PASS] reading: three requests (SPY, VIX, VIX9D), sources named; a failure returns a reason, never raises  — requests ['SPY', '^VIX', '^VIX9D']; failure -> 'regime history could not be computed'
  [PASS] shadow only: no pricing, scanner or structure code reads the regime history; the live label is unchanged  — 5 terminal lines use it, all in its panel; the scanner still calls RegimeEngine.detect_regime()
  [PASS] descriptive wording only (no forecast words); history fetched only when its button is pressed  — panel text scanned: forbidden words none; one fetch, behind 'Load ten years of regime history'
  [PASS] Deep Dive: both logged readings say whether they reached the model-readings log (no database detail)  — failed -> 'could not be added'; sent -> 'Added'; already sent -> 'Already in'; nothing to log -> silent

=== 40. CORRELATION STRUCTURE: RANDOM-MATRIX READING OF THE APEX-100 (V30.5.37) ===
  [PASS] the engine exposes CorrelationMonitor (RMT-1) on the APEX-100 without crypto pairs  — 98 names of 100 (crypto pairs out); windows 250 / 63, every 5 sessions, 30 names minimum; one request per name
  [PASS] parts 15 and 16 are v30.5.36's code character for character  — SHA-256 of each body = v30.5.36's (frozen 2026-11-01); negative control: one added space -> differs
  [PASS] correlation matrix equals numpy's corrcoef; constant series dropped; eigenvalues sum to N  — max difference 6.1e-16; trace 39.000000; smallest eigenvalue 2.38e-03 (positive semi-definite)
  [PASS] Marchenko–Pastur: density integrates to 1 with mean σ², ceiling σ²(1+√q)²; an iid spectrum follows it  — 3 (q, σ²) pairs; 600 iid series × 1,500 sessions: KS distance 0.003, largest eigenvalue 2.624 vs ceiling 2.665 and finite-size threshold 2.698
  [PASS] pure noise (98 names, 250 sessions): a factor shows above the threshold at most 2% of the time  — 2 of 300 with the Tracy–Widom 99% allowance; negative control: the plain Marchenko–Pastur ceiling shows one 21 times
  [PASS] planted 1, 3 and 5 factors are counted exactly (debiased noise variance, Baik–Silverstein)  — exact in 40/40, 40/40, 40/40; negative control: σ² = 1 − Σλ/N without debiasing over-counts 5 factors in 7/40
  [PASS] spike inversion recovers the underlying factor size; equicorrelation gives λ₁/N = ρ + (1−ρ)/N  — l = 1.5, 3, 10, 40 back from their sample eigenvalues (max error 2.2e-16); ρ = 0.3, 98 names: market mode 0.304 vs 0.307, average correlation 0.296
  [PASS] independent positions n²/(s′Cs): n/(1+(n−1)ρ) when all correlated ρ; signs enter; offsetting -> none  — 10 names at ρ = 0.3 -> 2.70; five of them short-delta -> s′Cs = 10 + 0.3·(0 − 10) = 7 -> 14.29; 7 uncorrelated -> 7; a name against itself -> offsetting
  [PASS] returns panel: a stray weekend bar dropped; a missing close carried at most 2 sessions, from past closes only  — Saturday close (one name of four) not a session; N1's return after a 2-session gap spans it; N2's third missing session is empty; N3 empty before its first close and from the third session after its last; a zero close treated as missing; time zones dropped
  [PASS] a window holds only names with every return in it; gaps and constant names are named, not filled  — B (hole inside the window) and E (constant) left out; matrix = corrcoef of the last 63 rows; too early -> none
  [PASS] causal: a session's panel row and reading are the same whether or not later closes exist  — 30 cut points on 36 names with data holes: closes cut at the session rebuild the same returns and the same 63-session reading as the full history; negative control: a centred 63-session window reads 0.332 against 0.341
  [PASS] full reading: names left out and why, record every 5 sessions to the last session, today against it  — 97 of 99 names; 508 quarter readings from 2017-02-10; highest 65% ending 2022-11-11 (planted stress quarter); one factor found in a one-factor market
  [PASS] same inputs, same reading; bounded time and memory at the full universe; an old panel is flagged  — 98 names × 2600 sessions in 0.8 s, peak 13 MB; panel 8 sessions old -> 7 full sessions behind; 20 names -> refused
  [PASS] fetch: one long-history request per name, never Alpha Vantage, Polygon or the 2-year waterfall; never raises  — failed / unverified / short / empty named and left out; progress after each name; full load = 98 requests; every fetch failing -> 'too few names with usable history (0, 30 needed)'
  [PASS] chosen names: signs parsed, names outside the panel fetched once, positions counted, most correlated quarter  — 4 names, 63 sessions -> 2.35 independent positions; most correlated quarter with all of them ending 2026-06-05 (2.90); crypto and non-tickers refused; one name -> refused
  [PASS] shadow only: no pricing, scanner or structure code reads it; the terminal shows it in its own panel; not logged  — 6 terminal lines use it, all in its panel; no Supabase write
  [PASS] descriptive wording only (no forecast words); fetched only when its buttons are pressed  — panel text scanned: forbidden words none; universe behind 'Load correlation structure', chosen names behind 'Check these names'

=== 41. SUITE INTEGRITY ===
  [PASS] a check that cannot run is counted SKIP: not passed, not failed, not published  — tally(PASS, SKIP, FAIL) = (1 passed, 1 failed, 1 skipped)
  [PASS] hermetic run: zero network connections or DNS lookups attempted by the whole suite  — 0 attempts

======================================================================
PASSED 339   FAILED 0