Skip to content

IBKR brackets, OCA groups, historical bars, and transport hardening - #1364

Open
eutialia wants to merge 9 commits into
TraderAlice:devfrom
eutialia:feat/ibkr-adapter-hardening
Open

IBKR brackets, OCA groups, historical bars, and transport hardening#1364
eutialia wants to merge 9 commits into
TraderAlice:devfrom
eutialia:feat/ibkr-adapter-hardening

Conversation

@eutialia

@eutialia eutialia commented Sep 5, 2026

Copy link
Copy Markdown

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.

  • Native TWS brackets. placeOrder with takeProfit/stopLoss now submits parent plus children as a transmit chain (parentId, ocaType=1, GTC children so protection survives a Day/GTD fill) and returns PlaceOrderResult.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 caller ocaGroup on 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 as Cancelled. IBKR UNSET sentinels are scrubbed from inbound records and outbound numeric fields.
  • Recovery no longer strands an account. Two races observed live: a probe that succeeded on a socket that was already dead promoted the account to connected and then nothing ever re-probed; and a connect attempt stuck in SYN_SENT was 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.
  • Transport liveness as a coarse failure detector. The clientId-0 session flapped every one to three minutes on a healthy gateway while another client on the same gateway got replies in about 1 ms. Causes: synchronous decode of account-update bursts starving the 3 s/5 s probe timers, probes coalesced onto in-flight order sweeps inheriting their deadline, and one missed currentTime treated as death. The reader now drains at most 200 frames per macrotask and isolates handler exceptions; probes and sweeps have independent deadlines; currentTime replies 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; waitForConnect requires managedAccounts as well as nextValidId, and an empty push cannot blank an established account id. After deploying this the session held for hours with zero flaps.
  • OCA groups, modify guards, historical bars, bar sessions. ocaType is accepted at placement (CLI strings coerced, route field added) and a type-0 group becomes CANCEL_WITH_BLOCK since TWS ignores type 0. Verified live: modifying a working order to add ocaGroup/parentId is answered by IBKR with 10327 when ocaType is sent and silently accepted-but-dropped otherwise, so modifyOrder now refuses those fields before touching the venue and compares every other field against the openOrder echo, reporting what the venue ignored. Brokers with no OCA primitive refuse the fields instead of dropping them. getHistorical is implemented for IBKR via reqHistoricalData with 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 unreleased useRTH and 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 a forced flag stamped on every response through UTA, the Alice SDK, BarService.meta, marketSnapshot, and /api/bars.
  • Held orders stay working in the ledger. TWS uses Inactive for both a reject and a legitimate hold (OCA sibling, transmit=false parent, exchange-closed). The ledger mapped a broker-accepted Inactive to a reason-less rejected, and sync treated everything but Submitted/PreSubmitted as 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). Inactive and PendingCancel now stay in the pending lane and the snapshot's pendingOrders uses the same predicate as sync. Adapters that had borrowed Inactive for their terminal rejects (Alpaca, CCXT, Longbridge, LeverUp) now emit Rejected; Longbridge Expired with 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 bracket
  • fix(uta): keep recovery alive when a stale success or a wedged probe races transport death
  • fix(ibkr): make transport liveness a coarse failure detector
  • feat(uta): IBKR OCA groups, modify guards, and historical bars
  • fix(uta): keep broker-held orders working in the ledger

Verification

Automated:

  • node scripts/run-tests.mjs --owner uta: 62 files, 1145 tests passed
  • node scripts/run-tests.mjs --package @traderalice/ibkr: 19 files, 117 tests passed
  • node scripts/run-tests.mjs --owner alice: 133 files, 1578 tests passed
  • node scripts/run-tests.mjs --package @traderalice/uta-protocol: passed
  • Typechecks clean: root npx tsc --noEmit, services/uta, packages/uta-protocol, packages/ibkr, cd ui && npx tsc -b
  • pnpm -F @traderalice/uta-broker-ibkr build: broker pack bundle builds
  • Every review fix has a spec that was confirmed to fail against the pre-fix code (parentId parsing, working-status predicate, managedAccounts re-push, bracket leg promises under a synchronous placeOrder throw)

Live, against a real IB Gateway (live API port, not paper):

  • Bracket placement returns parent plus legs, children carry the OCA group and GTC, parent outside the group.
  • Modify with ocaGroup and ocaType answered by IBKR with 10327; modify with ocaGroup alone accepted and the group silently absent from the open-orders sweep. Both are the behaviors the modify guard now refuses client-side.
  • Seven Inactive target 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.
  • ClientId-0 flapping every one to three minutes with a second client on the same gateway showing zero slow replies; zero flaps in the hours after deploying the liveness commit.
  • Recovery stranding twice in one day (stale success at 08:11Z, SYN_SENT at 19:26Z) from the UTA logs, both reproduced in unit tests.
  • Historical bars iterated against the live gateway; the duration clamp and negative-volume handling came from real 162 answers.

Not run: the opt-in live-paper lane (OPENALICE_UTA_LIVE_PAPER=1), since the only available account is live. The goodTillDate echo comparison has not been exercised with a live GTD modify.

Boundary touch

  • trading: order writes (bracket chain, OCA placement, modify refusal and echo verification), ledger status mapping, order-sync predicate
  • runtime: UTA recovery loop and IBKR transport liveness
  • packaging: IBKR broker pack contents change; no pack format or catalog change
  • none for auth, credentials, or migrations. BarParams.useRTH never shipped, so session replaces it directly.

Non-goals

  • A routine orderStatus push can complete a pending modify before the openOrder echo lands; the echo check then reports fields as unverified rather than failing. Parking body-less answers would add latency to cancels and needs a live decision.
  • goodTillDate may be normalized by TWS in the echo; the comparison is left strict until observed.
  • Reconciling the transport-epoch discard in _attemptReachGuarded with the late-success promotion in _noteSuccess; the current rule came from an observed stranding.
  • The cashQty bracket refusal still surfaces as { success: false } rather than a CONFIG error like the OCA refusal.
  • OrderHelper.scrub treats any integer equal to 2**31-1 as an IBKR sentinel when walking raw broker payloads.
  • Longbridge GTD orders parked as Expired between sessions; only GTC has evidence.

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.
Copilot AI lite review requested due to automatic review settings September 5, 2026 03:23
@vercel

vercel Bot commented Sep 5, 2026

Copy link
Copy Markdown

@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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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) + forced metadata 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.

Comment thread packages/ibkr/src/reader.ts Outdated
Comment thread services/uta/src/domain/trading/brokers/ibkr/IbkrBroker.ts
Comment thread services/uta/src/domain/trading/oca.ts
Comment thread ui/src/demo/handlers/market.ts Outdated
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants