Repository navigation
Releases
Earnings Decision Lab v4.1.0 — Resilient Forward Settlement
v4.1.0 — Resilient Forward Settlement
Released 2026-09-05. Tag v4.1.0. Previous release v4.0.2.
A minor release, not a patch: it changes settlement semantics, the database schema, the
scheduler, provider normalization, and how the Track Record reports realized outcomes.
Why
On 2026-09-04 the 15:30 ET forward window settled 8 of 35 configurations and failed 27, every
one under required exit side missing on leg(s) [...]. The five contracts blocking them had
answered within milliseconds — they had answered that nobody was bidding.
Captured in-process on the shared production connection, each delivered:
tickPrice(66 DELAYED_BID) = -1.0
tickSize (69 DELAYED_BID_SIZE)= 0
tickPrice(67 DELAYED_ASK) = 0.01 <- ask side quotes normally
Control contracts on the same delayed feed returned real bids of 0.65 (size 416) and 0.01
(size 4), so -1 with size 0 is not a feed limitation. It is IBKR stating that the bid book
is empty.
What changed
Provider normalization
_write_tick still refuses to record -1 as a price — it is not a price — but no longer
discards the statement with it. The sentinel is recorded as {side}_no_data_sentinel, bid and
ask sizes gained canonical fields, and OptionQuote carries bid_size, ask_size,
bid_book_empty and ask_book_empty, each set only from what the provider actually said.
The bounded warm-up gained a terminal predicate. An explicitly empty book stops it after one
attempt; genuine silence still spends all five. Measured: 7.54 s per affected leg before,
1.51 s after.
Failure taxonomy
NO_BID / NO_ASK (the book is empty) are distinguished from REQUIRED_SIDE_TIMEOUT (nothing
arrived). The persisted failure detail now names the required side, the contract id, and the
observed bid/ask/last with their sizes — instead of only the leg indices.
End-of-day settlement fallback
Per leg, in strict order:
| Order | Pricing source | Meaning |
|---|---|---|
| 1 | EXECUTABLE_BID / EXECUTABLE_ASK |
A real required-side quote captured at or before the close. Unchanged, and still first. |
| 2 | MARKET_CLOSE_FALLBACK |
That contract's own final same-session closing mark. A closing mark, explicitly not a fill. |
| 3 | EXPIRATION_INTRINSIC_AT_CLOSE |
Only for a contract expiring that day with no usable mark, against the official underlying close. |
A contract that is not expiring never reaches step 3, so a living option is never written
down to zero merely because its book was empty. No Black-Scholes or other model price, no
prior-day close, no midpoint, and no last-trade substitution exists anywhere in the path.
v4_eod_settlement_fallback runs weekdays at 16:30 ET, applying this to whatever that
session's window could not price. It is idempotent — a configuration that already settled is
skipped — so it is a no-op on a normal day.
Two real IBKR constraints
Both confirmed live, not assumed:
- Error 162 — daily (EODChart) bars are unavailable for OPTION contracts on this
entitlement, so option closing marks come from the last regular-hours intraday trade bar of
the session. - Error 2188 — "up-to-the-second historical data requires additional subscription" sits in
IBKR's 2100–2200 warning band and arrives before the real bars. Classifying it as a system
error failed every historical request whose window reached near the present, which is exactly
the request an end-of-day close must make. Handling it also exposed a latent bug in that notice
path: it coerced a pending result to a dict, which would have made the nexthistoricalData
callback raise on.append.
The CLOSE market-data tick (9/75) is deliberately not used: IBKR defines it as the
previous session's close. LULU's 127-call reported close = 3.79 against an ask of 0.01
that afternoon.
Schema — append-only settlement history
Migration d1f3a5c7e9b2:
- drops
uq_v4_shadow_config_settlement_one_per_config; - adds a partial unique index on
shadow_config_result_id WHERE status = 'SETTLED'; - adds
pricing_method,recovery_provenance,supersedes_settlement_id.
A failed attempt stays exactly as written and is never rewritten (the UPDATE-rejecting trigger
is unchanged). A recovery is appended as a new row pointing back at what it supersedes. "Never
settled twice" became a database guarantee rather than an application convention. Read models
resolve a configuration to its settlement of record so a recovered position is counted once,
not once as failed and again as settled.
The downgrade refuses to run while any configuration carries superseded history, rather than
destroying evidence to restore the old constraint.
Track Record — settlement quality is first-class
Every settled position carries an evidence grade, worst-wins across its legs:
| Grade | Meaning |
|---|---|
EXECUTABLE_BID_ASK |
Every leg priced on its own required side |
MARKET_CLOSE_FALLBACK |
At least one leg priced at that session's closing mark |
EXPIRATION_INTRINSIC_AT_CLOSE |
Intrinsic at the official underlying close |
UNRESOLVED |
No permitted price could be established |
The Track Record reports counts and rates for all four, and offers two views:
- All forward outcomes — every settlement of record.
- Executable-only outcomes — realized metrics restricted to settlements transactable on
every leg.
The second is an analytics filter and nothing more. No observation is deleted, both views read
the same immutable rows, and the quality counts always describe the full set so the two
reconcile. A closing mark is never presented as an executable fill.
Recovery of 2026-09-04
All 27 blocked configurations were settled at 16:54:13 ET — the real observation instant, not
restamped to 15:30. 16 unique contracts were quoted once each and mapped back to every
configuration sharing them. Expiration intrinsic was never needed: every blocked contract had a
real traded closing mark.
The original 27 failed rows and all 24 original observations are byte-identical to their
pre-recovery digests (65f1c8fd… settlements, 73091416… observations). The 15:30 window's own
telemetry still reads "1 of 6 captured", deliberately unaltered.
Verification
1,695 backend tests (ruff and mypy clean over 231 source files); frontend tsc, eslint and
build clean. Migration head d1f3a5c7e9b2.
Not in this release
Nothing here changes candidate generation, strike geometry, expected-move methodology, the
V4.4A valuation, V4.4B ranking v1, strategy semantics, or the six cohort definitions.
Earnings Decision Lab v4.0.2 — Earnings estimates from the calendar first
Earnings Decision Lab v4.0.2 — Earnings estimates from the calendar first
Patch release on top of v4.0.1 (same methodology, six configurations, 15:30 ET windows with
settlement priority). No performance claim.
Fixed
- Earnings estimates no longer depend on Alpha Vantage's daily quota. The research
preparation step now takes the next report date and the EPS / revenue consensus from the
EarningsAPI earnings calendar first (date_source = EARNINGS_CALENDAR); Alpha Vantage is
consulted only when the calendar has no upcoming report for the company. Lululemon's
"malformed row … rate-limit-degraded CSV shape" warning was exactly this: the free tier's
25 requests a day had been consumed by the worker although the calendar already knew the date
and the estimates. - Data Providers shows the new order (EarningsAPI primary, Alpha Vantage fallback).
Known limitations
Unchanged from v4.0.1.
Earnings Decision Lab v4.0.1 — Post-release fixes
Earnings Decision Lab v4.0.1 — Post-release fixes
Patch release on top of v4.0.0 (same methodology, same six configurations, same 15:30 ET
decision and T+1 15:30 ET settlement with settlement priority). No performance claim.
Fixed
- Earnings calendar: tickers, day numbers and the "+N more" overflow open a full day table
(every company reporting that day, timing, market cap, estimates, V4 pipeline state and
windows); tickers link to the company workspace. The old chips pointed at a route retired in
the V4-only reset. - Pipeline feed: forward-only by default — windows still open or ahead plus every event with
real V4 evidence. Pre-activation rows (decision window missed before V4 existed) are no longer
product rows;include_past=truereturns the complete monitoring view. - Brand mark: white-background mark by default (README, tab icon as a classic BMP
favicon.ico+ PNG links); sidebar navy tile on the light theme, white mark on the dark theme. - Eastern time for the Dashboard decision/settlement columns and the forward-window card.
- Eligibility: "US listed" now means listed on a US exchange (SEC's exchange list), not
domiciled in the US — Lululemon (CA, Nasdaq) and similar companies are eligible; an
unavailable lookup is reported as unverified, never as "not listed". - Pager: the "Show all N" control matches the other pager buttons on both themes.
- CI: official IBKR TWS API client fetched before
uv sync; retired-job migrations tolerate
a fresh database; backend tests run against an isolated CI test database and import cleanly
under a plainpytestinvocation.
Known limitations
Unchanged from v4.0.0: tiny forward sample, delayed market data, class-share resolution edge
cases, shared strategy-family universe for Aggressive and Moderate, no order execution.
Earnings Decision Lab v4.0.0 — V4 Forward Engine
Earnings Decision Lab v4.0.0 — V4 Forward Engine
First official V4-only software release. This is a software-architecture release, not a
proof of profitability: the forward evidence sample is one natural decision (AVGO,
2026-09-02) whose first settlement is scheduled for 2026-09-03 15:30 ET.
Highlights
- One decision engine (V4). The V3 engine, its pipeline, track record and UI are retired;
recovery points:archive/pre-v4-only-reset,archive/pre-v4-main. - 15:30 ET decision on the last trading day before the announcement; 15:30 ET settlement on
the first post-earnings trading day; due settlements are settled before any new decision
observation (single forward-window job, bounded lock scope, per-attempt telemetry). - Automatic research preparation with EDGAR company resolution and readiness KPIs.
- V4-only frontend with Live Operations, V4 Decision Lab, Candidate Explorer and the
six-cohort Forward Track Record; SPA navigation stalls fixed at the root.
Architecture
Earnings Calendar → Research Preparation → Company Evidence / RAG → DeepSeek DecisionView →
Expected Move → Semantic Compatibility → Strike Geometry → Candidate Generation → T+1 Scenario
Valuation → Ranking v1 → Six Configurations → 15:30 Entry Observation → Post-Earnings T+1 15:30
Settlement → Forward Track Record.
Six cohorts
$2K and $10K standardized capital × Conservative (15% risk cap, 0.80 liquidity floor, no
single-leg longs) / Moderate (30%, 0.40) / Aggressive (50%, no floor). Same frozen evidence,
independent RANKED / NO_ACTION outcomes, per-configuration entry and settlement rows.
Market data
Interactive Brokers TWS API, one long-lived socket per process, bounded timeouts, typed errors.
Current entitlement: delayed market data, labelled delayed on every quote and page.
Safety
No brokerage execution, no order-placement API, no position modification; the IBKR
integration is read-only by application design. Evidence tables reject updates at the
database level. Tests cannot reach production.
Forward testing
V4 evidence begins prospectively on 2026-09-02. No historical backfill, no V3 performance,
no profitability claim. Below 30 settled observations a cohort shows INSUFFICIENT SAMPLE.
Known limitations
- The forward sample size is currently tiny; no profitability claim is made or implied.
- The current IBKR market-data feed is delayed.
- Some class-share / company-resolution edge cases may remain (e.g.
BF.A,BF.B). - Aggressive and Moderate currently share the strategy-family universe.
- No order execution exists, by design.
Options Decision Engine V3
Options Decision Engine V3 — a deterministic pipeline from a real option chain to a scored, explained recommendation, with every intermediate decision shown, not buried in a black box.
Expiration Engine
Real, listed expirations are discovered live and scored on six components (Event Fit, Liquidity, Quote Coverage, Bid/Ask Quality, DTE Suitability, Data Quality) — never a hard-coded "nearest expiration." Auto mode selects the highest-scoring real candidate; Manual mode recomputes every contract, strike, premium, and strategy from any real listed expiration the user picks. Alternatives are never hidden.
Risk Profiles
Selected per decision — Conservative, Moderate, Aggressive — each with real, different liquidity gates and default risk-budget utilization. Confirmed live: the identical snapshot/budget produces a genuinely different recommended category and position size across the three, never just a cosmetic label. Uncovered/naked short options are never a default recommendation at any profile.
Probability Analytics
Three deliberately distinct metrics, never collapsed into one number: Historical Compatibility (real sample size, e.g. "100.0% (25/25 events)"), Estimated Probability (same empirical basis, Wilson 95% confidence interval, a Low sample confidence flag below N=20), and True Strategy Win Rate — honestly "Unavailable" until this project actually captures real settled option entry/exit prices, never substituted or estimated.
Strategy Ranking V2
9 weighted scoring components (Direction Fit, Volatility Fit, Expiration Fit, Breakeven Fit, Historical Fit, Risk/Reward, Risk Profile Fit, Liquidity, Data Quality), summing to 100. Reconstructed historical-last pricing is explicitly labeled and distinguished from real bid/ask throughout.
Explanation Engine
Every recommendation carries Why This Strategy, Why This Expiration, Why These Strikes, and Why Not Alternative — a real numeric comparison against the runner-up (premium cost ratio, max loss difference, driving score component, historical compatibility difference), never generic or duplicated text.
E2E Testing
Mandatory Playwright suite, 8/8 passing, deterministic — a dedicated fixture ticker and a DB-backed fixture options provider give the suite zero live-IBKR dependency, safe in CI. Covers score-driven Auto expiration selection, Manual expiration recomputation, Risk Profile round-tripping, budget sizing within its risk cap, honest probability/win-rate disclosure, numeric Why Not Alternative content, and no-fabrication for an unresearched ticker.
Testing: 925 backend tests passing (ruff clean, mypy clean), frontend TypeScript/ESLint/build clean, Playwright 8/8, full Docker Compose stack building and booting green in CI.
Not investment advice — see the README's Limitations and Disclaimer sections.