dry_run=True broker echo.
This lab descends from the TRAIL Lab (trail_lab_service.py +
vision_extractor.py), documented in the
TRAIL methodology whitepaper and the
Phase 1.5 study. TRAIL's founding mission is that the
engine infers edge from the screen capture: a multimodal model (GPT-4o) reads the rightmost candles and
returns {direction, confidence, last_close}, and the engine opens a paired call/put straddle sized
by an asymmetric ¼-Kelly tilt proportional to that confidence, then grades each leg against a re-captured close
under a pristine/realistic double-entry friction book.
Phase 1.5's headline result is the one this lab must keep front and centre, quoted from the study:
"The vision model did not produce a directional edge on this run. On the 11 ticks where the asymmetric tilt was non-zero … it called the realised 2-minute sign correctly 4 times — a 36.4% hit rate against a 50/50 null. … A friction-honest harness that returns 'your gut was wrong' is doing its job." — trail_lab_phase1_5_study.html §6
Phase 1.5 also surfaced three mechanisms that bear directly on the options pivot: (1) the straddle insured
against direction errors — a wrong directional read could still book a profit because the structure was
hedged, which means the binary straddle was already doing payoff-shaping by accident; (2) the model showed a
systematic short bias (10 of 11 directional ticks bet short), a calibration defect, not a directional
one; (3) vision latency and resolution drift are real, unbudgeted systematic errors. These map onto the
pivot's headline findings #1 (broken calibration in our direction) and #3 (b ≈ 1 is a lie). The
Webull TRAIL Lab replaces the always-both-legs straddle with an explicit, selected defined-risk
structure, turning the accidental payoff-shaping of Phase 1.5 into the deliberate object of study (finding #5).
The classifier is vision_extractor.extract_chart_state() wrapping GPT-4o (OpenAI 2024), used
unmodified as the forecast layer. It returns {direction, confidence, last_close, support, resistance}
with confidence clamped to [0.5, 0.95]. The TRAIL lab maps this to the §2.2 regime block:
direction from the vision read, strength from the clamped confidence, and a
vol_regime hint from the support/resistance band width relative to recent range. As in Phase 1.5,
a missing confidence defaults to 0.5 ("default to no edge, never default to no tick") — which
in options terms means a neutral, near-symmetric defined-risk structure rather than a skipped observation.
The selector is restricted, for this lab, to the defined-risk directional subset of the Periodic Table (WEBULL_PIVOT §3) — the spreads that turn a directional read into a capped-risk payoff:
| Vision read | Primary candidate | Alternates |
|---|---|---|
| Long, confidence high, IV-rank mid/low | bull_call_spread | long_call, bull_put_spread |
| Short, confidence high, IV-rank mid/low | bear_put_spread | long_put, bear_call_spread |
| Direction held, confidence ≈ 0.5 (neutral default) | long_iron_butterfly | long_call_calendar_spread |
Vision returned ok=false (unreadable frame) | declined — no structure, counted stale | — |
The iron butterfly in the neutral default is the deliberate heir to Phase 1.5's symmetric straddle: when the
model has no conviction, the lab takes a defined-risk, range-bound structure rather than a directional one —
no edge claimed, no edge missed, the friction clock still runs. The ok=false path preserves
TRAIL's honesty rule: a misread is a declined measurement, never a fabricated number, and the leg falls
through to a counted, zero-P/L stale state exactly as a dead OHLC feed did.
Closed trades persist on the existing paper_trade_service.py row (no parallel DB) with the §2.4
columns. The Phase 1.5 short-bias and overconfidence findings make the calibration columns load-bearing here:
webull_strategy_id + the per-bucket Wilson lower bound let us see whether the vision read's stated
confidence maps to realized win rate on each spread type. /api/cv/paper/calibration?strategy_id=
buckets bull_call_spread apart from bear_put_spread so the short bias (if it persists)
shows up as one bucket carrying the sample. Wilson 95% lower bound per bucket (Wilson 1927).
Break-even is p* = 1/(1+b) off the vertical geometry; sizing is fractional Kelly off
p_lower (F7 README — never p_hat), the direct successor to TRAIL's asymmetric
¼-Kelly tilt. The additive ergodic Monte Carlo (Peters 2019) reports time-average growth, the same instrument
that turned Phase 1.5's $100 → $73 bankroll line into a finding rather than a verdict. A positive ensemble 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* once friction is applied.
We reject H0 only if, over ≥ 100 closed paper option-trades in at least one
webull_strategy_id bucket, the Wilson lower bound exceeds break-even:
and the stated vision confidence is calibrated to within 5 points of that p_lower for the
bucket (the Phase 1.5 overconfidence clause). A bucket that clears p* only because one direction
happened to win during a trending sample — while the confidence remains miscalibrated and the short-bias
persists — does not reject H0; it reproduces the Phase 1.5 pathology in a new wrapper. The
per-strategy bucketing is what distinguishes "vision predicted direction" from "the tape trended our way."
Per the lab's stance — set explicitly by the Phase 1.5 study — a null-not-rejected result is a successful
run. A sustained failure to clear p* teaches:
defined_risk_only guard plus the iron-butterfly neutral default kept a confident-and-wrong
vision read (the ticks 5–8 pathology of Phase 1.5) from compounding into ruin.dry_run=false broker response, any
undefined-risk leg booked while defined_risk_only=true, or — TRAIL-specific — a fabricated
last_close from a frame the extractor could not actually read. The first two are caught by the
smoke-test sign-off; the third is contained by the ok=false → stale path.
Must-do:
bull_call_spread trades won? Below-diagonal → damp the confidence-to-tilt mapping.theta_paid_usd vs realized R per bucket.+0.8/−1.0 contract; this is the exact contract Phase 1.5 said the original "80% hit rate"
intuition should be benchmarked against.Must-not:
p_lower > p* across many sessions and symbols.defined_risk_only=false while p_lower < p* — a confident-and-wrong
vision read on an undefined-risk leg is precisely the failure mode the defined-risk default exists to
contain.b, break-even
p* = 1/(1+b); successor to TRAIL's asymmetric ¼-Kelly tilt.)