fix(uta,cli,ibkr): live-testing rounds 6+7 — alpaca bracket legs + IBKR acceptance run#335
Merged
Conversation
… order validation, bracket leg tracking Round 6 of the live-testing catalog (docs/uta-live-testing.md), first US-market-open run on the alpaca paper account. Three bugs: 1. CLI gateway silently stripped unknown flags: a typo'd --quantity / --limitPrice staged a quantity-less, price-less LMT order that committed clean. Gateway now parses with z.strictObject (unknown flags 400 loudly, naming the keys). 2. No stage-time required-field validation: the per-orderType rules lived only in tool description prose. UnifiedTradingAccount now refuses at stage time (LMT needs lmtPrice, STP needs auxPrice, totalQuantity XOR cashQty, cashQty MKT-only, TRAIL aux/percent exclusivity; "" treated as absent). 3. Bracket TP/SL legs untracked from birth: Alpaca brackets placed the legs on the venue, but only the parent id entered the ledger — order list, sync poller and cancel were all blind to them, and the held SL leg never appears in the venue's open-orders listing so no later diff could recover it. PlaceOrderResult.legs now carries child ids; TradingGit tracks legs as first-class pending orders (pending scan, known-ids set for the observation pass, sync resolution); compaction surfaces leg ids to the agent. Also: sync-commit log rows attribute per-update symbols (was 'unknown'). Live-verified on alpaca paper: S2 lifecycle, S3 hanger, S4 modify (replaceOrder mints a new id — tracked, no ghost), S5 bracket incl. OCO cancel (one leg cancelled → venue kills both → synced), S6 standalone stop. Account left flat at baseline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…ing, conId quotes, account-cache deltas, secType decode First live-testing-catalog acceptance run against IBKR TWS paper (docs/uta-live-testing.md round 7). Five findings: 1. placeOrder silently ignored its tpsl param (underscore-prefixed) — the ledger would record protection TWS never received. Now refuses loudly until the native parent/child bracket lands (ANG-103). 2. getOpenOrders unwired despite bridge.requestOpenOrders existing — wired (this-clientId scope; manual TWS-UI order observation needs reqAllOpenOrders + permId identity, deferred to Linear). 3. By-conId getQuote hit TWS error 321 — reqMktData refuses to resolve a bare conId even though the wire field is sent. Enrich once via reqContractDetails, cached per conId. Also: reqMarketDataType(3) declared at init + delayed tick types (66-73) mapped in the snapshot collector (paper accounts have no live entitlement). 4. Account-cache delta semantics: TWS pushes position deltas BETWEEN accountDownloadEnd markers; the swap-on-end cache left a filled sell showing as still-held for minutes, dropped zero-quantity (closed) updates entirely, and duplicated rows on repeated updates. Now upsert-by-conId into both the live cache and the pending rebuild buffer; account values apply immediately too. 5. decodeContractProto had an empty `if (cp.secType !== undefined)` body — dropped assignment; every protobuf portfolio row reached the UTA layer with secType ''. Also: dayTradesRemaining omitted when TWS doesn't report the tag (was fabricating 0; -1 = unlimited passes through). Live-verified on TWS paper: S2 lifecycle (fill @292.50 via execution data), S4 modify (same-id semantics — inverse of Alpaca), S6 standalone stop (PreSubmitted tracked, not mis-terminaled), S8 restart survival incl. TWS reconnect, S9 partial close, S12 staging undo. Account left flat at baseline. ANG-101 mechanism confirmed live (broker-layer HKD+USD blind sum) — diagnosis recorded, FX fix out of scope. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r routing
Round-7 follow-up, chased through the market-data entitlement session:
1. _expandAliceIdIfNeeded overlay copied Contract NUMERIC defaults: the
HTTP route wraps bodies with Object.assign(new Contract(), body), and
while string defaults ('') were skipped, conId=0 was copied over the
expanded conId — the broker received an all-empty contract and TWS
rejected with 321. The by-conId quote path was dead in production
while direct broker calls worked (why the probe and prod disagreed).
Numeric Contract fields carry no signal at 0 / UNSET sentinels — skip
them like string defaults.
2. request-bridge error routing swallowed every code >= 2000 as
"informational". The farm-status band is 2100-2200; the blanket also
ate the 10xxx REAL errors (10089 needs-subscription, 10197 competing
live session), so pending market-data requests died as context-free
timeouts. Agents now get the venue's actionable message.
Live-verified: by-conId AAPL quote through the production CLI now
surfaces "IBKR error 10089: ... requires additional subscription" with
the full venue text (entitlement is account-side: live-account US stock
market data subscription + paper sharing — sharing toggle alone gave
FX live ticks but not stocks).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ns, FX families
Design session outcome (with Ame): venue search returns two species —
LEAVES (tradeable contracts) and HUBS (directories: a bond issuer, an FX
currency family, an option chain behind a stock). The old identity layer
only modeled leaves; FX/BOND search rows either mis-resolved (symbol
nativeKey hardcoded STK → "EUR" the stock) or died as unaddressable
noise. Rather than excluding asset classes one by one, the hub/leaf
structure is now first-class and extensible:
- nativeKey grammar (IBKR): conId = canonical leaf for EVERYTHING
tradeable (bonds included — uniqueness doctrine intact); issuer:eXXX =
bond-issuer directory (addressable, loudly NOT tradeable); bare symbol
stays an STK convenience. resolveNativeKey refuses directory keys with
an actionable message instead of silently assuming STK.
- expandContract (protocol + IBroker optional + UTA + route + SDK + tool
+ CLI `contract expand`): issuer hub → individual bonds; underlying →
option parameter grid (expirations × strikes via reqSecDefOptParams);
underlying + expiry/right/strike window → concrete option contracts;
secType=FUT → futures months. Every leaf out carries its own conId
aliceId. No silent truncation (total + hint).
- Search: CASH family rows auto-expand inline into concrete pairs
(".USD" pattern suffix narrows); BOND issuer rows surface as
expandable hubs with the issuer name.
- Fixes en route: getContractDetails SMART/USD defaults poisoned conId
and non-STK queries (EUR.USD@IDEALPRO → error 200) — defaults now
STK-only; getQuote applied defaults BEFORE conId enrichment (same
poison, production-only); bondContractDetails callback was unrouted
(bond chains returned empty); compactContract drops meaningless
strike '0'.
Live-verified on TWS paper: EUR.USD search → conId aliceId → first
production IBKR quote (bid/ask 1.1573@IDEALPRO); IBM issuer hub → 65
bonds, each conId-addressable; AAPL grid (26 expirations × 123 strikes)
→ 5 concrete Jul-2026 calls → order lifecycle smoke (deep LMT placed,
tracked with conId aliceId, cancelled clean). Option quotes refuse
loudly with the venue's entitlement message (10091). Catalog gains S13
(hub/leaf identity) + checklist items.
Known polish gaps (daylight work): bond leaves' coupon/maturity labels
come through empty (decoder field investigation); Quote.last fabricates
'0' when the venue reports none (type change touches all brokers).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…295), mixed-book valuation (#314/ANG-101), dead-connection gate (#294), symbol-first display (#208) Triage pass over open UTA issues, live-verified on the running TWS paper account (mixed HKD+USD book — exactly the configuration the reporters describe): #295 — updateAccountValue ignored the currency argument; multi-currency tag families (CashBalance, NetLiquidationByCurrency, ExchangeRate, …) arrive once per currency + a consolidated BASE line, and last-write-wins left a random currency in the plain key. Values are now stored under key:currency composites; BASE owns the plain key and a per-currency line can never overwrite it. #314 + ANG-101 — IbkrBroker.getAccount blind-summed per-position unrealizedPnL and marketValue across currencies (live numbers: HKD -4767.62 + USD +368.80 reported as USD -4398.82; netLiq inflated $39.6k by counting 46.4k HKD as USD). TWS hands us per-currency ExchangeRate tags — position math is now rate-converted; mixed books prefer TWS's own consolidated NetLiquidation tag (authority + FX correctness) with rate-converted reconstruction as fallback; same- currency books keep the fresher position-derived reconstruction. Live: netLiq 1,085,714 → 1,046,098 (TWS says 1,046,101), uPnL -4,398 → -305.71. #314's duplicate-position complaint was already fixed by tonight's upsert-by-conId. #294 — a silently-dead Gateway socket kept health green because the IBKR account surface is cache-backed (no query ever touches the socket). Added: 45s reqCurrentTime heartbeat that marks the connection dead on timeout; connectionClosed marks dead immediately; cache reads and order paths loud-refuse while dead (orders could previously be accepted and never transmit); init() force-tears-down a half-open socket so the recovery loop's re-init actually reconnects. #208 — orders/positions tables rendered the internal aliceId before the symbol. Symbol first, aliceId moved to the tooltip. 1934 tests green (+10 regression specs). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ot-loop crash The community "option PnL direction is flipped" report, reproduced live via the four-combo matrix (long/short × call/put on AAPL paper) and S14-ified in the catalog: 1. IBKR averageCost is PER CONTRACT (multiplier-baked: 103 for an option bought at 1.03) while marketPrice is per unit. TWS's own pass-through PnL was correct, but every surface that RECOMPUTES (mark − avg) × mult — simulator, any pnlOf consumer — produced ~100x-wrong, sign-inverted numbers: a losing short put showed +10,181. avgCost is normalized to per-unit at the bridge. 2. simulatePriceChange matched positions by bare symbol — option rows sharing the underlying's symbol got RE-MARKED WITH THE STOCK PRICE (observed live: simulatedPrice 275.98 on a 1.15 put, +23,874% "move"). Symbol-level changes now exclude derivative secTypes with a loud note; 'all' scales each row's own mark; the simulated math is multiplier-aware (was also dropping ×100 on contract values). 3. Round-6 regression, found the hard way: getPendingOrderIds read operations[j] for every RESULT row — sync commits store 1 op with N results, so the first multi-update sync commit (two option fills in one poller pass) crashed the UTA process on EVERY boot once persisted in the journal. operations[j] ?? operations[0], plus getOperationSymbol tolerates undefined. This is exactly the workspace-agent-equivalence class: an in-workspace AI placing two orders would have bricked its UTA the same way. Live-verified: short call (+6.52 on 1.0223→0.9571) and short put (−10.99 on 1.0296→1.1395) both sign-correct on every surface; simulator excludes "AAPL OPT 260, AAPL OPT 320" loudly while moving the stock leg −5%; closed positions vanish from the cache immediately (zero-qty removal). Account left flat at baseline. Catalog gains S14. Known gap noted: venue reject REASONS for late rejections (post-submit, caught by sync) don't reach the ledger row — only the status does. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jun 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two catalog rounds on one branch (docs/uta-live-testing.md rounds 6–7).
Round 6 — Alpaca (market open):
--quantitystaged a quantity-less LMT order that committed clean. Nowz.strictObject→ 400 naming the bad keys.stagePlaceOrder(LMT needs lmtPrice, qty XOR cashQty,""= absent, etc.).PlaceOrderResult.legs→ TradingGit tracks legs as first-class pending orders (the held SL leg never appears in venue listings — place-time is the only recovery point). OCO cancel verified live.Round 7 — IBKR TWS paper (first acceptance run):
placeOrder(_tpsl)silently ignored protective legs (okx naked-entry species) — refuses loudly until native bracket lands (ANG-103).getOpenOrderswired (bridge primitive existed, never exposed).reqMarketDataType(3)+ delayed tick types (66-73) for paper entitlements.decodeContractProto: emptyif (cp.secType !== undefined)body — dropped assignment, every protobuf portfolio row had secType ''.Test plan
Boundary touch
Trading: uta-protocol wire types (additive), TradingGit scans, AlpacaBroker, IbkrBroker + request-bridge + @traderalice/ibkr decoder. No migrations, no auth.
🤖 Generated with Claude Code