@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 +13 -5
- package/dist/ibkr/ibkrClient.d.ts +10 -2
- package/dist/ibkr/ibkrClient.d.ts.map +1 -1
- package/dist/ibkr/ibkrClient.js +181 -22
- package/dist/ibkr/ibkrClient.js.map +1 -1
- package/dist/ibkr/optionContract.d.ts.map +1 -1
- package/dist/ibkr/optionContract.js +2 -0
- package/dist/ibkr/optionContract.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +9 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
139
|
-
that priming state in the authenticated session.
|
|
140
|
-
|
|
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(
|
|
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,
|
|
1
|
+
{"version":3,"file":"ibkrClient.d.ts","sourceRoot":"","sources":["../../src/ibkr/ibkrClient.ts"],"names":[],"mappings":"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"}
|
package/dist/ibkr/ibkrClient.js
CHANGED
|
@@ -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
|
|
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:
|
|
1335
|
+
currentDayProfitLoss: dayPnl.get(p.conid) ?? 0,
|
|
1320
1336
|
openProfitLoss: toNumber(p.unrealizedPnl),
|
|
1321
1337
|
};
|
|
1322
1338
|
}
|
|
1323
|
-
async getQuotes(
|
|
1324
|
-
const
|
|
1325
|
-
const
|
|
1326
|
-
|
|
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 =
|
|
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(
|
|
1382
|
+
const histories = await Promise.all(contracts.map((contract) => this.fetchQuoteHistory(contract.conid)));
|
|
1340
1383
|
const quotes = {};
|
|
1341
|
-
for (const [index, contract] of
|
|
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
|
-
|
|
3080
|
-
const
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
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
|
-
|
|
3227
|
-
|
|
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
|
-
|
|
3234
|
-
|
|
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
|
|
3423
|
+
symbol: raw.symbol ?? underlying.symbol,
|
|
3265
3424
|
maturityDate: raw.maturityDate,
|
|
3266
3425
|
right: raw.right,
|
|
3267
3426
|
strike: raw.strike,
|