You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[IBKR] Use broker NetLiquidation and refresh option position marks from snapshots #314
IBKR account and position reporting can drift from TWS/IB Gateway values:
IbkrBroker.getAccount() reconstructs netLiquidation from cash plus cached position market values whenever positions exist, even when IBKR account summary exposes an authoritative NetLiquidation value.
If requestAccountSummary() is unavailable, the fallback should be the cached account NetLiquidation value before reconstructing from positions.
IbkrBroker.getPositions() currently returns cached updatePortfolio() marks directly. Those marks can freeze after market hours; for option positions we should refresh from snapshot bid/ask when available and recompute market value / unrealized PnL.
RequestBridge.updatePortfolio() should deduplicate repeated callbacks for the same account + contract instead of appending duplicate positions during a batch.
getAccount() prefers account-summary NetLiquidation over reconstructed position values.
getAccount() falls back to cached account values when account summary is unavailable.
Net liquidation is reconstructed only when no broker NetLiquidation is available.
getPositions() refreshes option marks using snapshot quote bid/ask and recalculates market value / unrealized PnL.
getPositions() keeps cached updatePortfolio() values if the snapshot quote fails.
RequestBridge deduplicates repeated updatePortfolio callbacks by account and contract, preserving the newest values.
Suspected locations
services/uta/src/domain/trading/brokers/ibkr/IbkrBroker.ts around getAccount() and getPositions()
services/uta/src/domain/trading/brokers/ibkr/request-bridge.ts around updatePortfolio()
Expected behavior
Account-level NetLiquidation from IBKR wins whenever available.
Cached account-level NetLiquidation is the fallback if summary fetch fails.
Position-derived reconstruction is only a last resort.
Option position marks use snapshot midpoint (bid + ask) / 2 when snapshot data is available, with cached values retained on quote failure.
Repeated portfolio callbacks update the existing cache entry instead of duplicating the position.
Related context
This is adjacent to #295, but distinct: #295 is about account-value keys overwriting across currencies; this issue is about account valuation precedence, stale option marks, and duplicate portfolio rows.
✅ Duplicate updatePortfolio rows: the account cache now upserts by conId (both the live cache and the pending rebuild buffer). Zero-quantity updates REMOVE the position — previously a fully-closed position lingered until the next full download. Deltas also apply to the live cache immediately instead of waiting for the next accountDownloadEnd swap (a filled sell used to show as still-held for minutes).
✅ NetLiquidation precedence: mixed-currency books now prefer TWS's consolidated NetLiquidation tag (it's the FX-correct value); position-derived reconstruction is rate-converted via TWS ExchangeRate tags and only used as fallback. Same-currency books intentionally KEEP the position-derived reconstruction: the account-level tag is server-cached and refreshes slower than position marks, so freshness wins there. Happy to revisit if you have a live case where the reconstruction drifts worse than the staleness.
⏳ Option-mark refresh from snapshot bid/ask: not done. Our paper setup has no market-data entitlement, so a snapshot-refresh path can't be live-verified here yet; it also interacts with delayed-data availability (snapshot + delayed don't combine on TWS). Leaving this issue open, scoped to this remaining item.
Implemented the remaining valuation/option-mark work in #658 (merged to dev, merge commit fa4c2795).
Semantics fixed
NetLiquidation is now one stable account-level concept:
the finite broker-delivered NetLiquidation value wins for both same-currency and mixed-currency books;
local cash + signed, FX-converted position value reconstruction is used only when the broker value is missing or non-finite;
adding a foreign-currency position can no longer silently change which definition the field uses.
The implementation uses the broker value already delivered through the persistent account-update channel. We did not add a fresh reqAccountSummary subscription to every account read: IBKR documents that account-summary updates occur on a three-minute cadence, so repeatedly opening that subscription would not make the value semantically fresher. In live paper acceptance, the UTA value matched a separate reqAccountSummary(NetLiquidation) read exactly. See IBKR account-summary behavior.
The duplicate updatePortfolio upsert/removal behavior was already fixed by #335 and remains covered; this PR does not replace that work.
Option position marks
getPositions() now refreshes only OPT/FOP rows:
resolves the position's canonical IBKR contract;
requests an ordinary snapshot with snapshot=true and regulatorySnapshot=false;
when both bid and ask are positive, returns midpoint (bid + ask) / 2;
recomputes marketValue and unrealizedPnL through the shared multiplier-aware position math;
overlays those values on a returned copy—the broker-owned updatePortfolio cache is not mutated;
retains the cached row if entitlement is missing, bid/ask is incomplete, the market is closed, or the request times out.
Successful marks are cached for 15s; failed-entitlement/incomplete attempts are negatively cached for 60s. Refresh concurrency is capped at 8 and concurrent reads for the same conId share one request.
IBKR documents tickSnapshotEnd at roughly 11 seconds, so ordinary snapshot timeout is now 12.5s. Option marks may resolve earlier as soon as a positive bid+ask pair arrives. regulatorySnapshot=false means this path does not opt the account into paid per-request US regulatory snapshots. References: market-data snapshots, live/delayed market-data types.
Verification performed
Deterministic specs cover:
broker NetLiquidation precedence for same/mixed currency;
missing and non-finite broker-value fallback;
midpoint, multiplier, side, market value, and PnL math;
source-cache immutability;
missing-entitlement fallback and negative caching;
same-conId request coalescing and non-option no-snapshot behavior;
ordinary quotes waiting for tickSnapshotEnd while option marks can resolve on bid+ask.
Live read-only paper acceptance on IB Gateway 10.37.1r / serverVersion 203 confirmed:
account read succeeded and UTA NetLiquidation equaled a separate live account-summary value;
a real option contract resolved successfully;
this account has no usable option market-data permission, so the option mark took the intended cached fallback and left the source cache unchanged;
no order was submitted.
Full repository tests passed (3161 tests); the targeted UTA/IBKR suite passed 151 tests.
Reproduction invitation
The remaining evidence we cannot produce locally is a positive entitled option bid/ask overlay. If you have a paper account with an OPT or FOP position and live or delayed API market data, please retry against dev or a build containing fa4c2795:
Read the position once and note the TWS cached portfolio mark plus current snapshot bid/ask.
Expect the returned UTA mark to equal the positive bid/ask midpoint.
Expect marketValue = quantity × midpoint × multiplier; PnL should use the same multiplier and long/short sign.
Poll again inside 15s; it should reuse the mark rather than create a snapshot loop.
If the account lacks permission, the whole positions read should still succeed with the cached updatePortfolio row.
Useful sanitized details are Gateway/TWS version, API serverVersion, OPT vs FOP, multiplier, entitlement/delayed-data mode, bid/ask, cached mark, and returned mark. Please do not post account IDs or a full position/account payload.
I am leaving this issue open until someone with positive option market-data entitlement confirms the overlay. If nobody can, the implementation, acceptance boundary, and next debugging steps are preserved here.
Activity
fix(ibkr,ui): community-issue sweep — currency-keyed account values (#…
luokerenx4 commentedon Jun 13, 2026
2 of 3 addressed in #335 (merged to master):
updatePortfoliorows: the account cache now upserts by conId (both the live cache and the pending rebuild buffer). Zero-quantity updates REMOVE the position — previously a fully-closed position lingered until the next full download. Deltas also apply to the live cache immediately instead of waiting for the nextaccountDownloadEndswap (a filled sell used to show as still-held for minutes).NetLiquidationtag (it's the FX-correct value); position-derived reconstruction is rate-converted via TWSExchangeRatetags and only used as fallback. Same-currency books intentionally KEEP the position-derived reconstruction: the account-level tag is server-cached and refreshes slower than position marks, so freshness wins there. Happy to revisit if you have a live case where the reconstruction drifts worse than the staleness.luokerenx4 commentedon Jul 17, 2026
Implemented the remaining valuation/option-mark work in #658 (merged to
dev, merge commitfa4c2795).Semantics fixed
NetLiquidationis now one stable account-level concept:NetLiquidationvalue wins for both same-currency and mixed-currency books;cash + signed, FX-converted position valuereconstruction is used only when the broker value is missing or non-finite;The implementation uses the broker value already delivered through the persistent account-update channel. We did not add a fresh
reqAccountSummarysubscription to every account read: IBKR documents that account-summary updates occur on a three-minute cadence, so repeatedly opening that subscription would not make the value semantically fresher. In live paper acceptance, the UTA value matched a separatereqAccountSummary(NetLiquidation)read exactly. See IBKR account-summary behavior.The duplicate
updatePortfolioupsert/removal behavior was already fixed by #335 and remains covered; this PR does not replace that work.Option position marks
getPositions()now refreshes onlyOPT/FOProws:snapshot=trueandregulatorySnapshot=false;(bid + ask) / 2;marketValueandunrealizedPnLthrough the shared multiplier-aware position math;updatePortfoliocache is not mutated;Successful marks are cached for 15s; failed-entitlement/incomplete attempts are negatively cached for 60s. Refresh concurrency is capped at 8 and concurrent reads for the same conId share one request.
IBKR documents
tickSnapshotEndat roughly 11 seconds, so ordinary snapshot timeout is now 12.5s. Option marks may resolve earlier as soon as a positive bid+ask pair arrives.regulatorySnapshot=falsemeans this path does not opt the account into paid per-request US regulatory snapshots. References: market-data snapshots, live/delayed market-data types.Verification performed
Deterministic specs cover:
tickSnapshotEndwhile option marks can resolve on bid+ask.Live read-only paper acceptance on IB Gateway 10.37.1r / serverVersion 203 confirmed:
Full repository tests passed (3161 tests); the targeted UTA/IBKR suite passed 151 tests.
Reproduction invitation
The remaining evidence we cannot produce locally is a positive entitled option bid/ask overlay. If you have a paper account with an
OPTorFOPposition and live or delayed API market data, please retry againstdevor a build containingfa4c2795:marketValue = quantity × midpoint × multiplier; PnL should use the same multiplier and long/short sign.updatePortfoliorow.Useful sanitized details are Gateway/TWS version, API serverVersion,
OPTvsFOP, multiplier, entitlement/delayed-data mode, bid/ask, cached mark, and returned mark. Please do not post account IDs or a full position/account payload.I am leaving this issue open until someone with positive option market-data entitlement confirms the overlay. If nobody can, the implementation, acceptance boundary, and next debugging steps are preserved here.
RainMona commentedon Aug 17, 2026
Community triage (current
dev, Aug 2026)Status: likely fixed in code — still waiting on entitled option confirmation
#658 / related IBKR work on
devincludes:NetLiquidationprecedenceregulatorySnapshot=false) with entitlement fallback + cachingMaintainer paper acceptance could not confirm a positive entitled bid/ask overlay.
Ask: If anyone has paper/live API access with usable OPT/FOP market data, please retest on current
dev:Sanitized Gateway version, OPT vs FOP, bid/ask, returned mark is enough to close. Please do not post account IDs.
— triage note