fix(data_sources): merge Alpaca indicative NBBO + IEX spot into the options collector (ALP-949) - #359
Conversation
…ptions collector (ALP-949) Polygon's options snapshots on the current plan carry greeks/OI but withhold last_quote/last_trade/underlying_asset.price, so every options_contract_snapshots row since the table's first batch has NULL bid/ask/last_price/underlying_price — no IVr context line renders, the chain tool returns no contracts, premium validation rejects everything, and option marks have no price. Merge at collection time (no schema/schedule change): - New vendor module src/alphamind/data_sources/alpaca/options.py: fetch_chain_quotes (OptionHistoricalDataClient, indicative feed — _OPTIONS_FEED is the single flip point for a future OPRA upgrade) and fetch_underlying_trades (one multi-symbol IEX StockLatestTradeRequest). - collect_options_chains fetches the spot map once per run and the Alpaca chain per underlying (with_retries important); any Alpaca failure degrades that scope to Polygon-only rows — never aborts. - _build_rows fills bid/ask/last_price/underlying_price where Polygon's value is None (Polygon wins per field under a future plan upgrade); Alpaca rows matched via bare OCC symbol (O: prefix stripped); source is polygon+alpaca when the chain fetch succeeded, else polygon. - config/data_sources.yaml already carries the alpaca provider entry (Q1 failover) — no config change needed. - docs/runbooks/services.md documents the dependency + degraded mode. Tests: fakes in tests/data_sources/_fakes/alpaca.py (sanctioned broker-API boundary), vendor-module tests, merge/precedence/isolation/ spot-failure tests; existing options tests inject empty Alpaca fakes for hermeticity. Two `# type: ignore[no-untyped-call]` on APIError construction in tests, mirroring tests/execution/broker_adapter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Move the batch spot fetch inside track_run so an unexpected exception
still writes a failed-run record to collection_runs.
- Add ValueError to ALPACA_FETCH_ERRORS: alpaca-py raises it on
unexpected response shapes (and pydantic ValidationError subclasses
it) — a vendor format change must degrade, not abort the run. The
spot-failure test now pins this member.
- Install a socket-level timeout on both Alpaca data clients (alpaca-py
issues blocking requests calls with no timeout); mirrors the execution
layer's client factory shim (ALP-841) with a test in the same shape.
- Hoist the per-contract `alpaca_quotes or {}` allocation out of the
10k-iteration row loop.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ALP-949 Options chain snapshots carry no NBBO / underlying price (Polygon plan entitlement) — merge Alpaca indicative quotes + IEX spot into the options collector
SymptomThe first scheduled run under ALP-948 ( Evidence (prod DB + live Polygon + live Alpaca, 2026-06-11)
Root cause (confirmed as reported)The ALP-948 feature code is correct and fully wired. The Polygon options snapshot response on the current API plan never includes (A) IVr context. (B) Chain tool. (C) Premium validation. (D) Option position marks. Why it escaped testsThe collector tests fabricate Polygon snapshots via Reading
Scope — merge Alpaca indicative NBBO + IEX underlying spot into the existing polygon.options collection taskAlpaca cannot replace Polygon (no open interest / volume in its option snapshots — the chain filter's OI floor needs Polygon) and Polygon cannot supply quotes on the current plan — the two complement exactly, so the fix is a merge at collection time. No schema change, no schedule change, no new collector task. (A) New vendor module. (B) Merge in collect_options_chains ( (C) Provider registry entry. Add to (D) Tests. New fake (E) Runbook. Out of scope: no Alembic migration (all columns exist); no backfill of the ~30.1M historical quote-less rows (readers take only the latest snapshot per contract, and Acceptance criteria
Verification
Post-deploy production confirmation (operator, next market-hours collection): One-time prod data cleanupNo bulk cleanup needed. The historical quote-less rows are inert: chain reads use only the latest snapshot per contract, Separate observation — do NOT bundleCollector data-quality monitoring gap: run tracking counts RelatedALP-948 — this issue unblocks its production behavior; the feature code itself needs no change. |
Clarify in the _build_rows docstring that `source` records the merge ran for the underlying, not per-row Alpaca enrichment — a contract absent from the Alpaca chain keeps NULL quote columns under polygon+alpaca. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
options_contract_snapshotsrow since 2026-04-27 (~30.1M) has NULLbid/ask/last_price/underlying_price— no IVr reference line renders,retrieve_options_chainreturns nothing, premium validation rejects every options proposal, and option marks have no price.data_sources/alpaca/options.pyfetches the full chain on Alpaca's free indicative options feed (_OPTIONS_FEEDis the single flip point for a future OPRA upgrade) and the underlying spot via one multi-symbol IEX latest-trade request.collect_options_chainsmerges per field at collection time — Polygon wins when present (correct under a future plan upgrade), Alpaca fills the gaps; matched via bare OCC symbol (O:prefix strip).sourceispolygon+alpacawhen the chain fetch succeeded, elsepolygon.broker_adapter/client_factory.py, ALP-841) so a hung connection can't wedge the collector.config/data_sources.yamlalready carried thealpacaprovider entry (Q1 failover) — no config edit, snapshot unchanged.docs/runbooks/services.mddocuments the dependency + degraded mode.Note: two
# type: ignore[no-untyped-call]comments onAPIError(...)construction in tests, mirroring the existing pattern intests/execution/broker_adapter/.Closes https://linear.app/alphamind-jatassi/issue/ALP-949/options-chain-snapshots-carry-no-nbbo-underlying-price-polygon-plan
Test plan
.github/workflows/ci.yml) green on the PR — lint on Linux + full pytest on Windowsruff check,ruff format --check,mypy,lint-imports)🤖 Generated with Claude Code