dry_run=True broker echo.
This lab descends from the binary Provider Divergence Lab
(divergence_lab_service.py), documented in the
Provider Divergence Lab whitepaper. That instrument
answered one narrow, falsifiable question — given an LLM trained to read candlestick charts, how often do
two providers of the same underlying yield different verdicts? — using a per-tick Discrepancy Index
(d_direction, d_confidence, d_pattern) and session-cumulative agreement
corrected by Cohen's κ (1960) and Fleiss' κ (1971), interpreted on the Landis & Koch (1977) scale.
Crucially, the binary divergence lab declared placing trades explicitly out of scope: "The lab makes zero bets. That isolation is intentional." It was a pure measurement of disagreement. The Webull Divergence Lab is therefore not a re-skin — it is the first time the divergence signal is wired to an executable structure at all. That makes the lineage finding the pivot's headline #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
The thesis is intuitive: when two competent readers of the same instrument disagree on direction, the future
is genuinely more uncertain than a single confident reader implies — and elevated uncertainty is, in options
terms, elevated realized volatility relative to the implied volatility currently priced into the chain. A
long straddle (or strangle) is the textbook expression of "I expect realized to exceed implied." If
divergence predicts that gap, the structure pays regardless of which way price ultimately breaks. Findings #1
(broken calibration) and #3 (b ≈ 1 is a lie) still bind: we read the real b off the
straddle's cost-vs-payoff geometry and size off p_lower, never the engine's stated confidence.
The classifier is divergence_lab_service.py, used unmodified as the forecast layer. Its
divergence score — a function of the per-tick Discrepancy Index and the running κ — is mapped to a
volatility-regime expectation: high disagreement ⇒ vol_regime="expansion" (realized likely to
exceed implied), strong agreement ⇒ vol_regime="contraction". The engine emits the §2.2 regime
block with divergence populated and direction="neutral" in the high-divergence case —
because the entire point is that direction is unknown; only the magnitude expectation is informative.
The selector is restricted, for this lab, to the long-volatility / defined-debit subset of the Periodic Table (WEBULL_PIVOT §3):
| Regime read | Primary candidate | Alternates |
|---|---|---|
| High divergence, IV-rank low (implied cheap) | long_straddle | long_strangle |
| High divergence, IV-rank mid | long_strangle | long_call_calendar_spread |
| High divergence + slight directional lean | long_strangle (skewed strikes) | bull_call_spread/bear_put_spread |
| Strong agreement / low divergence | floor — regime not armed | — |
A long straddle/strangle is defined-risk by construction (max loss = net debit paid), so it satisfies
defined_risk_only=true without special handling — a clean fit. The payoff ratio
b = max_gain/max_loss for a long straddle is large but the win probability is correspondingly low
(both legs paid for, only large moves pay), which is exactly why the geometry-derived break-even
p* = 1/(1+b) and the Wilson lower bound matter so much here: a straddle can look cheap and still
be a negative-expectancy bet if realized rarely clears the doubled premium.
Closed trades persist on the existing paper_trade_service.py row (no parallel DB) with the §2.4
columns. iv_rank_entry / iv_rank_exit and vega_at_entry are central
here: a long straddle is a long-vega bet, so the calibration must show whether a "win" came from realized
exceeding implied (the thesis) or merely from IV-rank rising after entry (a vega tailwind unrelated to
divergence). /api/cv/paper/calibration?strategy_id= buckets long_straddle separately
from long_strangle. Wilson 95% lower bound per bucket (Wilson 1927).
Break-even is p* = 1/(1+b) off the straddle geometry; sizing is fractional Kelly off
p_lower (F7 README — never p_hat). The long straddle's payoff distribution is the
mirror image of the exhaustion lab's: it wins rarely and large, loses often and small (theta bleed) —
positive skew with a high break-even probability. The additive ergodic Monte Carlo (Peters 2019) is essential
here precisely because a positively-skewed bet can show a tempting ensemble EV while the time-average growth
path stalls under the steady theta drag between the rare paydays. A positive EV with ≤ 0 growth rate is a fail
(finding #2).
webull_strategy_id bucket, the Wilson lower bound p_lower stays at or below the
geometry break-even p*.
We reject H0 only if, over ≥ 100 closed paper option-trades in at least one
webull_strategy_id bucket (long_straddle or long_strangle being the
buckets the null names), the Wilson lower bound exceeds break-even:
and the edge is attributable to divergence rather than to a generic long-vega tailwind — i.e. the same
straddle bucket, entered on low-divergence ticks as a control, does not also clear
p*. If straddles pay regardless of divergence, the signal is "vol was cheap," not "feeds
disagreed," and H0 stands. The κ-bucketed divergence score plus the IV-rank columns are what make
that separation auditable.
b can be 3–5, which drops p* to
0.17–0.25 and makes it tempting to declare an edge on a thin sample. We hold to ≥ 100 closed trades per bucket
anyway, because positive-skew payoffs are exactly where small samples lie most — a handful of lucky large
winners can lift p_hat far above the truth, which is the entire reason the F7 discipline sizes off
p_lower and not p_hat.
Per the lab's stance, a null-not-rejected result is a successful run. A sustained failure to clear
p* teaches:
dry_run=false broker response or any leg
bundle that is not defined-risk while defined_risk_only=true. The smoke-test sign-off proves
neither occurred.
Must-do:
theta_paid_usd against realized R; a long straddle
lives and dies by whether rare large moves outrun the steady decay.+0.8/−1.0 contract; a "range vs break" binary is the binary analogue of the straddle and
lets the original divergence claim be benchmarked directly.Must-not:
p_lower > p*.defined_risk_only=false while p_lower < p* — there is no reason
to add a short-vol leg to a long-vol thesis that has not yet proven itself.b, break-even
p* = 1/(1+b).)