p_lower,
and size by Kelly off that lower bound — never off the optimistic point estimate. F7-options carries
that exact machinery onto multi-leg defined-risk Webull structures: each webull_strategy_id is its
own bucket, its break-even p* is read from the structure's payoff geometry, and the gate clears
only when that one bucket's p_lower exceeds its own p*. The gate is deliberately
bucket-level, not pool-level — averaging an edge in one structure against a drag in another is precisely
how a non-existent edge is manufactured, so no aggregate claim ever authorizes a live flip. We are on the
n = 25 → n ≥ 100 trajectory (largest bucket long_call at 29 closed); this document sets the gate,
it does not promise the gate will clear.
p_lower over ≥100 closed paper trades of a multi-leg options structure
does NOT predict p_lower > break_even survival when the same structure is flipped live, because
(a) the chain Greeks used by options_strategy_mapper.select_strategy are synthesized at selection
time and only OPRA-enriched at submit (per Phase-2 §2.2), and (b) live execution friction — assignment risk,
gap risk, IV crush around earnings, dealer skew flips — is not simulated in the paper-mode harness."
The null is the working assumption until data overturns it: a paper edge is an artifact of the harness, not a
property of the market. What would falsify it is narrow and specific — a single webull_strategy_id
bucket where, after ≥ 100 closed paper trades, p_lower > break_even and the realized
p_hat on the first 30 live trades stays within ±5 points of the paper p_hat.
Both halves are required. A paper bucket that clears the gate but whose p_hat collapses on contact
with real spreads, real fills, and real assignment is not a falsification — it is a confirmation of the
null, and the correct response is to stop flipping buckets, not to flip more. The gate is a hypothesis test
whose default outcome is "the edge did not survive"; clearing it is the surprising result that has to earn its
way past the lower bound.
This is the same posture the TRAIL Phase 1.5 study took toward a 36% vision hit rate and the F7 binary lab took toward the candle bet: a friction-honest instrument that returns "your edge did not survive" is doing its job. The F7-options gate is that instrument, pointed at the 52-element options Periodic Table one bucket at a time.
The gate clears for a bucket if and only if, over ≥ 100 closed paper trades of that specific
webull_strategy_id, the Wilson lower bound exceeds that bucket's geometry break-even:
This is the §0.6 binding rule of the original workboard, applied at bucket granularity. The discipline
is stated as a prohibition: no aggregate-level edge claim authorizes a live flip; only per-bucket clearance
does. A pool-level "the Webull labs are net-positive on paper" number is meaningless here — it can hide a
real edge in long_call behind a drag in short_box_spread, or, far more dangerously,
manufacture an apparent edge by averaging across structures whose risks do not net. The gate refuses to look at
the pool.
The gate's machinery emits a per-bucket verdict (from webull_service.f7_gate()):
verdict ∈ {
"INSUFFICIENT_SAMPLE", # n_closed < 100 → keep accumulating paper trades; nothing else changes
"EDGE_REJECTED", # n_closed ≥ 100 AND p_lower ≤ break_even → bucket retired from live consideration
"EDGE_CONFIRMED", # n_closed ≥ 100 AND p_lower > break_even → operator review may begin (NOT auto-flip)
}
Exactly one verdict authorizes operator action: EDGE_CONFIRMED. INSUFFICIENT_SAMPLE
means we are not done measuring. EDGE_REJECTED means we measured, and the edge was not there — that
bucket is retired from live consideration until the methodology itself is revised. Neither of those two is a
failure of the lab; they are the lab succeeding at telling the truth.
break_even per Periodic-Table element
Every element of the options Periodic Table (options_strategy_mapper.supported_strategies() →
52 entries) has a price-level break-even read directly from its leg geometry, and a probability-sense break-even
in Kelly form. The 6–8 most common structures:
| Strategy | break_even (price-level) | break_even (probability sense) |
|---|---|---|
long_call | S_T > K + premium_paid | 1/(1+b), b = (max_gain_at_T − premium)/premium |
long_put | S_T < K − premium_paid | same form, b from the put payoff geometry |
long_call_spread | S_T > K_long + net_debit | 1/(1+b), b = (K_short − K_long − debit)/debit |
long_put_spread | S_T < K_long − net_debit | symmetric to the call spread |
short_iron_condor | K_put_short < S_T < K_call_short (both wings stay OTM) | credit/(max_loss − credit) inverted into 1/(1+b) |
bull_put_spread | S_T > K_short − net_credit | credit/(spread_width − credit) → 1/(1+b) |
long_straddle | |S_T − K| > total_premium | 1/(1+b), b keyed to expected move |
long_strangle | as straddle, with two strikes | symmetric |
p* = 1/(1+b), where b is the realized payoff ratio averaged over the closed trades
in that bucket. This is exactly the F7 binary break-even — the formula does not change at all when we move
from a one-touch candle bet to a four-leg defined-risk structure. The only thing that changes is the source of
b: in the binary lab it was a fixed per-trade ratio |target−entry|/|entry−stop|; here it
is a per-bucket empirical average of the realized payoff ratio across that structure's closed trades. The
options pivot did not invent new math; it changed what fills the one variable the math already had.
The three buckets carrying live data (long_call, bull_put_ladder,
short_box_spread — §9) each resolve to the same p* = 1/(1+b) form; their b
values differ because the geometries differ, which is exactly why the gate is computed per bucket and never
pooled. The break-even semantics above are read-only descriptions of options_strategy_mapper.py; this
document does not modify the selector.
The win/loss outcome of a closed options bucket is a binomial proportion, and at the sample sizes we are at —
long_call at n = 29 is the largest bucket — the naive Wald normal approximation
p̂ ± z·√(p̂(1−p̂)/n) is badly miscalibrated: it produces intervals that are too narrow, can run below
0 or above 1, and degenerate entirely when p̂ is near 0 or 1. The Wilson (1927) score interval is
the standard remedy:
Wilson's interval is centred at a shrinkage of p̂ toward ½, is bounded in [0,1] by
construction, and — the property we depend on — its lower bound is materially tighter (more conservative)
than Wald's at small n, which is exactly the regime where over-claiming an edge blows up an account. Brown, Cai
& DasGupta (2001) surveyed the binomial-proportion interval zoo and found Wilson (and Agresti-Coull, which
approximates it) decisively preferable to Wald across small and moderate n. We size off p_lower, not
p̂, precisely so that a lucky-looking thin bucket cannot fund a fat bet — the F7 README's binding
instruction, carried forward verbatim.
p_lower and p̂ at n = 30 is wide enough that almost no bucket could honestly clear, and
by n = 100 that gap has narrowed to where a real edge, if present, can show itself above break-even. The number
is a convergence threshold, not a ritual.
Once a bucket has a defensible p_lower and a geometry-derived b, the growth-optimal
fraction is Kelly (1956), evaluated at the lower bound rather than the point estimate:
The quarter-Kelly cap is the Peters (2019) ergodicity discipline: full Kelly maximises the time-average growth
rate only if p and b are known exactly, which they never are; fractional Kelly is the
Bayesian-correct response to parameter uncertainty and trades a sliver of growth for a large reduction in the
dispersion of the single trajectory we actually live. The binding rule that makes this safe on the live flip:
f × defined_max_loss, NOT f × notional. A defined-risk
structure has a max loss we know to the dollar before submit — the debit paid, or the spread width minus
the credit. Sizing to that floor (not to the implied-notional ceiling the underlying could theoretically reach)
is what keeps a live flip survivable even for an EDGE_CONFIRMED bucket. The synthesized chain at
selection time gives us a modeled max-loss; the OPRA enrichment at submit (§6) gives us the priced
max-loss — Phase 2 is what makes the dollar figure in the budget honest rather than mocked.
b
Phase 1 selected and submitted on a synthesized chain: smooth mocked volatility, zero mocked spreads.
Phase 2 leaves the selector's synthesized chain in place (we still have no real chain endpoint) but enriches the
submitted legs with a live OPRA snapshot via webull_service.enrich_legs() — real bid/ask, real
IV, real per-leg Greeks. The two sources produce different b:
Input to b | Synthesized chain (_ensure_chain) | OPRA-enriched (enrich_legs) |
|---|---|---|
| Bid–ask spread | zero → no entry drag | real → debit paid is higher, b lower |
| Implied vol | smooth/flat surface | true IV smile → realized payoff geometry shifts as vol moves |
Resulting b | typically over-estimated | realistic — reflects the price you can actually be filled at |
| Calibration honesty flag | enriched: false | enriched: true (written into legs_json) |
This is Phase 2's central honesty improvement. The synthesized chain flatters b because zero spreads
and smooth vol are free money that does not exist; an over-stated b lowers p* = 1/(1+b)
and makes a bucket look closer to clearing than it is. The 75 additional closed paper trades that bring each
bucket from ~25 to 100 will be better trades, not merely more of them: their b reflects the
real OPRA payoff geometry, so the p_lower > break_even test they feed is measuring the structure
we could actually trade live — not a frictionless cartoon of it. Every leg carries the enriched flag
so calibration can, if needed, segregate the honest trades from the synthesized ones.
Code may surface the gate state. Code may not flip dry_run=False. The flip is a deliberate,
signed, bucket-scoped operator action:
GET /api/webull/f7/gate
↓
for bucket in response.buckets:
if bucket.verdict == "EDGE_CONFIRMED":
Operator review required. NOT auto-flipped.
Operator may then set ALLOW_LIVE_BUCKET=<strategy_id> in webull.env
and restart the service. webull_service.submit() reads this env on
every call and permits dry_run=False for that ONE strategy_id only.
All other strategies remain paper-mode.
elif bucket.verdict == "EDGE_REJECTED":
Bucket permanently retired from live consideration until methodology revision.
elif bucket.verdict == "INSUFFICIENT_SAMPLE":
Continue accumulating paper trades. Nothing else changes.
strategy_id at a time, never the pool. No verdict other than
EDGE_CONFIRMED on that specific bucket authorizes it, and even then the authorization is permission
for a human to review, not an instruction for the system to act.
MONTECARLOLAB/F7_MONTECARLO_LAB_README.md (F7 lineage — Wilson lower-bound,
derived b, ergodic Monte Carlo); docs/WEBULL_PIVOT_STUB_PROMPT.md §1 findings 1–4;
TRAIL Lab Phase 1.5 study. Companion Phase-1
whitepapers: Edge,
Exhaustion,
Divergence,
TRAIL,
Hybrid.This is the gauge of where we are — a live snapshot of closed paper trades per bucket against the n = 100 gate:
long_call n=29 closed → 71 more to gate bull_put_ladder n=15 closed → 85 more to gate short_box_spread n=15 closed → 85 more to gate
The F7 page in the Labs² nav surfaces this live (cursor's per-lab Gate cards plus the labs2-gate-pill
make it visible at a glance). Operator review is triggered when any bucket hits EDGE_CONFIRMED
— not when all of them do, and not on any pooled summary. As of this writing every bucket is
INSUFFICIENT_SAMPLE; the honest reading is that we have not yet measured enough to say anything, and
the gate is doing exactly what it should by withholding judgment.