EXQ-1 — one invalid symbol dumps the ENTIRE order batch to market orders (live momentum exposed; contaminates the December TCA gate)
status: open · raised panel run 13 (2026-08-24) · class: execution-quality (live trading path) · judgement: YES · effort ~0.5 pd · horizon: live now, 3rd recurrence (2026-08-17 / 08-18 / 08-21)
Plain-language summary for an owner reading one paragraph. Your live
momentum execution is supposed to place marketable-limit orders (it exists
precisely because plain market orders slip ~+101 bps vs the 10 bps cost
model). But whenever a single dash-class ticker such as BRK-B is in the
batch, the one pre-trade quote request for the whole book throws, the code
catches it and returns no quotes for anyone, and every order that day
falls back to a market order. On 2026-08-21 that was all 133 meanrev
orders. It also silently poisons the December go-live TCA criterion (median
slippage ≤ 50 bps), which then measures market-order slippage while believing
it is measuring marketable-limit. Two coupled root causes; a small, local fix.
Mechanism — verified line-by-line at HEAD 05895ab
(a) Whole-batch quote fail-open.
src/thales/execution/alpaca_broker.py:~278-289 issues ONE batched
get_stock_latest_quote(symbol_or_symbols=list(symbols)) inside a single
try/except. Any APIError — e.g. invalid symbol: BRK-B — is caught and the
function returns {} for the entire universe (log line: "quote fetch
failed — falling back to market orders", :~288). Then
src/thales/execution/pipeline.py:~318-319 sets limit_price=None for every
order whose symbol is absent from the (now empty) map → the whole batch
submits MARKET via the retry path. The function docstring (:~273-275)
promises per-symbol omission; the batch-level except breaks that promise.
(b) Missing reverse symbology map — the standing trigger.
src/thales/data/universe.py:~34 normalizes BRK.B → BRK-B one-directionally
for the data side. There is no to_alpaca_symbol and no reverse map
anywhere in src/thales (grep-confirmed). Alpaca lists Berkshire class B as
BRK.B, so the dash-form order both fails on its own
(42210000 asset "BRK-B" not found) and is the recurring trigger for (a).
Every dash-class name in the Russell 1000 (BRK-B, BF-B, LEN-B,
UHAL-B, …) is a candidate trigger.
Blast radius — production is exposed. config/settings.yaml:~267
order_type: "marketable_limit" (live momentum) and config/meanrev.yaml:~153
both route through the shared pipeline.execute_orders / get_latest_quotes
path. So the LIVE momentum book carries the same whole-batch downgrade, not
just the negative-control sleeve.
Severity beyond one day's slippage (added by the adversarial review).
Marketable-limit exists because market orders slipped +101 bps vs the 10 bps
model (pipeline.py:~297-302). One bad symbol silently reverts the entire
book to market for the day, so every fill books market slippage while the
pre-registered TCA gate (settings.yaml tca_median_slippage_bps_max: 50)
believes it is measuring marketable-limit. This contaminates the
December-gate TCA criterion, not merely a single day's execution quality.
Data plan
None — static code + the 08-21 job log already in the repo
(research/2026-08-21_meanrev_clearing_day_churn.md:46-62 log-proves the three
recurrences). Owned.
Test design + kill criterion (pre-registered, negative-controlled)
Unit test with a data-client stub that raises APIError("invalid symbol: X")
on ONE symbol in a multi-symbol request: get_latest_quotes must still return
quotes for every other symbol (deny-asserted / red today, green post-fix).
Second test: to_alpaca_symbol("BRK-B") == "BRK.B" and the response is
reverse-mapped back to BRK-B, both directions. Kill criterion: both
tests green post-fix; revert either the symbology map or the per-symbol
quote-fetch and the corresponding test reds. If, after the fix, a live batch
still degrades to market en masse, the root cause was mis-identified — reopen.
Fix shape (~0.5 pd, PROPOSE-only — this row does not implement it)
to_alpaca_symbol()dash→dot translation at the Alpaca boundary (order submit and the quote request), reverse-mapping response keys back — this alone removes the knownBRK-Btrigger.- Defense in depth in
get_latest_quotes: on anAPIErrornaming an invalid symbol, drop it and retry once (or chunk the request), so one bad name can never zero the batch — this closes the class, not just the known trigger.
Ops note (not a code leg, no kill criterion) — AVB stuck position
Alpaca 40010001 "asset AVB is not active": the broker HOLDS the position but
marks the asset untradeable, so the strategy cannot exit it. Broker-side action
only (manual close, or waiting out the corporate action). Recorded so it is not
mistaken for a code defect; nothing in this row fixes it.
BUILT — 2026-08-26
Outcome: shipped. Both legs of the proposed fix shape, plus one the row did not specify (see below). Full suite green (1238 passed, 1 skipped).
Route — recorded honestly: built on the owner's direct instruction in an
interactive session, NOT via queue/approved/ and the Wednesday implementer.
The approval gate exists to stop the MACHINE self-approving; the owner moving
his own item is not a gate breach, but the row should not read as though it
passed through triage when it did not.
Timing. Momentum's next monthly full rebuild is Tue 2026-09-01 — the
concentrated exposure, since the flagship is quiet between selections. The
universe carries exactly two live triggers (BRK-B, BF-B).
What shipped (src/thales/execution/alpaca_broker.py):
to_alpaca_symbol/from_alpaca_symbol— class-share translation scoped to theAAA-B↔AAA.Bshape, so OCC option symbols and ordinary tickers pass through untouched.- Per-symbol quote resilience —
_fetch_quotes_resilientdrops the symbol the error NAMES (word-bounded match; a bare substring test would drop good names) and retries; if the error names nothing actionable it BISECTS. Bounded at 24 calls, degrading to market orders only for the unpriced remainder — never for the whole book. - Beyond the row's fix shape: the reverse map is applied to every
read-back path too (
get_positions,get_position,get_open_orders,get_order_status,get_order_by_client_id). Without this the fix would have introduced a NEW defect — orders would finally fill, and every class-share position would reconcile as an unknown symbol.
Kill criterion — met, with isolation. Reverting the symbology map reds only the three symbology tests (batch-resilience tests stay green); reverting the per-symbol fetch reds only the batch tests (pure symbology tests stay green). Both verified by running each revert.
Not fixed here (unchanged from the row's ops note): the AVB stuck
position. Alpaca 40010001 asset not active — the broker holds it but marks
it untradeable, so no code change can exit it. As of 2026-08-26 it has failed
7 consecutive sessions (since 08-17), which is long enough that the digest's
!! N FAILED order(s) line has become permanent furniture — a real new
failure would now hide behind it. Broker-side action, owner.
Provenance recorded by triage 2026-09-07 (annotation only; the record above is untouched): shipped in PR #117 (2072f4b, merged 2026-08-26).