Thales
← research journal

An internal research document, published verbatim by the automated daily export — not written for an audience, and better for it. All performance discussed is simulated paper trading; nothing here is investment advice.

SKW-2 — the skew capture sends dash-class symbols Alpaca rejects; BRK-B and BF-B have never captured

status: open · raised 2026-09-01 (owner session; observed daily in skew logs since at least 08-28, first flagged 2026-08-29) · class: capture-stream completeness — deliberately a queue row, not a stealth fix, because the remedy changes what a pre-registered stream collects · judgement: owner call · effort ~0.25 pd · horizon: every capture day adds two more permanent per-name notches


Plain-language summary. Every skew snapshot logs skew: BRK-B chain fetch failed: invalid underlying symbol: BRK-B (and the same for BF-B). Same root cause as EXQ-1 (2026-08-26): the internal canon is the dash form, Alpaca wants BRK.B. The trading path got a symbology map at the broker boundary; the capture path (options_skew.py) still sends the dash form, so the two class-share names in the 903-name universe fail their chain fetch daily and have NEVER contributed rows to options_skew.jsonl, options_chains/, or options_iv_term/.

Why this is not just "apply the fix"

The fix is one line (to_alpaca_symbol at the fetch, reverse-map on write — the helpers already exist in execution/alpaca_broker.py). But applying it CHANGES A PRE-REGISTERED CAPTURE STREAM mid-accrual: two names would begin appearing on the fix date, with a permanent pre-fix hole behind them. The moat doctrine treats stream semantics as registered, and the B4 skew activation pin computes cross-name medians and row floors over the stream — so the change must be:

  1. dated in CAPTURES.md (per-name coverage note: BRK-B/BF-B absent <stream start><fix date>, present after — a vendor-reject hole, not a collection change);
  2. checked against the B4 activation criteria for sensitivity (adding 2 of ~900 names to medians is almost certainly immaterial — say so with the arithmetic, don't assume it);
  3. applied to the capture path only (the trading boundary is already fixed).

Test design + kill criterion

Unit: a fake chain client asserting the wire symbol is BRK.B while the persisted row's underlying is BRK-B; revert the map → red. Live check: first post-fix capture day shows both names in the day's jsonl and chains parquet, and the daily chain fetch failed warnings disappear.

Explicitly out of scope

The per-name backfill of the pre-fix hole (no free historical source — the same reason the whole stream is forward-only).


Triage note — 2026-09-07 (annotation only; the body above is untouched)

Premise re-verified from the data, and the row GAINED scope this week. At HEAD e25f80b, src/thales/data/options_skew.py:280 still sends the internal dash canon straight to the wire (OptionChainRequest(underlying_symbol=symbol)), with no to_alpaca_symbol — the helper that exists and is applied at every trading boundary (alpaca_broker.py:31, used at :153, :199, :240, :410). Independently confirmed: zero rows for BRK-B/BF-B (or their dot forms) anywhere in data/options_skew.jsonl across the whole stream, while BRK-B is in the universe (data/universe.csv:684). Neither prerequisite has landed — CAPTURES.md still carries no per-name coverage note for either name.

New scope, not triage's doing: SKW-4's built record explicitly re-routes its own leg 3 — the per-name coverage census — into this row (research/queue/built/skw-4-non-session-rows-served-and-per-name-coverage.md:126-129), naming GHC and NVR at 0 of 62 capture days. So this row now owns four never-captured names, not two, and the CAPTURES.md dated note it asks for covers all of them. Effort is still ~0.25 pd; the census leg adds a little.