binary_exhaustion_engine.py regime detector. It tests one pre-registered null hypothesis, stated
in full here and never softened downstream: H0 — the exhaustion regime, paired with an expected
volatility contraction, does not produce a calibrated debit-spread edge that survives friction; the
"exhaustion ↔ vol-contraction → debit-spread edge" claim is false. The prior leg established that
exhaustion regimes contain regime information but did not yield an executable binary edge after costs
(finding #4). This lab asks whether that same regime signal, re-expressed as a defined-risk debit spread sized
for an expected fade-plus-contraction, finally clears break-even. We reject H0 only on the hard
evidence of §3; a failure to reject is the lab's expected and fully successful outcome. No real order is
placed this phase; every leg is a dry_run=True broker echo.
This lab descends from the binary Exhaustion Lab (exhaustion_lab_service.py +
binary_exhaustion_engine.py), whose methodology is documented in the
Binary Exhaustion Lab — hybrid_v1 whitepaper. That
engine runs a consecutive-candle counter, moving-average stacking (9/20/50/200), distance-from-mean in ATR
units, ZigZag swing context, and Kaufman's Efficiency Ratio (ER) regime gate, then folds an optional vision
vote and a PAV isotonic calibrator to emit a bull-probability in [0,1]. The binary lab even
shipped a non-directional "straddle" variant and a CHOP-BUSTER addendum — early acknowledgements that the
exhaustion signal is really a volatility statement dressed as a direction statement.
The binary phase's verdict on this regime is headline finding #4, quoted verbatim:
"The provider-divergence and exhaustion regimes do contain regime information, but in binary form they don't produce an executable edge after costs. They DO meaningfully shift implied-vol expectations — which is the bridge to options." — WEBULL_PIVOT_STUB_PROMPT.md §1.4, finding 4
That last clause is the entire reason this lab exists. An exhaustion print — price stretched many ATRs from its
mean, consecutive same-color candles, MAs fanned — is a statement that a move is overextended and that
realized volatility is likely to contract as the trend mean-reverts or stalls. A binary up/down bet
cannot monetise "vol is about to contract while price fades"; a debit spread can — it caps risk, defines
the fade direction, and pays as the underlying drifts the expected way while decay works with rather than
against a tightly-bracketed structure. Finding #3 ("b ≈ 1 is a lie") and finding #1 (broken
calibration) both warn that we must read the real payoff ratio off the spread geometry and size off
p_lower, not the engine's stated confidence.
The classifier is binary_exhaustion_engine.analyze_binary_exhaustion(), used unmodified as
the forecast layer (WEBULL_PIVOT §1.2 — the engines feed the selector, they are not rewritten). Two of its
outputs drive this lab specifically: the exhaustion score (how overextended the trend is, in ATR-stretch
and consecutive-candle terms) and the Kaufman ER regime label (trending
/ mixed / choppy), which the selector reads as a
volatility-regime proxy. The engine emits the §2.2 regime block with exhaustion populated and
vol_regime ∈ {expansion, contraction, mid} derived from ER and ATR trend.
The selector (options_strategy_mapper.select_strategy) is restricted, for this lab, to the
defined-risk fade + contraction subset of the Periodic Table (WEBULL_PIVOT §3):
| Regime read | Primary candidate | Alternates |
|---|---|---|
| Exhaustion high at top, vol-contraction expected | bear_put_spread (debit) | bear_call_spread, long_put_butterfly |
| Exhaustion high at bottom, vol-contraction expected | bull_call_spread (debit) | bull_put_spread, long_call_butterfly |
| Exhaustion high, vol-contraction, no directional bias | long_iron_butterfly | long_call_calendar_spread |
| Exhaustion low / ER trending strongly | floor — regime not armed | — |
The debit spread is the primary because it is the cleanest test of the null: a debit structure pays for
the fade direction with strictly defined risk and a geometry-derived payoff ratio b = max_gain/max_loss.
Butterflies and calendars are alternates that lean harder on the contraction (theta-positive) leg of the thesis.
With defined_risk_only=true the selector never returns a naked short-vol leg — important here,
because selling a strangle into an exhaustion print is exactly the fat-tail trap the hybrid_v1 CHOP-BUSTER
section warned against (Merton 1969).
Closed trades persist on the existing paper_trade_service.py row (no parallel DB) with the §2.4
columns. theta_paid_usd and vega_at_entry matter most for this lab: the exhaustion
thesis is partly a short-vega / positive-theta bet, so the calibration must show whether a "win" was the fade
being right or merely carry being collected. /api/cv/paper/calibration?strategy_id= buckets per
structure so a debit-spread edge is not averaged against a calendar drag. Wilson 95% lower bound per bucket as
in the binary lab (Wilson 1927).
Break-even for a defined-risk spread is p* = 1/(1+b) with b read off the spread
geometry. Sizing is fractional Kelly off p_lower (F7 README: never p_hat). The
additive ergodic Monte Carlo (Peters 2019) reports time-average growth, not ensemble EV — and for a
short-vol-flavoured structure the fat-tailed loss when a fade fails is exactly the kind of variance that makes
ensemble EV and time-average growth diverge. A mildly positive EV with ≤ 0 growth rate remains a fail
(finding #2).
webull_strategy_id bucket the lab funds, the Wilson lower-bound
p_lower stays at or below the geometry break-even p* once friction is applied.
We reject H0 only if, over ≥ 100 closed paper option-trades in at least one
webull_strategy_id bucket (the debit-spread buckets are the ones the null names explicitly), the
Wilson lower bound exceeds break-even:
and the edge is not an artifact of collected theta — i.e. the bucket's mean realized R stays positive after
theta_paid_usd is attributed honestly. A bucket that "wins" only because decay was collected while
the directional fade was a coin flip does not reject H0; it confirms the regime carried
vol information (finding #4) without a directional edge.
vega_at_entry / theta_paid_usd
columns exist precisely to separate "the engine predicted direction" from "we sold vol that happened to
contract." Only the former rejects the directional null; the latter is a different (and weaker) claim that
needs its own ≥ 100-trade bucket to stand on.
Per the lab's stance, a null-not-rejected result is a successful run. A sustained failure to clear
p* teaches:
defined_risk_only guard prevented an exhaustion-fade-gone-wrong from blowing up the account
— we learned the regime has no executable edge without paying ruinously to learn it.dry_run=false broker response, or
any undefined-risk leg bundle booked while defined_risk_only=true — e.g. a bare short straddle
slipping through the selector. Either breaks the instrument. The smoke-test sign-off proves neither occurred.
Must-do:
bear_put_spread trades actually paid? Below-diagonal → dampen the exhaustion
score's influence on selection.theta_paid_usd vs realized R so a contraction
harvest is never misread as a directional edge.+0.8/−1.0 contract and re-derive break-even, so the original binary exhaustion claim can be
benchmarked head-to-head against the options expression.Must-not:
p_lower > p*.defined_risk_only=false while p_lower < p* — selling
undefined-risk vol into an exhaustion print is the Mertonian fat-tail trap the hybrid_v1 CHOP-BUSTER
analysis already flagged.b, break-even
p* = 1/(1+b).)