@huskly/ibkr-client 0.26.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -65,12 +65,16 @@ routing, and caching. Public request types are the caller contract: this package
65
65
  use TypeScript and pass values accepted by those types. Provider responses remain untrusted and are
66
66
  validated at runtime. Its broker-neutral account API includes:
67
67
 
68
- - `getAccountBalances()` and `getPositions()` for account state. Account balances include typed
68
+ - `getAccountBalances()` and `getPositions()` for account state. Each position carries its current
69
+ session's broker contract ID for exact follow-up reads. Account balances include typed
69
70
  `margin.total`, `margin.securities`, and `margin.commodities` snapshots with IBKR's available
70
71
  funds, buying power, excess liquidity, cushion, SMA, equity-with-loan, Reg-T, initial- and
71
72
  maintenance-margin, full, look-ahead, and leverage values. Margin values are `null` when IBKR
72
73
  omits or returns an invalid value; numeric zero remains `0`.
73
- - `getQuotes()` and `searchInstruments()` for equity/ETF discovery and quotes.
74
+ - `getQuotes()` and `searchInstruments()` for equity/ETF discovery and quotes. Quote requests accept
75
+ a symbol and an optional broker ID. A broker ID reads that exact contract without symbol
76
+ discovery. A request without one can also resolve a complete OSI option symbol without loading
77
+ its option chain.
74
78
  - `fetchTransactionHistory()` for normalized portfolio transactions.
75
79
  - `fetchOrders()` for normalized live orders, including aggregate `WORKING`
76
80
  matching across IBKR's active order states.
@@ -84,6 +88,8 @@ The reusable `IbkrClient` also exposes typed, read-only strategy data:
84
88
  - `getOptionChain(...)` returns an exact-expiry chain with canonical OSI symbols, conids,
85
89
  bid/ask/mid prices, delta, session volume, and open interest.
86
90
  - `getOptionQuote(...)` resolves and prices one exact contract with the same market-data shape.
91
+ It uses one security-definition request after the per-underlying session search. It does not load
92
+ the complete option chain.
87
93
  - `getOptionContract(conid)` maps a broker conid back to durable OSI identity.
88
94
 
89
95
  ### Broker-neutral derivative discovery
@@ -135,9 +141,11 @@ to `live`, `delayed`, `frozen`, `frozen-delayed`, or `unavailable`. A missing su
135
141
  never reported as live data. All discovery APIs are read-only and do not call preview, order,
136
142
  warning-reply, or cancellation endpoints.
137
143
 
138
- Contract discovery always calls `secdef/search` before `secdef/strikes`, because IBKR keeps
139
- that priming state in the authenticated session. Empty post-prime strikes and incomplete
140
- bid/ask/delta snapshots throw instead of looking like a valid chain with no candidates.
144
+ Full-chain contract discovery always calls `secdef/search` before `secdef/strikes`, because IBKR
145
+ keeps that priming state in the authenticated session. An exact OSI lookup uses the cached
146
+ per-underlying search and one `secdef/info` request. It does not request the strike list. Empty
147
+ post-prime strikes and incomplete bid/ask/delta snapshots throw instead of looking like a valid
148
+ chain with no candidates.
141
149
  Request shaping is resilient by design: option discovery normalizes the requested symbol,
142
150
  applies bounded batching for secondary-definition and market-data calls, and retries read-only
143
151
  requests on transient `429` responses with capped exponential backoff (including `Retry-After`
@@ -1,5 +1,5 @@
1
1
  import type { IbkrOauth1Config } from "./oauthConfig.js";
2
- import type { AccountBalances, ActiveDerivativeOrder, AuthStatus, BrokerAccountOrders, BrokerClient, BrokerInstrument, BrokerInstrumentSearchProjection, BrokerOrdersOptions, BrokerPosition, BrokerQuote, BrokerTransactionHistory, DerivativeContract, DerivativeContractQuery, DerivativeComboExecutionRequest, DerivativeComboReconciliation, DerivativeComboReconciliationRequest, DerivativeComboPreviewRequest, DerivativeComboPreviewResult, DerivativeContingentChildOrderRequest, DerivativeContingentParentOrderRequest, DerivativeDiscoveryClient, DerivativeExecutionClient, DerivativeExecution, DerivativeExecutionQuery, DerivativeMultiOrderResult, DerivativeOrderGraphLookup, DerivativeOrderGraphRequest, DerivativeOrderGraphResult, DerivativeOrderGraphWarningContinuation, DerivativeOrderCancellationResult, DerivativeOrderCancelRequest, DerivativeOrderLifecycle, DerivativeOrderLookup, DerivativeOrderSubmissionResult, DerivativeSingleOrderRequest, DerivativePreviewClient, DerivativeExpiry, DerivativeExpiryQuery, DerivativeQuote, DerivativeReferenceQuote, OptionContract, OptionMarketQuote, OptionQuoteRequest, OptionRight, PriceHistoryBar, PriceHistoryRequest, TradingDiagnostics } from "../types.js";
2
+ import type { AccountBalances, ActiveDerivativeOrder, AuthStatus, BrokerAccountOrders, BrokerClient, BrokerInstrument, BrokerInstrumentSearchProjection, BrokerOrdersOptions, BrokerPosition, BrokerQuote, BrokerQuoteRequest, BrokerTransactionHistory, DerivativeContract, DerivativeContractQuery, DerivativeComboExecutionRequest, DerivativeComboReconciliation, DerivativeComboReconciliationRequest, DerivativeComboPreviewRequest, DerivativeComboPreviewResult, DerivativeContingentChildOrderRequest, DerivativeContingentParentOrderRequest, DerivativeDiscoveryClient, DerivativeExecutionClient, DerivativeExecution, DerivativeExecutionQuery, DerivativeMultiOrderResult, DerivativeOrderGraphLookup, DerivativeOrderGraphRequest, DerivativeOrderGraphResult, DerivativeOrderGraphWarningContinuation, DerivativeOrderCancellationResult, DerivativeOrderCancelRequest, DerivativeOrderLifecycle, DerivativeOrderLookup, DerivativeOrderSubmissionResult, DerivativeSingleOrderRequest, DerivativePreviewClient, DerivativeExpiry, DerivativeExpiryQuery, DerivativeQuote, DerivativeReferenceQuote, OptionContract, OptionMarketQuote, OptionQuoteRequest, OptionRight, PriceHistoryBar, PriceHistoryRequest, TradingDiagnostics } from "../types.js";
3
3
  import { type IbkrRequestSchedulerOptions, type IbkrRequestTelemetry } from "./requestScheduler.js";
4
4
  interface IbkrRequestInput {
5
5
  path: string;
@@ -22,6 +22,8 @@ export declare class IbkrClient implements BrokerClient, DerivativeDiscoveryClie
22
22
  private initPromise?;
23
23
  private accountIdPromise?;
24
24
  private readonly optionDiscovery;
25
+ private readonly optionUnderlyingDiscovery;
26
+ private readonly optionContractResolution;
25
27
  private readonly derivativeDiscovery;
26
28
  private readonly requestScheduler;
27
29
  constructor(config: IbkrOauth1Config, options?: IbkrClientOptions);
@@ -95,7 +97,8 @@ export declare class IbkrClient implements BrokerClient, DerivativeDiscoveryClie
95
97
  /** Return { conid: day P&L }. Snapshots need a warm-up call before data lands. */
96
98
  private fetchDayPnl;
97
99
  private normalizePosition;
98
- getQuotes(symbols: string[]): Promise<Record<string, BrokerQuote>>;
100
+ getQuotes(requests: readonly BrokerQuoteRequest[]): Promise<Record<string, BrokerQuote>>;
101
+ private fetchQuotes;
99
102
  /** Resolve equity/ETF symbols to IBKR contracts via `trsrv/stocks`. */
100
103
  searchInstruments(symbol: string, projection?: BrokerInstrumentSearchProjection): Promise<BrokerInstrument[]>;
101
104
  fetchTransactionHistory(startDate: Date, endDate: Date): Promise<BrokerTransactionHistory[]>;
@@ -191,10 +194,15 @@ export declare class IbkrClient implements BrokerClient, DerivativeDiscoveryClie
191
194
  /** Resolve a conid back into the canonical OSI-bearing option contract. */
192
195
  getOptionContract(conid: number): Promise<OptionContract | null>;
193
196
  private resolveOptionContract;
197
+ /** Resolve one known option directly, without enumerating its month's complete chain. */
198
+ private loadExactOptionContract;
194
199
  private discoverDerivativeMonth;
195
200
  private loadDerivativeContracts;
196
201
  private fetchDerivativeQuotes;
197
202
  private discoverOptions;
203
+ /** Search once per underlying because IBKR uses this call to prime option lookup state. */
204
+ private discoverOptionUnderlying;
205
+ private loadOptionUnderlying;
198
206
  private loadOptionContracts;
199
207
  private fetchOptionQuotes;
200
208
  private historyDays;
@@ -1 +1 @@
1
- {"version":3,"file":"ibkrClient.d.ts","sourceRoot":"","sources":["../../src/ibkr/ibkrClient.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EACV,eAAe,EACf,qBAAqB,EAGrB,UAAU,EACV,mBAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,gCAAgC,EAGhC,mBAAmB,EACnB,cAAc,EACd,WAAW,EAEX,wBAAwB,EAGxB,kBAAkB,EAClB,uBAAuB,EACvB,+BAA+B,EAC/B,6BAA6B,EAC7B,oCAAoC,EACpC,6BAA6B,EAC7B,4BAA4B,EAC5B,qCAAqC,EAErC,sCAAsC,EACtC,yBAAyB,EACzB,yBAAyB,EACzB,mBAAmB,EACnB,wBAAwB,EAGxB,0BAA0B,EAE1B,0BAA0B,EAE1B,2BAA2B,EAC3B,0BAA0B,EAC1B,uCAAuC,EACvC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,qBAAqB,EAErB,+BAA+B,EAC/B,4BAA4B,EAE5B,uBAAuB,EACvB,gBAAgB,EAChB,qBAAqB,EACrB,eAAe,EACf,wBAAwB,EACxB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EAEX,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EACnB,MAAM,aAAa,CAAC;AAkCrB,OAAO,EAIL,KAAK,2BAA2B,EAChC,KAAK,oBAAoB,EAC1B,MAAM,uBAAuB,CAAC;AA6M/B,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,gBAAgB,CAAC,EAAE,IAAI,CACrB,2BAA2B,EAC3B,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,eAAe,GAAG,aAAa,CAC7D,CAAC;IACF,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;CAC5D;AAED;;;;;GAKG;AACH,qBAAa,UACX,YACE,YAAY,EACZ,yBAAyB,EACzB,uBAAuB,EACvB,yBAAyB;IAE3B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAgB;IACpC,OAAO,CAAC,WAAW,CAAC,CAAgB;IACpC,OAAO,CAAC,gBAAgB,CAAC,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAgD;IAChF,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAoD;IACxF,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAuB;gBAE5C,MAAM,EAAE,gBAAgB,EAAE,OAAO,GAAE,iBAAsB;IAcrE,6EAA6E;IAC7E,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IASf,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;IAWpC,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAwBrE,sBAAsB,CAC1B,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,4BAA4B,CAAC;IAsBlC,qBAAqB,CACzB,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,+BAA+B,CAAC;IAgCrC,2BAA2B,CAC/B,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,+BAA+B,CAAC;IA2BrC,gCAAgC,CAAC,OAAO,EAAE;QAC9C,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,sCAAsC,CAAC;QAC/C,KAAK,EAAE,qCAAqC,CAAC;KAC9C,GAAG,OAAO,CAAC,0BAA0B,CAAC;IAgDjC,0BAA0B,CAC9B,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,0BAA0B,CAAC;IAiBhC,sCAAsC,CAAC,KAAK,EAAE;QAClD,YAAY,EAAE,uCAAuC,CAAC;QACtD,SAAS,EAAE,IAAI,CAAC;KACjB,GAAG,OAAO,CAAC,0BAA0B,CAAC;IAyBjC,2BAA2B,CAC/B,KAAK,EAAE,0BAA0B,EACjC,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,0BAA0B,CAAC;IAmLtC,OAAO,CAAC,6BAA6B;IAiCrC,OAAO,CAAC,uBAAuB;YAYjB,mCAAmC;IAsQjD,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,kCAAkC;IAW1C,OAAO,CAAC,+BAA+B;IAmBvC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,yBAAyB;IA4CjC,OAAO,CAAC,kCAAkC;IAe1C,OAAO,CAAC,0BAA0B;IAyBlC,OAAO,CAAC,4BAA4B;IAQpC,OAAO,CAAC,0BAA0B;IAMlC,OAAO,CAAC,8BAA8B;IAoEtC,OAAO,CAAC,oBAAoB;IAS5B,OAAO,CAAC,iCAAiC;IAQzC,OAAO,CAAC,2BAA2B;IAUnC,OAAO,CAAC,6BAA6B;IA6B/B,uBAAuB,CAAC,KAAK,EAAE;QACnC,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,IAAI,CAAC;KACjB,GAAG,OAAO,CAAC,+BAA+B,CAAC;IAYtC,iCAAiC,CAAC,KAAK,EAAE;QAC7C,YAAY,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,mBAAmB,EAAE,MAAM,CAAA;SAAE,CAAC;QAC/D,SAAS,EAAE,IAAI,CAAC;KACjB,GAAG,OAAO,CAAC,0BAA0B,CAAC;IAqBjC,wBAAwB,CAC5B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,wBAAwB,CAAC;IAqB9B,mBAAmB,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAwBpF,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC;IAyD/E,uBAAuB,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IA+BxF,iCAAiC,CACrC,OAAO,EAAE,oCAAoC,GAC5C,OAAO,CAAC,6BAA6B,CAAC;IA0BnC,qBAAqB,CACzB,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,iCAAiC,CAAC;IAmBvC,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAY/B,kBAAkB,IAAI,OAAO,CAAC,eAAe,CAAC;IAmD9C,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAa9D,yEAAyE;YAC3D,iBAAiB;IAc/B,kFAAkF;YACpE,WAAW;IAqBzB,OAAO,CAAC,iBAAiB;IAsBnB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAyCxE,uEAAuE;IACjE,iBAAiB,CACrB,MAAM,EAAE,MAAM,EACd,UAAU,GAAE,gCAAkD,GAC7D,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAiBxB,uBAAuB,CAC3B,SAAS,EAAE,IAAI,EACf,OAAO,EAAE,IAAI,GACZ,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAoChC,WAAW,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IA2B/E,OAAO,CAAC,oBAAoB;IA4C5B,OAAO,CAAC,kBAAkB;IAoC1B,OAAO,CAAC,uBAAuB;IAkB/B;;;;;OAKG;IACH,OAAO,CAAC,oBAAoB;IAO5B;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,mBAAmB;IAI3B,OAAO,CAAC,wBAAwB;IAUhC,OAAO,CAAC,gBAAgB;IAiCxB,OAAO,CAAC,yBAAyB;IA2EjC,OAAO,CAAC,6BAA6B;IAYrC,OAAO,CAAC,iCAAiC;IAczC,OAAO,CAAC,6BAA6B;IAqBrC,OAAO,CAAC,mBAAmB;IA6B3B,OAAO,CAAC,yBAAyB;IA+BjC,OAAO,CAAC,mBAAmB;IAc3B,OAAO,CAAC,gBAAgB;IA2BxB,OAAO,CAAC,iBAAiB;IA2BzB,OAAO,CAAC,wBAAwB;IAkDhC,OAAO,CAAC,mBAAmB;IAuD3B,OAAO,CAAC,yBAAyB;IAWjC,OAAO,CAAC,6BAA6B;IAuIrC,OAAO,CAAC,6BAA6B;IAyErC,OAAO,CAAC,2BAA2B;IA+CnC,OAAO,CAAC,qBAAqB;IA0G7B,OAAO,CAAC,yBAAyB;IAcjC,OAAO,CAAC,wBAAwB;IAgBhC,OAAO,CAAC,wBAAwB;IAsChC,OAAO,CAAC,oBAAoB;IA8B5B,OAAO,CAAC,uBAAuB;IA4B/B,OAAO,CAAC,iCAAiC;IA4DzC,OAAO,CAAC,4BAA4B;IAuCpC,OAAO,CAAC,8BAA8B;IA+EtC,OAAO,CAAC,6BAA6B;IAiGrC,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,4BAA4B;IAsCpC,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,uBAAuB;IAkB/B,OAAO,CAAC,uBAAuB;IAgB/B,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,6BAA6B;IAwBrC,OAAO,CAAC,gCAAgC;IA+IxC,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,0BAA0B;IAIlC,OAAO,CAAC,KAAK;IAKb,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,8BAA8B;IAetC,OAAO,CAAC,wBAAwB;IAQhC,OAAO,CAAC,qBAAqB;IA8B7B,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,YAAY;YAQN,uBAAuB;IAkBrC,OAAO,CAAC,qBAAqB;IAgB7B,OAAO,CAAC,sBAAsB;YAehB,oBAAoB;IAwDlC,qFAAqF;IAC/E,eAAe,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA4B7E,0EAA0E;IACpE,qBAAqB,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAgDtF,wFAAwF;IAClF,sBAAsB,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAoB3F,sFAAsF;IAChF,yBAAyB,CAC7B,KAAK,EAAE,uBAAuB,GAAG;QAAE,KAAK,EAAE,WAAW,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GACtE,OAAO,CAAC,kBAAkB,CAAC;IAkB9B,mFAAmF;IAC7E,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAgBpF,sFAAsF;IAChF,2BAA2B,CAC/B,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAAC,wBAAwB,CAAC;IAkDpC,mFAAmF;IAC7E,iBAAiB,CACrB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,EAAE,CAAC;IAuBpB,2FAA2F;IACrF,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAclF,2EAA2E;IACrE,cAAc,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAMlF,2EAA2E;IACrE,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;YA2BxD,qBAAqB;IAYnC,OAAO,CAAC,uBAAuB;YAkCjB,uBAAuB;YAuGvB,qBAAqB;IA2CnC,OAAO,CAAC,eAAe;YAWT,mBAAmB;YAgEnB,iBAAiB;IAmE/B,OAAO,CAAC,WAAW;YAeL,iBAAiB;IAqB/B,OAAO,CAAC,oBAAoB;IA2C5B,OAAO,CAAC,cAAc;IAsDtB,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,oBAAoB;IAS5B,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,qBAAqB;IAK7B,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,cAAc;IAyBtB,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,8BAA8B;IAOtC,OAAO,CAAC,oBAAoB;IAmC5B,OAAO,CAAC,cAAc;IAUtB,6EAA6E;IAC7E,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzC,yEAAyE;IACzE,SAAS,CAAC,GAAG,IAAI,MAAM;IAIvB,2EAA2E;IAC3E,SAAS,CAAC,MAAM,IAAI,MAAM;IAI1B,OAAO,CAAC,cAAc;IAsDtB,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,cAAc;IAmBtB,iEAAiE;cACjD,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,CAAC,CAAC;IAInE,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,CAAC,CAAC;IAIrD,OAAO,CAAC,oBAAoB;IAI5B,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,oBAAoB;IAiB5B,OAAO,CAAC,gBAAgB;IAsBxB,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,mBAAmB;IAc3B,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,WAAW;CAuBpB"}
1
+ {"version":3,"file":"ibkrClient.d.ts","sourceRoot":"","sources":["../../src/ibkr/ibkrClient.ts"],"names":[],"mappings":"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"}
@@ -96,6 +96,9 @@ function parseRetryAfter(raw) {
96
96
  }
97
97
  return undefined;
98
98
  }
99
+ function isUnknownRecord(input) {
100
+ return typeof input === "object" && input !== null && !Array.isArray(input);
101
+ }
99
102
  function isHeadersLike(input) {
100
103
  return (typeof input === "object" &&
101
104
  input !== null &&
@@ -176,6 +179,8 @@ export class IbkrClient {
176
179
  initPromise;
177
180
  accountIdPromise;
178
181
  optionDiscovery = new Map();
182
+ optionUnderlyingDiscovery = new Map();
183
+ optionContractResolution = new Map();
179
184
  derivativeDiscovery = new Map();
180
185
  requestScheduler;
181
186
  constructor(config, options = {}) {
@@ -1257,7 +1262,14 @@ export class IbkrClient {
1257
1262
  async getPositions(symbol) {
1258
1263
  const accountId = await this.getAccountId();
1259
1264
  const rows = await this.fetchAllPositions(accountId);
1260
- const dayPnl = await this.fetchDayPnl(rows.map((p) => String(p.conid)).filter(Boolean));
1265
+ for (const position of rows) {
1266
+ if (position.conid === undefined ||
1267
+ !Number.isSafeInteger(position.conid) ||
1268
+ position.conid <= 0) {
1269
+ throw new Error(`IBKR returned an invalid position contract id for ${position.contractDesc ?? "-"}`);
1270
+ }
1271
+ }
1272
+ const dayPnl = await this.fetchDayPnl(rows.map((position) => String(position.conid)));
1261
1273
  let positions = rows.map((p) => this.normalizePosition(p, dayPnl));
1262
1274
  if (symbol) {
1263
1275
  const upper = symbol.toUpperCase();
@@ -1307,7 +1319,11 @@ export class IbkrClient {
1307
1319
  const symbol = assetClass === "OPT"
1308
1320
  ? (extractOsiPositionSymbol(contractDescription) ?? contractDescription)
1309
1321
  : contractDescription;
1322
+ if (p.conid === undefined || !Number.isSafeInteger(p.conid) || p.conid <= 0) {
1323
+ throw new Error(`IBKR returned an invalid position contract id for ${symbol}`);
1324
+ }
1310
1325
  return {
1326
+ brokerId: String(p.conid),
1311
1327
  symbol,
1312
1328
  assetType: ASSET_CLASS_LABELS[assetClass] ?? (assetClass || "-"),
1313
1329
  longQuantity: qty > 0 ? qty : 0,
@@ -1316,16 +1332,43 @@ export class IbkrClient {
1316
1332
  ...(p.multiplier === undefined ? {} : { multiplier: p.multiplier }),
1317
1333
  marketPrice: toNumber(p.mktPrice),
1318
1334
  marketValue: toNumber(p.mktValue),
1319
- currentDayProfitLoss: p.conid !== undefined ? (dayPnl.get(p.conid) ?? 0) : 0,
1335
+ currentDayProfitLoss: dayPnl.get(p.conid) ?? 0,
1320
1336
  openProfitLoss: toNumber(p.unrealizedPnl),
1321
1337
  };
1322
1338
  }
1323
- async getQuotes(symbols) {
1324
- const contracts = await Promise.all(symbols.map((symbol) => this.resolveQuoteContract(symbol)));
1325
- const resolvedContracts = contracts.filter((contract) => contract !== undefined);
1326
- if (!resolvedContracts.length)
1339
+ async getQuotes(requests) {
1340
+ const unique = new Map();
1341
+ for (const request of requests) {
1342
+ if (!request.symbol.trim())
1343
+ throw new Error("A quote request symbol is required");
1344
+ const existing = unique.get(request.symbol);
1345
+ if (existing !== undefined && existing.brokerId !== request.brokerId) {
1346
+ throw new Error(`Conflicting IBKR broker contract ids for ${request.symbol}`);
1347
+ }
1348
+ unique.set(request.symbol, request);
1349
+ }
1350
+ const contracts = await Promise.all([...unique.values()].map(async (request) => {
1351
+ if (request.brokerId === undefined)
1352
+ return this.resolveQuoteContract(request.symbol);
1353
+ if (!/^[1-9]\d*$/.test(request.brokerId)) {
1354
+ throw new Error(`Invalid IBKR broker contract id: ${request.brokerId}`);
1355
+ }
1356
+ const conid = Number(request.brokerId);
1357
+ if (!Number.isSafeInteger(conid) || conid <= 0) {
1358
+ throw new Error(`Invalid IBKR broker contract id: ${request.brokerId}`);
1359
+ }
1360
+ return {
1361
+ requestedSymbol: request.symbol,
1362
+ symbol: request.symbol,
1363
+ conid,
1364
+ };
1365
+ }));
1366
+ return this.fetchQuotes(contracts.filter((contract) => contract !== undefined));
1367
+ }
1368
+ async fetchQuotes(contracts) {
1369
+ if (!contracts.length)
1327
1370
  return {};
1328
- const conids = resolvedContracts.map((contract) => contract.conid).join(",");
1371
+ const conids = contracts.map((contract) => contract.conid).join(",");
1329
1372
  const params = { conids, fields: QUOTE_FIELDS };
1330
1373
  await this.req({ path: "iserver/marketdata/snapshot", params }); // warm up
1331
1374
  await this.wait(2000);
@@ -1336,16 +1379,15 @@ export class IbkrClient {
1336
1379
  const snapshotByConid = new Map(snapshots
1337
1380
  .filter((snapshot) => snapshot.conid !== undefined)
1338
1381
  .map((snapshot) => [snapshot.conid, snapshot]));
1339
- const histories = await Promise.all(resolvedContracts.map((contract) => this.fetchQuoteHistory(contract.conid)));
1382
+ const histories = await Promise.all(contracts.map((contract) => this.fetchQuoteHistory(contract.conid)));
1340
1383
  const quotes = {};
1341
- for (const [index, contract] of resolvedContracts.entries()) {
1384
+ for (const [index, contract] of contracts.entries()) {
1342
1385
  const snapshot = snapshotByConid.get(contract.conid);
1343
1386
  if (snapshot === undefined)
1344
1387
  continue;
1345
1388
  const history = histories[index];
1346
1389
  const quote = this.normalizeQuote(contract, snapshot, history);
1347
1390
  quotes[contract.requestedSymbol] = quote;
1348
- quotes[contract.symbol] = quote;
1349
1391
  }
1350
1392
  return quotes;
1351
1393
  }
@@ -3076,11 +3118,87 @@ export class IbkrClient {
3076
3118
  strike: raw.strike,
3077
3119
  });
3078
3120
  }
3079
- async resolveOptionContract(input) {
3080
- const contracts = await this.discoverOptions(input.symbol, monthCode(input.expiry));
3081
- return (contracts.find((contract) => contract.expiry === input.expiry &&
3082
- contract.right === input.right &&
3083
- contract.strike === input.strike) ?? null);
3121
+ resolveOptionContract(input) {
3122
+ const underlying = input.symbol.trim().toUpperCase();
3123
+ const month = monthCode(input.expiry);
3124
+ const key = [underlying, input.expiry, String(input.strike), input.right].join(":");
3125
+ let pending = this.optionContractResolution.get(key);
3126
+ if (!pending) {
3127
+ pending = this.loadExactOptionContract({ ...input, symbol: underlying }, month).then((contract) => {
3128
+ if (contract === null)
3129
+ this.optionContractResolution.delete(key);
3130
+ return contract;
3131
+ }, (error) => {
3132
+ this.optionContractResolution.delete(key);
3133
+ throw error;
3134
+ });
3135
+ this.optionContractResolution.set(key, pending);
3136
+ }
3137
+ return pending;
3138
+ }
3139
+ /** Resolve one known option directly, without enumerating its month's complete chain. */
3140
+ async loadExactOptionContract(input, month) {
3141
+ const underlying = await this.discoverOptionUnderlying(input.symbol);
3142
+ const definitions = await this.req({
3143
+ path: "iserver/secdef/info",
3144
+ params: {
3145
+ conid: String(underlying.conid),
3146
+ sectype: "OPT",
3147
+ month,
3148
+ strike: input.strike,
3149
+ right: input.right,
3150
+ },
3151
+ });
3152
+ if (!Array.isArray(definitions)) {
3153
+ throw new Error(`IBKR returned malformed option definitions for ${input.symbol} ${month}`);
3154
+ }
3155
+ const matches = [];
3156
+ let malformed = false;
3157
+ for (const raw of definitions) {
3158
+ if (!isUnknownRecord(raw)) {
3159
+ malformed = true;
3160
+ continue;
3161
+ }
3162
+ const rawConid = raw["conid"];
3163
+ const rawSymbol = raw["symbol"];
3164
+ const rawMaturityDate = raw["maturityDate"];
3165
+ const rawRight = raw["right"];
3166
+ const rawStrike = raw["strike"];
3167
+ const rawSecType = raw["secType"];
3168
+ let contract;
3169
+ try {
3170
+ contract = normalizeOptionContract({
3171
+ conid: typeof rawConid === "number" ? rawConid : undefined,
3172
+ symbol: typeof rawSymbol === "string" ? rawSymbol : underlying.symbol,
3173
+ maturityDate: typeof rawMaturityDate === "string" ? rawMaturityDate : undefined,
3174
+ right: typeof rawRight === "string" ? rawRight : undefined,
3175
+ strike: typeof rawStrike === "string" || typeof rawStrike === "number" ? rawStrike : undefined,
3176
+ });
3177
+ }
3178
+ catch {
3179
+ contract = null;
3180
+ }
3181
+ if (contract === null ||
3182
+ (rawSecType !== undefined &&
3183
+ (typeof rawSecType !== "string" || rawSecType.toUpperCase() !== "OPT"))) {
3184
+ malformed = true;
3185
+ continue;
3186
+ }
3187
+ if (contract.underlying === input.symbol &&
3188
+ contract.expiry === input.expiry &&
3189
+ contract.right === input.right &&
3190
+ contract.strike === input.strike) {
3191
+ matches.push(contract);
3192
+ }
3193
+ }
3194
+ if (malformed) {
3195
+ throw new Error(`IBKR returned malformed option definitions for ${input.symbol} ${month}`);
3196
+ }
3197
+ const unique = [...new Map(matches.map((contract) => [contract.conid, contract])).values()];
3198
+ if (unique.length > 1) {
3199
+ throw new Error(`IBKR returned ambiguous option definitions for ${input.symbol} ${input.expiry}`);
3200
+ }
3201
+ return unique[0] ?? null;
3084
3202
  }
3085
3203
  discoverDerivativeMonth(underlying, assetClass, month, exchange, right, strike) {
3086
3204
  const normalizedUnderlying = underlying.trim().toUpperCase();
@@ -3223,18 +3341,59 @@ export class IbkrClient {
3223
3341
  }
3224
3342
  return pending;
3225
3343
  }
3226
- async loadOptionContracts(symbol, month) {
3227
- // This search is load-bearing: IBKR silently returns empty strikes unless the current
3344
+ /** Search once per underlying because IBKR uses this call to prime option lookup state. */
3345
+ discoverOptionUnderlying(symbol) {
3346
+ const normalized = symbol.trim().toUpperCase();
3347
+ let pending = this.optionUnderlyingDiscovery.get(normalized);
3348
+ if (!pending) {
3349
+ pending = this.loadOptionUnderlying(normalized).catch((error) => {
3350
+ this.optionUnderlyingDiscovery.delete(normalized);
3351
+ throw error;
3352
+ });
3353
+ this.optionUnderlyingDiscovery.set(normalized, pending);
3354
+ }
3355
+ return pending;
3356
+ }
3357
+ async loadOptionUnderlying(symbol) {
3358
+ // This search is load-bearing: IBKR silently returns empty definitions unless the current
3228
3359
  // session has first searched the underlying.
3229
3360
  const search = await this.req({
3230
3361
  path: "iserver/secdef/search",
3231
3362
  params: { symbol },
3232
3363
  });
3233
- const underlying = search.find((candidate) => candidate.conid !== undefined &&
3234
- candidate.sections?.some((section) => section.secType === "OPT"));
3235
- if (underlying?.conid === undefined) {
3236
- throw new Error(`IBKR did not identify ${symbol} as an optionable underlying`);
3364
+ if (!Array.isArray(search)) {
3365
+ throw new Error(`IBKR returned malformed option underlying results for ${symbol}`);
3237
3366
  }
3367
+ const candidates = search.flatMap((item) => {
3368
+ if (!isUnknownRecord(item))
3369
+ return [];
3370
+ const candidateSymbol = item["symbol"];
3371
+ if (typeof candidateSymbol !== "string" || candidateSymbol.trim().toUpperCase() !== symbol) {
3372
+ return [];
3373
+ }
3374
+ const sections = item["sections"];
3375
+ if (!Array.isArray(sections) ||
3376
+ !sections.some((section) => isUnknownRecord(section) &&
3377
+ typeof section["secType"] === "string" &&
3378
+ section["secType"].trim().toUpperCase() === "OPT")) {
3379
+ return [];
3380
+ }
3381
+ const conid = Number(item["conid"]);
3382
+ return Number.isSafeInteger(conid) && conid > 0 ? [{ conid, symbol }] : [];
3383
+ });
3384
+ const unique = [
3385
+ ...new Map(candidates.map((candidate) => [candidate.conid, candidate])).values(),
3386
+ ];
3387
+ if (unique.length !== 1) {
3388
+ throw new Error(`IBKR option underlying identity is ${unique.length ? "ambiguous" : "missing"} for ${symbol}`);
3389
+ }
3390
+ const [underlying] = unique;
3391
+ if (underlying === undefined)
3392
+ throw new Error(`IBKR lost the selected underlying for ${symbol}`);
3393
+ return underlying;
3394
+ }
3395
+ async loadOptionContracts(symbol, month) {
3396
+ const underlying = await this.discoverOptionUnderlying(symbol);
3238
3397
  const strikes = await this.req({
3239
3398
  path: "iserver/secdef/strikes",
3240
3399
  params: { conid: String(underlying.conid), sectype: "OPT", month },
@@ -3261,7 +3420,7 @@ export class IbkrClient {
3261
3420
  for (const raw of responses.flat()) {
3262
3421
  const contract = normalizeOptionContract({
3263
3422
  conid: raw.conid,
3264
- symbol: raw.symbol ?? underlying.symbol ?? symbol,
3423
+ symbol: raw.symbol ?? underlying.symbol,
3265
3424
  maturityDate: raw.maturityDate,
3266
3425
  right: raw.right,
3267
3426
  strike: raw.strike,