IBKR brackets, OCA groups, historical bars, and transport hardening - #1364
IBKR brackets, OCA groups, historical bars, and transport hardening#1364eutialia wants to merge 9 commits into
Conversation
IBKR placeOrder with takeProfit/stopLoss now submits parent + children (transmit chain, parentId, ocaType=1) and returns PlaceOrderResult.legs instead of refusing a naked entry. Children sit in the OCA group; the parent does not. Far targets set overridePercentageConstraints; child TIF is GTC so protection survives a GTD/DAY fill. Standalone --oca-group defaults ocaType=1. The bracket always mints its own uta-br-<parentId> group, and placeOrder refuses a caller-supplied ocaGroup outside its try/catch so the combination surfaces as a CONFIG error. Moving a caller group onto the protective children and stripping it from the entry made a take-profit fill cancel every unrelated resting order in that group while the entry lost its intended membership. `placeBracketOrder` registers the parent and child order requests before its try block, and `Promise.allSettled` inside it is their only observer. A synchronous throw from `client.placeOrder` jumps to the catch, leaving those requests to reject unobserved at their own 10s timeout -- an unhandled rejection, which ends the UTA process under Node's default. Every leg request is now observed at registration. Hardening found while testing the bracket on a live gateway: - Refuse an attached TP/SL on a monetary-value (cashQty) entry instead of forwarding the notional to the protective children; TWS rejects a notional STP leg with 10244 and the position would be partly uncovered. - Only unwind the bracket chain when the parent has NOT filled. Cancelling the surviving legs behind a filled entry stripped protection off a real position; that case now returns the entry plus the acknowledged legs with a message naming the gap. - Gate cancel completion on a cancel-confirming status via an `accepts` predicate on requestOrder, so a fill that beat the cancel cannot resolve the cancel request as Cancelled over an open position. - Park an Inactive openOrder/orderStatus for a grace window instead of dropping it. TWS uses Inactive for exchange-closed and precautionary holds with no error(), which turned a live order into a NETWORK timeout. - Recognise Decimal order fields across realms, send an empty totalQuantity for monetary-value orders, and strip IBKR UNSET sentinels from inbound records and numeric Order fields. OrderHelper.scrub passes Date/Map/Set/RegExp/Error/binary through instead of flattening them.
…races transport death An IB Gateway restart left ibkr-tws-c6e6feb7 offline for 90 minutes with `recovering:false`, `consecutiveFailures:0`, and `lastSuccessAt` 256ms AFTER `lastFailureAt` -- no recovery in flight and nothing left to arm one. Two defects combined: - `_onSuccess` never repaired `_currentReach`. A read that entered the broker before the transport-dead event and resolved after it reset the failure counter, cleared `_recovering`, and cancelled the retry timer while reach stayed pinned at 'down' -- and `health` derives 'offline' from reach alone. The account was then unrecoverable: the order-sync poller skips non-healthy accounts and `nudgeRecovery()` no-ops when not already recovering, so only a process restart fixed it. - A reach probe that was in flight when the transport died reported its result anyway, letting a lapsed success clobber reach back up the ladder. `_onSuccess` now promotes reach off 'down' (a completed round-trip proves the transport is up) and only dismantles the recovery machinery when the target reach is actually satisfied, re-arming recovery otherwise. Reach probes in `_connect` and the recovery loop run through `_attemptReachGuarded`, which discards a result whose transport generation lapsed mid-probe. A recovery attempt also only re-arms its retry timer AFTER the probe promise settles, so a probe that never settles silently terminates auto-recovery: `recovering` stays true, no timer is armed, health freezes, and only a process restart repairs it. That is reachable today. `Connection.connect()` resolved on 'connect' and rejected on 'error', but a socket destroyed while still in SYN_SENT emits only 'close' -- and destroying it is exactly what the request bridge does when its own 15s connect deadline expires. The connect promise then stayed pending forever, wedging `EClient.connect` -> `waitForConnect` -> `IbkrBroker.init` -> the UTA recovery loop. Observed against an IB Gateway container recreated mid-login: recovery attempt 1 logged "down", attempt 2 never logged anything for 5+ minutes. Settle `Connection.connect()` on socket close, and make the loop itself self-bounding regardless of what a broker pack does: race each probe against RECOVERY_PROBE_TIMEOUT_MS (45s, above IBKR's 15s handshake + 20s account download), bump the transport epoch so the abandoned probe's answer is discarded, tear the broker down so the next attempt starts clean, and re-schedule.
The clientId-0 session was flapping every 1-3 minutes on a healthy gateway: a single unanswered currentTime probe tore the socket down while another client on the same gateway kept getting replies in ~1ms. Reference implementations use no application probe at all, so the probe has to be a failure detector, not a latency assertion. - Raise the write probe deadline to 10s and the heartbeat probe to 15s, require two consecutive misses (with no inbound traffic in between) before the heartbeat declares death, and retry a missed write probe once before refusing the write. The inbound-traffic exemption is bounded so a half-open socket that keeps pushing account updates cannot hide a dead write path forever (issue TraderAlice#294). - Give liveness probes and open-order sweeps independent deadlines. A probe coalesced onto an in-flight sweep inherited the sweep's deadline. Sweeps are single-flight and every joiner receives the full batch. - currentTime replies are matched FIFO to their waiters, and the "a reply is still owed" credit decays on a grace timer. A reply the gateway never sent used to eat the next probe's reply, starving every later probe. - waitForConnect requires managedAccounts as well as nextValidId; an empty account list no longer resolves the handshake as a non-retryable CONFIG error. - The refusal and markDead reason carry the underlying probe error, so health.lastError shows the actual timeout. - EReader.stop() is called from EClient.reset() so frames queued when a socket died are not handed to the next session's decoder. The probe can only be trusted once the timers it runs on are, so inbound dispatch is batched. Decode and dispatch of every inbound frame happened synchronously in one event-loop turn, so account-update bursts and open/completed-order sweeps starved the timers that UTA uses for request deadlines and liveness probes. The reader now extracts frames into an ordered queue and drains at most 200 per macrotask, yielding with setImmediate between batches. An exception thrown inside a decoder callback also tore the socket down; only a framing failure loses alignment, so a consumer-side handler defect is now reported with safe metadata and skipped. `managedAccounts` assigned `accountId_` before the empty-list guard, so a transient empty push -- TWS re-sends managedAccounts on FA re-login -- reset an id the session had already established, and every later `getAccountId()` returned null under a live account. It returns before touching the field. `EReader.stop()` also claimed EClient reuses one EReader across reconnects. It does not: `EClient.connect` builds a fresh one. The distinction is load-bearing because `stopped` latches and `start()` early-returns on it, so if the claim were true a reconnect would silently never resume reading.
OCA groups. placeOrder accepts an explicit ocaType (CLI flags arrive as
strings and are coerced; the place-order route carries the field), and a
group left at type 0, which TWS silently ignores, becomes
CANCEL_WITH_BLOCK. ocaGroup/ocaType/parentId ride through the
stage-modify-order route and show in the agent-facing order rows, which
previously displayed a grouped stop as standalone. Brokers with no OCA
primitive (Alpaca, CCXT, Longbridge, LeverUp) loud-refuse these fields
before the write instead of dropping them. TRAIL LIMIT is declared among
IBKR's supported order types. The placeOrder tool states that ocaGroup is
mutually exclusive with takeProfit/stopLoss, since a bracket mints its own
group for its exit legs.
`parseOrderLinkId` exists so a malformed parentId is refused rather than
coerced, but `parseInt` stops at the first non-digit: '12abc' parsed to 12
and '1.9' to 1. Both passed the finite/integer check and were sent to the
venue as an order id the caller never named -- the silent link loss the
function was written to remove.
Modify guards. Verified live: IBKR rejects an OCA or parent revision on a
working order with error 10327 when ocaType is sent, and ACCEPTS but
ignores it otherwise, so the ledger reported a linked stop that was
independent at the venue. IbkrBroker.modifyOrder now refuses
ocaGroup/ocaType/parentId before touching the venue and compares every
other requested change against the openOrder echo, reporting fields the
venue ignored. The tool description states the recipe: place the new leg
with the group, then cancel and re-place the old protective order.
StageModifyOrderParams documents the same refusal so the field docs match
refuseOcaRevision.
Historical bars. ibkr-historical.ts owns the reqHistoricalData mapping as
pure functions: a BarParams window becomes endDateTime + durationStr, all
eight BarIntervals map to a native bar size, over-long spans clamp to the
per-size ceiling, and the response is re-bounded locally. Error 162 is
split into empty window, pacing (transient NETWORK, retried), and real
failure. Negative bar volume is treated as unset. Capability quality is
'subscription'.
Bar sessions. BarParams.session ('regular' | 'extended') replaces a
per-call useRTH boolean and is resolved per instrument by
resolveBarSession: stocks and options default to regular hours, futures
and CFDs to continuous with an explicit regular honoured, FX and crypto
are forced continuous. Brokers declare historicalBars.sessions; an
unsupported session falls back to the continuous tape when the broker
declares it, otherwise to the first session it does declare, still marked
forced, instead of assuming 'extended' and handing a regular-only broker
the one session it said it cannot serve. The result rides back as
{ bars, session, forced } through the UTA route and the Alice SDK;
BarService stamps meta.session / meta.sessionForced, and marketSnapshot
accepts session and reports the effective one.
The /api/bars route parses the session query, rejects any other value with
400, and mirrors the meta fields plus the query parameter on the UI client
and demo handler, so the chart can ask for regular or extended bars.
TWS uses Inactive for BOTH a reject and a legitimate hold: an OCA sibling parked behind its partner, a transmit=false bracket parent, an exchange-closed or precautionary hold. A real reject arrives as error() and rejects the pending order promise, so anything that resolves with success: true and Inactive was accepted by the venue. TradingGit.mapOrderStatus mapped Inactive to rejected on the success path, where no error field exists, and UnifiedTradingAccount.sync treated every status other than Submitted/PreSubmitted as terminal. Both wrote a reason-less rejection, and since rejected is terminal the order fell out of order-sync for good. On the live account this produced seven MU target legs reported rejected with no text; all seven were working at IBKR and the eighth came back as IBKR error 201 (15 working orders per side). Held is working: Inactive stays in the pending lane and sync reconciles it. Brokers whose terminal reject is a plain status (Alpaca, CCXT, Longbridge, LeverUp) now report Rejected instead of Inactive, and a rejection with an empty broker message records 'Unknown error' rather than an empty string. "Is this order still working" is then answered the same way everywhere. WORKING_ORDER_STATUSES arrived for order-sync but a second, narrower predicate stayed behind. `PendingCancel` was missing from the set: it means the cancel request has been sent and the venue has not confirmed it, so the order can still fill, yet sync folded it into the terminal `rejected` branch and wrote the same message-less ledger row that `Inactive` was just fixed to avoid. The GitState snapshot still filtered `pendingOrders` on Submitted/PreSubmitted, so a held order stayed in the ledger's pending lane and was chased by sync while being absent from the persisted state and from `pendingOrderCount`. It now reuses the constant. Longbridge has the same shape under a different status. It reports US-equity GTC limit orders as Expired (status 16) between sessions and reverts them to New at the open. Mapping every Expired to a terminal status dropped those orders from order-sync, so a later fill or the still-open order was invisible to UTA. Disambiguate on the order's time in force: Expired with GTC stays Submitted and keeps polling; Day and GTD stay terminal, since a GTD parking has not been observed. Evidence and the disambiguation come from upstream TraderAlice#1125 by FaintGhost.
|
@eutialia is attempting to deploy a commit to the luokerenx4's Team Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
🟡 Changes recommended
There are a few correctness/performance issues in newly added code paths (historical request timing, linkage id validation, and a hot-loop queue operation) that should be addressed before approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR extends OpenAlice’s IBKR adapter and UTA trading/market-data layers to support native bracket orders, OCA semantics, historical bars (with explicit bar-session policy), and improved transport liveness/recovery behavior, while hardening persistence/wire shapes against IBKR “UNSET” sentinels.
Changes:
- Implement IBKR native TP/SL brackets and OCA semantics (including revision guards and safer linkage parsing).
- Add IBKR historical bars via
reqHistoricalData, and propagate bar session (regular/extended) +forcedmetadata through UTA → Alice SDK →/api/bars→ UI. - Improve IBKR transport robustness (bounded probes, heartbeat policy, batching/exception isolation in reader) and correct “working vs terminal” order-state mapping across brokers.
File summaries
| File | Description |
|---|---|
| ui/src/demo/handlers/market.ts | Demo bars handler: adds session parsing and (currently) stamps session metadata in BarMeta. |
| ui/src/api/types.ts | Adds ocaType to PlaceOrderRequest UI API types. |
| ui/src/api/market.ts | Adds BarSession, BarMeta.session/sessionForced, and forwards session in /api/bars query. |
| src/webui/routes/bars.ts | Validates session query param and forwards it to barService.getBars. |
| src/webui/routes/bars.spec.ts | Adds route tests for session forwarding/validation. |
| src/tool/trading.ts | Adds CLI-facing ocaType schema and updates OCA/bracket docs in tool schemas. |
| src/tool/trading.spec.ts | Adds tests for ocaType CLI string coercion and rejection. |
| src/tool/trading-compact.ts | Includes OCA/bracket linkage fields (ocaGroup, ocaType, parentId) in compact order rendering. |
| src/tool/snapshot.ts | Adds session option to snapshot tool and forwards it into snapshot bar requests. |
| src/services/uta-client/UTAAccountSDK.ts | Changes getHistorical to return HistoricalBarsResult (bars + session + forced). |
| src/domain/market-data/bars/types.ts | Adds session fields to bar meta/opts and updates UTA historical account surface type. |
| src/domain/market-data/bars/bar-service.ts | For UTA bar sources, forwards session requests and stamps served session/forced onto meta. |
| src/domain/market-data/bars/bar-service.spec.ts | Updates UTA-path tests to expect HistoricalBarsResult + session metadata behavior. |
| src/domain/analysis/snapshot.ts | Threads session request/served session metadata through snapshot results. |
| services/uta/src/http/routes-trading.ts | Adds ocaType to placeOrder schema and returns full historical result (bars + session + forced). |
| services/uta/src/http/routes-trading-historical.spec.ts | New spec verifying /uta/:id/historical wire shape preserves session/forced. |
| services/uta/src/domain/trading/UnifiedTradingAccount.ts | Adds working-status predicate, bounded recovery probes, session resolution for historical, and OCA/linkage validation. |
| services/uta/src/domain/trading/UnifiedTradingAccount.spec.ts | Adds extensive tests for working-status handling, linkage validation, recovery races, and bar session policy. |
| services/uta/src/domain/trading/OrderHelper.ts | Reworks sentinel scrubbing to deep-remove IBKR UNSET values across numeric fields. |
| services/uta/src/domain/trading/oca.ts | New shared OCA/linkage helpers (ocaType validation, parentId parsing, refusal helpers). |
| services/uta/src/domain/trading/git/TradingGit.ts | Scrubs sentinels in projected operations/results; refines status mapping for held vs rejected. |
| services/uta/src/domain/trading/git/TradingGit.spec.ts | Updates tests for held Inactive handling and sentinel stripping guarantees. |
| services/uta/src/domain/trading/brokers/others/leverup/LeverupBroker.ts | Refuses OCA linkage; maps terminal failures to Rejected (not Inactive). |
| services/uta/src/domain/trading/brokers/longbridge/LongbridgeBroker.ts | Refuses OCA linkage; forwards TIF into order-state mapping. |
| services/uta/src/domain/trading/brokers/longbridge/LongbridgeBroker.spec.ts | Updates status mapping tests including Expired/TIF behavior. |
| services/uta/src/domain/trading/brokers/longbridge/longbridge-contracts.ts | Maps Longbridge statuses to IBKR-style strings without reusing IBKR Inactive semantics. |
| services/uta/src/domain/trading/brokers/ibkr/request-bridge.ts | Adds batch sweeps, FIFO currentTime probes w/ credit decay, Inactive hold grace, and connect readiness requiring managedAccounts. |
| services/uta/src/domain/trading/brokers/ibkr/request-bridge.spec.ts | Adds coverage for probe deadlines, batch sweep concurrency, connect readiness, and Inactive hold behavior. |
| services/uta/src/domain/trading/brokers/ibkr/README.md | Documents bar sessions, bracket orders, OCA revision restrictions, and liveness policy rationale. |
| services/uta/src/domain/trading/brokers/ibkr/IbkrBroker.ts | Implements brackets, modify echo verification, historical bars, and updated liveness/heartbeat policy. |
| services/uta/src/domain/trading/brokers/ibkr/IbkrBroker.spec.ts | Adds tests for bracket behavior, liveness retries, echo verification, and OCA revision refusal. |
| services/uta/src/domain/trading/brokers/ibkr/ibkr-historical.ts | New IBKR historical request mapping + bar decoding + error-162 classification. |
| services/uta/src/domain/trading/brokers/ibkr/ibkr-historical.spec.ts | New test suite for request mapping, decoding, error classification, and broker wiring. |
| services/uta/src/domain/trading/brokers/ibkr/ibkr-bracket.ts | New bracket builder and standalone OCA type defaulting logic. |
| services/uta/src/domain/trading/brokers/ibkr/ibkr-bracket.spec.ts | New unit tests for bracket construction and OCA behaviors. |
| services/uta/src/domain/trading/brokers/ccxt/CcxtBroker.ts | Refuses OCA linkage for CCXT brokers. |
| services/uta/src/domain/trading/brokers/ccxt/ccxt-contracts.ts | Maps terminal CCXT statuses to Rejected (avoids IBKR Inactive semantics). |
| services/uta/src/domain/trading/brokers/alpaca/AlpacaBroker.ts | Refuses OCA linkage; documents that bar session is ignored by Alpaca feed. |
| services/uta/src/domain/trading/brokers/alpaca/AlpacaBroker.spec.ts | Adds tests ensuring OCA/linkage refusal works for Alpaca. |
| services/uta/src/domain/trading/brokers/alpaca/alpaca-contracts.ts | Maps Alpaca terminal statuses to Rejected (not Inactive). |
| services/uta/src/domain/trading/bar-session.ts | New centralized per-instrument bar-session policy resolver. |
| services/uta/src/domain/trading/bar-session.spec.ts | New tests for session resolution + capability fallback rules. |
| packages/uta-protocol/src/types/git.ts | Adds ocaType (and linkage fields) to stage params in the protocol. |
| packages/uta-protocol/src/types/broker.ts | Adds BarSession, HistoricalBarsResult, and session capabilities to the protocol. |
| packages/ibkr/tests/reader-batching.spec.ts | New tests for reader batching/yielding, handler exception isolation, and teardown semantics. |
| packages/ibkr/tests/order-monetary-value.spec.ts | New tests ensuring notional orders don’t leak UNSET sentinel digits to wire encoding. |
| packages/ibkr/tests/comm.spec.ts | Adds tests for Decimal “foreign realm” handling and UNSET suppression. |
| packages/ibkr/src/reader.ts | Batches inbound frame dispatch and adds a stop/reset mechanism. |
| packages/ibkr/src/connection.ts | Ensures connect() settles on terminal socket outcomes (e.g. close without error). |
| packages/ibkr/src/connection.spec.ts | New tests for connect settlement on close/destroy/connect success. |
| packages/ibkr/src/comm.ts | Makes Decimal detection realm-safe (Decimal.isDecimal) for correct UNSET suppression. |
| packages/ibkr/src/client/orders.ts | Uses makeFieldHandleEmpty for totalQuantity to avoid sending sentinel as a quantity. |
| packages/ibkr/src/client/base.ts | Stops reader on reset; isolates consumer handler exceptions from framing failures. |
| docs/uta-live-testing.md | Updates live-testing checklist for bracket legs and IBKR specifics. |
| docs/market-data-architecture.md | Documents session semantics as part of the federated bar meta contract. |
Review details
- Files reviewed: 55/55 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…sage shift() per dispatched frame made a large inbound burst quadratic in the queue length. Advance a head cursor and drop the dispatched prefix once per batch, keeping the per-turn budget, the setImmediate yield, stop() dropping everything queued, and the decoder-error stop.
buildHistoricalRequest ran before enqueueHistorical, so a start-only window kept a durationStr computed for the enqueue-time "now" and under-fetched by however long the pacing queue held the task. Derive the request inside the queued task and re-check liveness there so a queued request cannot transmit on a socket that died while it waited.
IBKR reads parentId 0 as "no parent", so 0 and negative ids were accepted and the bracket linkage vanished silently. Reject them with the same error shape as a malformed id.
The demo /api/bars handler echoed meta.session/sessionForced for any query carrying a session, including vendor sources the real route never sessions.
Summary
Five behavior changes to the IBKR adapter and the UTA layer around it, all found by running OpenAlice against a live IB Gateway for several days. Each commit is one behavior with its tests.
placeOrderwithtakeProfit/stopLossnow submits parent plus children as a transmit chain (parentId,ocaType=1, GTC children so protection survives a Day/GTD fill) and returnsPlaceOrderResult.legs, instead of refusing a naked entry. A monetary-value (cashQty) entry with attached TP/SL is refused, since a notional exit leg is a different share count. A callerocaGroupon a bracket entry is refused; the bracket mints its own group. If the parent fills before a child is acknowledged, the surviving legs are reported instead of cancelled from under a real position. Cancel completion is gated on a cancel-confirming status so a fill that beats the cancel cannot resolve it asCancelled. IBKRUNSETsentinels are scrubbed from inbound records and outbound numeric fields.SYN_SENTwas destroyed by the bridge timeout with no rejection, so recovery waited forever. Every reach probe is now bounded by a transport epoch and a deadline.currentTimetreated as death. The reader now drains at most 200 frames per macrotask and isolates handler exceptions; probes and sweeps have independent deadlines;currentTimereplies are matched FIFO with a decaying credit; the write probe is 10 s with one retry, the heartbeat 45 s/15 s with two consecutive misses required and a bounded inbound-traffic exemption;waitForConnectrequiresmanagedAccountsas well asnextValidId, and an empty push cannot blank an established account id. After deploying this the session held for hours with zero flaps.ocaTypeis accepted at placement (CLI strings coerced, route field added) and a type-0 group becomesCANCEL_WITH_BLOCKsince TWS ignores type 0. Verified live: modifying a working order to addocaGroup/parentIdis answered by IBKR with 10327 whenocaTypeis sent and silently accepted-but-dropped otherwise, somodifyOrdernow refuses those fields before touching the venue and compares every other field against theopenOrderecho, reporting what the venue ignored. Brokers with no OCA primitive refuse the fields instead of dropping them.getHistoricalis implemented for IBKR viareqHistoricalDatawith pure wire mapping (ibkr-historical.ts), per-bar-size duration clamps, and error 162 split into empty window, pacing (retried), and real failure.BarParams.session(regular|extended) replaces the unreleaseduseRTHand is resolved per instrument: stocks and options default to regular hours, futures and CFDs to continuous, FX and crypto forced continuous, with the effective session and aforcedflag stamped on every response through UTA, the Alice SDK,BarService.meta,marketSnapshot, and/api/bars.Inactivefor both a reject and a legitimate hold (OCA sibling, transmit=false parent, exchange-closed). The ledger mapped a broker-acceptedInactiveto a reason-lessrejected, andsynctreated everything butSubmitted/PreSubmittedas terminal. Observed live: seven target legs recorded as rejected with no message, all seven working at IBKR, the eighth answered with IBKR 201 (15 working orders per side).InactiveandPendingCancelnow stay in the pending lane and the snapshot'spendingOrdersuses the same predicate as sync. Adapters that had borrowedInactivefor their terminal rejects (Alpaca, CCXT, Longbridge, LeverUp) now emitRejected; LongbridgeExpiredwith GTC stays working per the evidence in fix(longbridge): keep GTC/GTD orders alive on transient Expired status #1125.Included increments
feat(ibkr): place attached TP/SL as a native TWS bracketfix(uta): keep recovery alive when a stale success or a wedged probe races transport deathfix(ibkr): make transport liveness a coarse failure detectorfeat(uta): IBKR OCA groups, modify guards, and historical barsfix(uta): keep broker-held orders working in the ledgerVerification
Automated:
node scripts/run-tests.mjs --owner uta: 62 files, 1145 tests passednode scripts/run-tests.mjs --package @traderalice/ibkr: 19 files, 117 tests passednode scripts/run-tests.mjs --owner alice: 133 files, 1578 tests passednode scripts/run-tests.mjs --package @traderalice/uta-protocol: passednpx tsc --noEmit,services/uta,packages/uta-protocol,packages/ibkr,cd ui && npx tsc -bpnpm -F @traderalice/uta-broker-ibkr build: broker pack bundle buildsmanagedAccountsre-push, bracket leg promises under a synchronousplaceOrderthrow)Live, against a real IB Gateway (live API port, not paper):
ocaGroupandocaTypeanswered by IBKR with 10327; modify withocaGroupalone accepted and the group silently absent from the open-orders sweep. Both are the behaviors the modify guard now refuses client-side.Inactivetarget legs recorded as rejected with no reason while working at the venue; eighth attempt IBKR 201. Reproduced the ledger mapping in unit tests from those records.SYN_SENTat 19:26Z) from the UTA logs, both reproduced in unit tests.Not run: the opt-in live-paper lane (
OPENALICE_UTA_LIVE_PAPER=1), since the only available account is live. ThegoodTillDateecho comparison has not been exercised with a live GTD modify.Boundary touch
BarParams.useRTHnever shipped, sosessionreplaces it directly.Non-goals
orderStatuspush can complete a pending modify before theopenOrderecho lands; the echo check then reports fields asunverifiedrather than failing. Parking body-less answers would add latency to cancels and needs a live decision.goodTillDatemay be normalized by TWS in the echo; the comparison is left strict until observed._attemptReachGuardedwith the late-success promotion in_noteSuccess; the current rule came from an observed stranding.cashQtybracket refusal still surfaces as{ success: false }rather than a CONFIG error like the OCA refusal.OrderHelper.scrubtreats any integer equal to2**31-1as an IBKR sentinel when walking raw broker payloads.Expiredbetween sessions; only GTC has evidence.