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 > 0andpost_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.