2026-05-18 - PUT exhaustion veto shipped (MK2 Stage-2)

Second validated negative-filter in the MK2 net-positive-cash-flow stack (after the ToD PUT-block). Commit 3eb1951, pushed branch + main, live next session via the normal launchd restart. GH issue #56 work-logged.

Validated basis

scripts/analysis/exhaustion_veto_stage1.py → verdict GO. After the fixed-ToD control AND on top of the already-shipped ToD PUT-block: all-PUT marginal **net saved 24,200, winner cost 19,304. Trend-day clip cost in Stage-1: $0 (0/32 flagged were path-trend days).

The rule (PUT-only, pre-entry only)

Block a PUT entry when all hold, computed from pre-entry path only (t_offset_ms <= 0, post_exit != 1, price/range only): range ≥ 0.15%, net÷range ≥ 0.45, dominant(down)-bar fraction ≥ 0.38, final range position in the outer ≤25% band (at the pre-entry low extreme). Thresholds env-overridable; PUT_EXHAUSTION_VETO_ENABLED default true, one-line revert. CALL untouched (no mirror unless separately validated).

Trend-day override (the one real risk, resolved)

Stage-1 path-proxy vs engine/ADX only agreed 20%, so a live veto needed an intraday-knowable trend proxy. Shipped: suppress the veto when the pre-entry PUT path is a strong one-way continuation - abs(net)/path_length >= 0.70 OR down-bar fraction >= 0.72. Note net/path_length (not net/range) is used here, consistent with the Stage-1 memo’s own guidance that path-length is the trend-day diagnostic. Higher bar than the veto trigger by design: moderate exhaustion → vetoed; violent one-way trend day → not vetoed.

Verification (independent, not the report-about-the-report)

  • Scope: only config.py, engines/scoring.py, 3 test files (+593/-0), both refs. No live trading/exit/position-manager/CALL path.
  • Look-ahead clean (the load-bearing check): the function explicitly drops rows with t_offset_ms > 0 and post_exit == 1, builds the path only from the pre-entry buffer up to the decision timestamp. Post-entry deceleration is never read by the veto.
  • Pure, flag-gated, PUT-only, early-return on disabled / not-PUT / insufficient-path (fails open - does not suppress on missing data).
  • Placement: post-meta-gate + post-ToD-block, pre-order - the same admission layer already verified clean for the ToD PUT-block (04968a8).
  • Tests: full suite 871 passed (Codex; not independently re-run - scope + hunk + look-ahead were the verification, consistent with the ToD-block standard).

Role in the MK2 → MK3 decision

This is filter #2 in the stack whose collective job is to get the MK2 alert stream to net-positive expectancy after costs with a bounded loser tail. If the stack (ToD-block + exhaustion + expectancy gate + thesis-invalidation) achieves that, porting the signal logic to MK3 is worth it; if not, MK3 should be the SPY-Hunter rebuild. Either outcome is decision-grade. See 2026-05-18-put-side-structural-loss-and-bypass-impulse, 2026-05-15-mk3-setup-hunter-architecture, leading-signal-method-families-0dte.