@huskly/ibkr-client 0.12.0 → 0.13.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 +30 -2
- package/dist/ibkr/ibkrClient.d.ts +24 -1
- package/dist/ibkr/ibkrClient.d.ts.map +1 -1
- package/dist/ibkr/ibkrClient.js +390 -46
- package/dist/ibkr/ibkrClient.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 +88 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,7 +61,9 @@ Optional environment variables:
|
|
|
61
61
|
|
|
62
62
|
`IbkrClient` owns IBKR authentication, requests, raw response types, and
|
|
63
63
|
normalization. Consumers such as huskly-cli provide presentation, command
|
|
64
|
-
routing, and caching.
|
|
64
|
+
routing, and caching. Public request types are the caller contract: this package assumes consumers
|
|
65
|
+
use TypeScript and pass values accepted by those types. Provider responses remain untrusted and are
|
|
66
|
+
validated at runtime. Its broker-neutral account API includes:
|
|
65
67
|
|
|
66
68
|
- `getAccountBalances()` and `getPositions()` for account state.
|
|
67
69
|
- `getQuotes()` and `searchInstruments()` for equity/ETF discovery and quotes.
|
|
@@ -166,13 +168,39 @@ Permission metadata is diagnostic only; the What-If response remains authoritati
|
|
|
166
168
|
already enforced their own reviewed-preview workflow. It does not persist previews or decide
|
|
167
169
|
whether live execution is allowed.
|
|
168
170
|
|
|
171
|
+
Submission rejection is authoritative only when IBKR returns its documented top-level error object
|
|
172
|
+
with a meaningful message, code, or failure status. Array-contained or non-diagnostic error fields
|
|
173
|
+
return `recovery_required`, because they do not prove that every submitted ticket failed.
|
|
174
|
+
|
|
175
|
+
- `submitDerivativeSingleOrder(...)` places one single-leg LIMIT or STOP option order with exact
|
|
176
|
+
contract, side, quantity, TIF, and session. Standalone orders require a unique client order ID;
|
|
177
|
+
sequentially attached child orders instead require the parent's exact ID and omit their own
|
|
178
|
+
`cOID`, as required by IBKR. LIMIT and STOP requests are discriminated: LIMIT orders require only
|
|
179
|
+
a positive `limit`, while STOP orders require only a positive `stopPrice`. Equity-option (`OPT`)
|
|
180
|
+
orders omit CME-only fields; futures-option (`FOP`) orders require the caller's exact `extOperator`
|
|
181
|
+
and `manualIndicator`. Mixed, multiple, unknown, or malformed response evidence returns
|
|
182
|
+
`recovery_required`; pending-cancel acknowledgements also require recovery. Callers must reconcile
|
|
183
|
+
the retained broker order IDs before another write.
|
|
169
184
|
- `submitDerivativeCombo(...)` places one atomic combo with the exact legs, signed ratios,
|
|
170
185
|
quantity, price effect, limit, TIF, and session supplied by the caller. The request requires a
|
|
171
186
|
unique client order ID. Futures-option (`FOP`) writes also require the caller's exact CME
|
|
172
187
|
`extOperator` and manual/automated-origin `manualIndicator`; equity-option (`OPT`) writes omit
|
|
173
|
-
both CME-only fields.
|
|
188
|
+
both CME-only fields. As with single orders, ambiguous response evidence returns
|
|
189
|
+
`recovery_required` and must block blind resubmission.
|
|
190
|
+
- `submitDerivativeContingentOrders(...)` places a general parent/child LIMIT-or-STOP pair in one
|
|
191
|
+
bracket request. The parent carries the caller's unique client order ID; the client derives the
|
|
192
|
+
child's `parentId` from it and deliberately omits a child `cOID`. Parent and child must target the
|
|
193
|
+
same account, but may reference the same or different contracts. IBKR does not promise an
|
|
194
|
+
all-or-none response: `accepted` therefore requires exactly two non-failure acknowledgements,
|
|
195
|
+
while mixed, incomplete, pending-cancel, canceled, rejected, unknown, or malformed evidence is
|
|
196
|
+
returned as `recovery_required` with every observed broker order ID retained.
|
|
174
197
|
- `acknowledgeOrderWarning(...)` replies once to an exact broker warning ID. Only documented
|
|
175
198
|
warning message IDs are marked `known`; consumers must stop on unknown warnings.
|
|
199
|
+
- A warning from `submitDerivativeContingentOrders(...)` includes a typed `continuation` containing
|
|
200
|
+
the exact reply ID and parent client ID. Pass that object unchanged to
|
|
201
|
+
`acknowledgeContingentOrderWarning(...)`; its result retains both broker acknowledgements or
|
|
202
|
+
returns all partial evidence as `recovery_required`. Do not route contingent warnings through the
|
|
203
|
+
single-order acknowledgement method.
|
|
176
204
|
- `getDerivativeOrderStatus(...)` uses IBKR's exact order-ID status endpoint so fast terminal
|
|
177
205
|
orders remain visible after live-list eviction. It normalizes pending, working, partial-fill,
|
|
178
206
|
fill, canceled, and rejected lifecycle states with leg ratios and order economics, and fails
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IbkrOauth1Config } from "./oauthConfig.js";
|
|
2
|
-
import type { AccountBalances, AuthStatus, BrokerAccountOrders, BrokerClient, BrokerInstrument, BrokerInstrumentSearchProjection, BrokerOrdersOptions, BrokerPosition, BrokerQuote, BrokerTransactionHistory, DerivativeContract, DerivativeContractQuery, DerivativeComboExecutionRequest, DerivativeComboReconciliation, DerivativeComboReconciliationRequest, DerivativeComboPreviewRequest, DerivativeComboPreviewResult, DerivativeDiscoveryClient, DerivativeExecutionClient, DerivativeExecution, DerivativeExecutionQuery, DerivativeOrderCancellationResult, DerivativeOrderCancelRequest, DerivativeOrderLifecycle, DerivativeOrderLookup, DerivativeOrderSubmissionResult, DerivativePreviewClient, DerivativeExpiry, DerivativeExpiryQuery, DerivativeQuote, DerivativeReferenceQuote, OptionContract, OptionMarketQuote, OptionQuoteRequest, OptionRight, PriceHistoryBar, PriceHistoryRequest, TradingDiagnostics } from "../types.js";
|
|
2
|
+
import type { AccountBalances, AuthStatus, BrokerAccountOrders, BrokerClient, BrokerInstrument, BrokerInstrumentSearchProjection, BrokerOrdersOptions, BrokerPosition, BrokerQuote, BrokerTransactionHistory, DerivativeContract, DerivativeContractQuery, DerivativeComboExecutionRequest, DerivativeComboReconciliation, DerivativeComboReconciliationRequest, DerivativeComboPreviewRequest, DerivativeComboPreviewResult, DerivativeContingentChildOrderRequest, DerivativeContingentParentOrderRequest, DerivativeDiscoveryClient, DerivativeExecutionClient, DerivativeExecution, DerivativeExecutionQuery, DerivativeMultiOrderResult, 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;
|
|
@@ -31,10 +31,23 @@ export declare class IbkrClient implements BrokerClient, DerivativeDiscoveryClie
|
|
|
31
31
|
getTradingDiagnostics(accountId: string): Promise<TradingDiagnostics>;
|
|
32
32
|
previewDerivativeCombo(request: DerivativeComboPreviewRequest): Promise<DerivativeComboPreviewResult>;
|
|
33
33
|
submitDerivativeCombo(request: DerivativeComboExecutionRequest): Promise<DerivativeOrderSubmissionResult>;
|
|
34
|
+
submitDerivativeSingleOrder(request: DerivativeSingleOrderRequest): Promise<DerivativeOrderSubmissionResult>;
|
|
35
|
+
submitDerivativeContingentOrders(request: {
|
|
36
|
+
accountId: string;
|
|
37
|
+
parent: DerivativeContingentParentOrderRequest;
|
|
38
|
+
child: DerivativeContingentChildOrderRequest;
|
|
39
|
+
}): Promise<DerivativeMultiOrderResult>;
|
|
34
40
|
acknowledgeOrderWarning(input: {
|
|
35
41
|
replyId: string;
|
|
36
42
|
confirmed: true;
|
|
37
43
|
}): Promise<DerivativeOrderSubmissionResult>;
|
|
44
|
+
acknowledgeContingentOrderWarning(input: {
|
|
45
|
+
continuation: {
|
|
46
|
+
replyId: string;
|
|
47
|
+
parentClientOrderId: string;
|
|
48
|
+
};
|
|
49
|
+
confirmed: true;
|
|
50
|
+
}): Promise<DerivativeMultiOrderResult>;
|
|
38
51
|
getDerivativeOrderStatus(accountId: string, orderId: string): Promise<DerivativeOrderLifecycle>;
|
|
39
52
|
findDerivativeOrder(input: DerivativeOrderLookup): Promise<DerivativeOrderLifecycle>;
|
|
40
53
|
getDerivativeExecutions(input: DerivativeExecutionQuery): Promise<DerivativeExecution[]>;
|
|
@@ -54,10 +67,19 @@ export declare class IbkrClient implements BrokerClient, DerivativeDiscoveryClie
|
|
|
54
67
|
fetchTransactionHistory(startDate: Date, endDate: Date): Promise<BrokerTransactionHistory[]>;
|
|
55
68
|
fetchOrders(options: BrokerOrdersOptions): Promise<BrokerAccountOrders[]>;
|
|
56
69
|
private validateComboPreview;
|
|
70
|
+
private validateSingleOrder;
|
|
71
|
+
private validateSingleOrderFields;
|
|
57
72
|
private cmeOperatorMetadata;
|
|
58
73
|
private comboOrderTicket;
|
|
74
|
+
private singleOrderTicket;
|
|
59
75
|
private normalizeOrderSubmission;
|
|
76
|
+
private normalizeMultiOrderSubmission;
|
|
77
|
+
private decodeOrderSubmission;
|
|
78
|
+
private singleOrderRecoveryResult;
|
|
79
|
+
private contingentRecoveryResult;
|
|
80
|
+
private submissionRecoveryReason;
|
|
60
81
|
private normalizeBrokerError;
|
|
82
|
+
private isMeaningfulBrokerError;
|
|
61
83
|
private normalizeDerivativeOrderLifecycle;
|
|
62
84
|
private normalizeDerivativeExecution;
|
|
63
85
|
private parseTradeTime;
|
|
@@ -69,6 +91,7 @@ export declare class IbkrClient implements BrokerClient, DerivativeDiscoveryClie
|
|
|
69
91
|
private round;
|
|
70
92
|
private parseComboLegs;
|
|
71
93
|
private normalizeDerivativeOrderStatus;
|
|
94
|
+
private canonicalIbkrOrderStatus;
|
|
72
95
|
private normalizeComboPreview;
|
|
73
96
|
private whatIfMargin;
|
|
74
97
|
private whatIfNumber;
|
|
@@ -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,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,yBAAyB,EACzB,yBAAyB,EACzB,mBAAmB,EACnB,wBAAwB,EAGxB,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,qBAAqB,EAErB,+BAA+B,EAC/B,uBAAuB,EACvB,gBAAgB,EAChB,qBAAqB,EACrB,eAAe,EACf,wBAAwB,EACxB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,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
|
@@ -268,6 +268,74 @@ export class IbkrClient {
|
|
|
268
268
|
});
|
|
269
269
|
return this.normalizeOrderSubmission(response, request.clientOrderId);
|
|
270
270
|
}
|
|
271
|
+
async submitDerivativeSingleOrder(request) {
|
|
272
|
+
this.validateSingleOrder(request);
|
|
273
|
+
const cmeOperatorMetadata = this.cmeOperatorMetadata(request.contract.assetClass, request);
|
|
274
|
+
const diagnostics = await this.getTradingDiagnostics(request.accountId);
|
|
275
|
+
if (!diagnostics.authenticated || diagnostics.competingSession) {
|
|
276
|
+
throw new Error("IBKR brokerage session is not safely authenticated for submission");
|
|
277
|
+
}
|
|
278
|
+
await this.prepareBrokerageAccount(request.accountId);
|
|
279
|
+
const response = await this.singleAttemptRequest({
|
|
280
|
+
path: `iserver/account/${request.accountId}/orders`,
|
|
281
|
+
method: "POST",
|
|
282
|
+
data: {
|
|
283
|
+
orders: [
|
|
284
|
+
{
|
|
285
|
+
...this.singleOrderTicket(request),
|
|
286
|
+
...(request.clientOrderId !== undefined ? { cOID: request.clientOrderId } : {}),
|
|
287
|
+
...(request.parentId !== undefined ? { parentId: request.parentId } : {}),
|
|
288
|
+
...cmeOperatorMetadata,
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
return this.normalizeOrderSubmission(response, request.clientOrderId ?? null);
|
|
294
|
+
}
|
|
295
|
+
async submitDerivativeContingentOrders(request) {
|
|
296
|
+
const { accountId, parent, child } = request;
|
|
297
|
+
if (!accountId.trim())
|
|
298
|
+
throw new Error("An explicit IBKR account ID is required");
|
|
299
|
+
if (parent.accountId !== accountId || child.accountId !== accountId) {
|
|
300
|
+
throw new Error("Contingent parent and child orders must target the exact same account");
|
|
301
|
+
}
|
|
302
|
+
this.validateSingleOrderFields(parent);
|
|
303
|
+
this.validateSingleOrderFields(child);
|
|
304
|
+
if (typeof parent.clientOrderId !== "string" ||
|
|
305
|
+
!parent.clientOrderId.trim() ||
|
|
306
|
+
parent.clientOrderId.length > 64) {
|
|
307
|
+
throw new Error("Parent client order ID must contain 1 to 64 characters");
|
|
308
|
+
}
|
|
309
|
+
if ("clientOrderId" in child || "parentId" in child) {
|
|
310
|
+
throw new Error("Contingent child identity is derived from the parent order");
|
|
311
|
+
}
|
|
312
|
+
const parentMetadata = this.cmeOperatorMetadata(parent.contract.assetClass, parent);
|
|
313
|
+
const childMetadata = this.cmeOperatorMetadata(child.contract.assetClass, child);
|
|
314
|
+
const diagnostics = await this.getTradingDiagnostics(accountId);
|
|
315
|
+
if (!diagnostics.authenticated || diagnostics.competingSession) {
|
|
316
|
+
throw new Error("IBKR brokerage session is not safely authenticated for submission");
|
|
317
|
+
}
|
|
318
|
+
await this.prepareBrokerageAccount(accountId);
|
|
319
|
+
const response = await this.singleAttemptRequest({
|
|
320
|
+
path: `iserver/account/${accountId}/orders`,
|
|
321
|
+
method: "POST",
|
|
322
|
+
data: {
|
|
323
|
+
orders: [
|
|
324
|
+
{
|
|
325
|
+
...this.singleOrderTicket(parent),
|
|
326
|
+
cOID: parent.clientOrderId,
|
|
327
|
+
...parentMetadata,
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
...this.singleOrderTicket(child),
|
|
331
|
+
parentId: parent.clientOrderId,
|
|
332
|
+
...childMetadata,
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
return this.normalizeMultiOrderSubmission(response, parent.clientOrderId);
|
|
338
|
+
}
|
|
271
339
|
async acknowledgeOrderWarning(input) {
|
|
272
340
|
if (!input.replyId.trim())
|
|
273
341
|
throw new Error("An exact warning reply ID is required");
|
|
@@ -278,6 +346,24 @@ export class IbkrClient {
|
|
|
278
346
|
});
|
|
279
347
|
return this.normalizeOrderSubmission(response, null);
|
|
280
348
|
}
|
|
349
|
+
async acknowledgeContingentOrderWarning(input) {
|
|
350
|
+
const confirmed = input.confirmed;
|
|
351
|
+
if (confirmed !== true) {
|
|
352
|
+
throw new Error("Order warning confirmation must be true");
|
|
353
|
+
}
|
|
354
|
+
if (!input.continuation.replyId.trim()) {
|
|
355
|
+
throw new Error("An exact warning reply ID is required");
|
|
356
|
+
}
|
|
357
|
+
if (!input.continuation.parentClientOrderId.trim()) {
|
|
358
|
+
throw new Error("An exact parent client order ID is required");
|
|
359
|
+
}
|
|
360
|
+
const response = await this.singleAttemptRequest({
|
|
361
|
+
path: `iserver/reply/${encodeURIComponent(input.continuation.replyId)}`,
|
|
362
|
+
method: "POST",
|
|
363
|
+
data: { confirmed: true },
|
|
364
|
+
});
|
|
365
|
+
return this.normalizeMultiOrderSubmission(response, input.continuation.parentClientOrderId);
|
|
366
|
+
}
|
|
281
367
|
async getDerivativeOrderStatus(accountId, orderId) {
|
|
282
368
|
if (!accountId.trim() || !orderId.trim()) {
|
|
283
369
|
throw new Error("Exact account and order IDs are required");
|
|
@@ -616,6 +702,55 @@ export class IbkrClient {
|
|
|
616
702
|
}
|
|
617
703
|
}
|
|
618
704
|
}
|
|
705
|
+
validateSingleOrder(request) {
|
|
706
|
+
this.validateSingleOrderFields(request);
|
|
707
|
+
const identityFields = request;
|
|
708
|
+
const hasClientOrderId = "clientOrderId" in identityFields;
|
|
709
|
+
const hasParentId = "parentId" in identityFields;
|
|
710
|
+
if (hasClientOrderId && typeof identityFields.clientOrderId !== "string") {
|
|
711
|
+
throw new Error("Client order ID must be a string");
|
|
712
|
+
}
|
|
713
|
+
if (hasParentId && typeof identityFields.parentId !== "string") {
|
|
714
|
+
throw new Error("Parent order ID must be a string");
|
|
715
|
+
}
|
|
716
|
+
const clientOrderId = hasClientOrderId ? identityFields.clientOrderId : undefined;
|
|
717
|
+
const parentId = hasParentId ? identityFields.parentId : undefined;
|
|
718
|
+
if (clientOrderId !== undefined && parentId !== undefined) {
|
|
719
|
+
throw new Error("Attached child orders must not include a client order ID");
|
|
720
|
+
}
|
|
721
|
+
const identity = clientOrderId ?? parentId;
|
|
722
|
+
if (!identity?.trim() || identity.length > 64) {
|
|
723
|
+
throw new Error(parentId === undefined
|
|
724
|
+
? "Client order ID must contain 1 to 64 characters"
|
|
725
|
+
: "Parent order ID must contain 1 to 64 characters");
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
validateSingleOrderFields(request) {
|
|
729
|
+
if (!request.accountId.trim())
|
|
730
|
+
throw new Error("An explicit IBKR account ID is required");
|
|
731
|
+
const orderType = request.orderType;
|
|
732
|
+
if (orderType !== "LMT" && orderType !== "STP") {
|
|
733
|
+
throw new Error("Order type must be LMT or STP");
|
|
734
|
+
}
|
|
735
|
+
if (!Number.isSafeInteger(request.quantity) || request.quantity <= 0) {
|
|
736
|
+
throw new Error("Order quantity must be a positive integer");
|
|
737
|
+
}
|
|
738
|
+
if (!Number.isSafeInteger(request.contract.conid) || request.contract.conid <= 0) {
|
|
739
|
+
throw new Error("Order contract has an invalid IBKR conid");
|
|
740
|
+
}
|
|
741
|
+
if (request.orderType === "LMT") {
|
|
742
|
+
const limit = request.limit;
|
|
743
|
+
if (typeof limit !== "number" || !Number.isFinite(limit) || limit <= 0) {
|
|
744
|
+
throw new Error("LIMIT order requires a positive limit price");
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
if (request.orderType === "STP") {
|
|
748
|
+
const stopPrice = request.stopPrice;
|
|
749
|
+
if (typeof stopPrice !== "number" || !Number.isFinite(stopPrice) || stopPrice <= 0) {
|
|
750
|
+
throw new Error("STOP order requires a positive stop price");
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
619
754
|
cmeOperatorMetadata(assetClass, input) {
|
|
620
755
|
if (assetClass === "OPT")
|
|
621
756
|
return {};
|
|
@@ -643,58 +778,250 @@ export class IbkrClient {
|
|
|
643
778
|
outsideRTH: request.session === "OVERNIGHT",
|
|
644
779
|
};
|
|
645
780
|
}
|
|
781
|
+
singleOrderTicket(request) {
|
|
782
|
+
return {
|
|
783
|
+
acctId: request.accountId,
|
|
784
|
+
conid: request.contract.conid,
|
|
785
|
+
orderType: request.orderType,
|
|
786
|
+
side: request.side,
|
|
787
|
+
price: request.orderType === "LMT" ? request.limit : request.stopPrice,
|
|
788
|
+
tif: request.tif,
|
|
789
|
+
quantity: request.quantity,
|
|
790
|
+
outsideRTH: request.session === "OVERNIGHT",
|
|
791
|
+
};
|
|
792
|
+
}
|
|
646
793
|
normalizeOrderSubmission(response, clientOrderId) {
|
|
794
|
+
const decoded = this.decodeOrderSubmission(response);
|
|
795
|
+
const correlatedClientOrderId = decoded.orders.length === 1 ? clientOrderId : null;
|
|
796
|
+
const orders = decoded.orders.map((order) => ({
|
|
797
|
+
...order,
|
|
798
|
+
clientOrderId: correlatedClientOrderId,
|
|
799
|
+
}));
|
|
800
|
+
const hasWarnings = decoded.warnings.length > 0;
|
|
801
|
+
const hasErrors = decoded.errors.length > 0;
|
|
802
|
+
const hasUnknown = decoded.unrecognizedResponses.length > 0;
|
|
803
|
+
const hasPendingCancel = decoded.pendingCancelOrderIds.length > 0;
|
|
804
|
+
if (decoded.warnings.length === 1 && !hasErrors && orders.length === 0 && !hasUnknown) {
|
|
805
|
+
return { state: "warning", warnings: decoded.warnings };
|
|
806
|
+
}
|
|
807
|
+
if (hasErrors &&
|
|
808
|
+
!decoded.responseIsArray &&
|
|
809
|
+
!hasWarnings &&
|
|
810
|
+
orders.length === 0 &&
|
|
811
|
+
!hasUnknown) {
|
|
812
|
+
return {
|
|
813
|
+
state: "rejected",
|
|
814
|
+
reasons: decoded.errors.map(({ message }) => message),
|
|
815
|
+
errors: decoded.errors,
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
if (!hasWarnings && !hasErrors && !hasUnknown && !hasPendingCancel && orders.length === 1) {
|
|
819
|
+
const order = orders[0];
|
|
820
|
+
if (order === undefined)
|
|
821
|
+
throw new Error("Single-order normalization lost order evidence");
|
|
822
|
+
if (order.status === "REJECTED" || order.status === "CANCELED") {
|
|
823
|
+
return {
|
|
824
|
+
state: "rejected",
|
|
825
|
+
reasons: [`Order ${order.orderId} returned terminal status ${order.status}`],
|
|
826
|
+
errors: [],
|
|
827
|
+
orders,
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
if (order.status !== "UNKNOWN") {
|
|
831
|
+
return { state: "accepted", ...order, warnings: [] };
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
return this.singleOrderRecoveryResult(decoded, orders);
|
|
835
|
+
}
|
|
836
|
+
normalizeMultiOrderSubmission(response, parentClientOrderId) {
|
|
837
|
+
const decoded = this.decodeOrderSubmission(response);
|
|
838
|
+
const hasDistinctBrokerOrderIds = decoded.orders.length === 2 &&
|
|
839
|
+
new Set(decoded.orders.map(({ orderId }) => orderId)).size === 2;
|
|
840
|
+
const rolesArePositionallyComplete = decoded.orders.length === 2 &&
|
|
841
|
+
hasDistinctBrokerOrderIds &&
|
|
842
|
+
decoded.warnings.length === 0 &&
|
|
843
|
+
decoded.errors.length === 0 &&
|
|
844
|
+
decoded.unrecognizedResponses.length === 0;
|
|
845
|
+
const orders = decoded.orders.map((order, index) => ({
|
|
846
|
+
...order,
|
|
847
|
+
clientOrderId: rolesArePositionallyComplete && index === 0 ? parentClientOrderId : null,
|
|
848
|
+
role: rolesArePositionallyComplete && index === 0
|
|
849
|
+
? "parent"
|
|
850
|
+
: rolesArePositionallyComplete && index === 1
|
|
851
|
+
? "child"
|
|
852
|
+
: "unknown",
|
|
853
|
+
}));
|
|
854
|
+
const hasWarnings = decoded.warnings.length > 0;
|
|
855
|
+
const hasErrors = decoded.errors.length > 0;
|
|
856
|
+
const hasUnknown = decoded.unrecognizedResponses.length > 0;
|
|
857
|
+
if (decoded.warnings.length === 1 && !hasErrors && orders.length === 0 && !hasUnknown) {
|
|
858
|
+
const warning = decoded.warnings[0];
|
|
859
|
+
if (warning === undefined)
|
|
860
|
+
throw new Error("Contingent warning evidence was lost");
|
|
861
|
+
return {
|
|
862
|
+
state: "warning",
|
|
863
|
+
warnings: decoded.warnings,
|
|
864
|
+
continuation: { replyId: warning.replyId, parentClientOrderId },
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
if (hasErrors &&
|
|
868
|
+
!decoded.responseIsArray &&
|
|
869
|
+
!hasWarnings &&
|
|
870
|
+
orders.length === 0 &&
|
|
871
|
+
!hasUnknown) {
|
|
872
|
+
return {
|
|
873
|
+
state: "rejected",
|
|
874
|
+
parentClientOrderId,
|
|
875
|
+
reasons: decoded.errors.map(({ message }) => message),
|
|
876
|
+
errors: decoded.errors,
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
if (!hasWarnings &&
|
|
880
|
+
!hasErrors &&
|
|
881
|
+
!hasUnknown &&
|
|
882
|
+
decoded.pendingCancelOrderIds.length === 0 &&
|
|
883
|
+
orders.length === 2 &&
|
|
884
|
+
hasDistinctBrokerOrderIds) {
|
|
885
|
+
const [parent, child] = orders;
|
|
886
|
+
if (parent !== undefined && child !== undefined) {
|
|
887
|
+
const terminalFailure = orders.find(({ status }) => status === "REJECTED" || status === "CANCELED");
|
|
888
|
+
const unknownStatus = orders.find(({ status }) => status === "UNKNOWN");
|
|
889
|
+
if (terminalFailure === undefined && unknownStatus === undefined) {
|
|
890
|
+
return { state: "accepted", orders: [parent, child], warnings: [] };
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return this.contingentRecoveryResult(decoded, orders, parentClientOrderId);
|
|
895
|
+
}
|
|
896
|
+
decodeOrderSubmission(response) {
|
|
647
897
|
const items = Array.isArray(response) ? response : [response];
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
898
|
+
const decoded = {
|
|
899
|
+
responseIsArray: Array.isArray(response),
|
|
900
|
+
orders: [],
|
|
901
|
+
pendingCancelOrderIds: [],
|
|
902
|
+
warnings: [],
|
|
903
|
+
errors: [],
|
|
904
|
+
unrecognizedResponses: [],
|
|
905
|
+
};
|
|
906
|
+
for (const item of items) {
|
|
907
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) {
|
|
908
|
+
decoded.unrecognizedResponses.push(item);
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
const record = item;
|
|
912
|
+
let recognized = false;
|
|
913
|
+
const rawWarningId = record["id"];
|
|
914
|
+
if (typeof rawWarningId === "string" && rawWarningId.trim()) {
|
|
915
|
+
const rawMessageIds = record["messageIds"];
|
|
916
|
+
const messageIds = Array.isArray(rawMessageIds)
|
|
917
|
+
? rawMessageIds.filter((value) => typeof value === "string")
|
|
918
|
+
: [];
|
|
919
|
+
decoded.warnings.push({
|
|
920
|
+
replyId: rawWarningId.trim(),
|
|
921
|
+
messages: Array.isArray(record["message"])
|
|
922
|
+
? record["message"].filter((value) => typeof value === "string")
|
|
659
923
|
: [],
|
|
660
924
|
messageIds,
|
|
661
|
-
known:
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
925
|
+
known: Array.isArray(rawMessageIds) &&
|
|
926
|
+
rawMessageIds.length > 0 &&
|
|
927
|
+
rawMessageIds.every((id) => typeof id === "string" && KNOWN_ORDER_WARNING_IDS.has(id)),
|
|
928
|
+
});
|
|
929
|
+
recognized = true;
|
|
930
|
+
}
|
|
931
|
+
else if ("id" in record) {
|
|
932
|
+
decoded.unrecognizedResponses.push({ ...record });
|
|
933
|
+
recognized = true;
|
|
934
|
+
}
|
|
935
|
+
if (record["error"] !== undefined && record["error"] !== null) {
|
|
936
|
+
if (this.isMeaningfulBrokerError(record["error"], record)) {
|
|
937
|
+
decoded.errors.push(this.normalizeBrokerError(record["error"], record));
|
|
938
|
+
}
|
|
939
|
+
else {
|
|
940
|
+
decoded.unrecognizedResponses.push({ ...record });
|
|
941
|
+
}
|
|
942
|
+
recognized = true;
|
|
943
|
+
}
|
|
944
|
+
const hasOrderId = "order_id" in record || "orderId" in record;
|
|
945
|
+
const rawOrderId = record["order_id"] ?? record["orderId"];
|
|
946
|
+
const orderId = typeof rawOrderId === "string" && rawOrderId.trim()
|
|
947
|
+
? rawOrderId.trim()
|
|
948
|
+
: typeof rawOrderId === "number" && Number.isSafeInteger(rawOrderId) && rawOrderId > 0
|
|
949
|
+
? String(rawOrderId)
|
|
950
|
+
: null;
|
|
951
|
+
if (orderId !== null) {
|
|
952
|
+
const orderStatus = record["order_status"] ?? record["orderStatus"];
|
|
953
|
+
if (typeof orderStatus === "string" &&
|
|
954
|
+
this.canonicalIbkrOrderStatus(orderStatus) === "PENDING_CANCEL") {
|
|
955
|
+
decoded.pendingCancelOrderIds.push(orderId);
|
|
956
|
+
}
|
|
957
|
+
decoded.orders.push({
|
|
958
|
+
orderId,
|
|
685
959
|
status: this.normalizeDerivativeOrderStatus(typeof orderStatus === "string" ? orderStatus : undefined, 0, 0),
|
|
686
|
-
clientOrderId,
|
|
687
|
-
|
|
688
|
-
|
|
960
|
+
clientOrderId: null,
|
|
961
|
+
});
|
|
962
|
+
recognized = true;
|
|
689
963
|
}
|
|
964
|
+
else if (hasOrderId) {
|
|
965
|
+
decoded.unrecognizedResponses.push({ ...record });
|
|
966
|
+
recognized = true;
|
|
967
|
+
}
|
|
968
|
+
if (!recognized)
|
|
969
|
+
decoded.unrecognizedResponses.push({ ...record });
|
|
690
970
|
}
|
|
691
|
-
|
|
971
|
+
if (items.length === 0)
|
|
972
|
+
decoded.unrecognizedResponses.push({});
|
|
973
|
+
return decoded;
|
|
974
|
+
}
|
|
975
|
+
singleOrderRecoveryResult(decoded, orders) {
|
|
692
976
|
return {
|
|
693
|
-
state: "
|
|
694
|
-
reasons: [
|
|
695
|
-
|
|
977
|
+
state: "recovery_required",
|
|
978
|
+
reasons: [this.submissionRecoveryReason(decoded, orders.length, 1)],
|
|
979
|
+
orders,
|
|
980
|
+
warnings: decoded.warnings,
|
|
981
|
+
errors: decoded.errors,
|
|
982
|
+
unrecognizedResponses: decoded.unrecognizedResponses,
|
|
696
983
|
};
|
|
697
984
|
}
|
|
985
|
+
contingentRecoveryResult(decoded, orders, parentClientOrderId) {
|
|
986
|
+
return {
|
|
987
|
+
state: "recovery_required",
|
|
988
|
+
parentClientOrderId,
|
|
989
|
+
reasons: [this.submissionRecoveryReason(decoded, orders.length, 2)],
|
|
990
|
+
orders,
|
|
991
|
+
warnings: decoded.warnings,
|
|
992
|
+
errors: decoded.errors,
|
|
993
|
+
unrecognizedResponses: decoded.unrecognizedResponses,
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
submissionRecoveryReason(decoded, orderCount, expectedOrderCount) {
|
|
997
|
+
const pendingCancelOrderId = decoded.pendingCancelOrderIds[0];
|
|
998
|
+
if (pendingCancelOrderId !== undefined) {
|
|
999
|
+
return `Order ${pendingCancelOrderId} has a pending cancellation`;
|
|
1000
|
+
}
|
|
1001
|
+
const terminal = decoded.orders.find(({ status }) => status === "REJECTED" || status === "CANCELED");
|
|
1002
|
+
if (terminal !== undefined) {
|
|
1003
|
+
return `Order ${terminal.orderId} returned terminal status ${terminal.status}`;
|
|
1004
|
+
}
|
|
1005
|
+
if (decoded.orders.some(({ status }) => status === "UNKNOWN")) {
|
|
1006
|
+
return "IBKR returned an order ID with an unknown status";
|
|
1007
|
+
}
|
|
1008
|
+
if (new Set(decoded.orders.map(({ orderId }) => orderId)).size < decoded.orders.length) {
|
|
1009
|
+
return "IBKR returned duplicate broker order IDs";
|
|
1010
|
+
}
|
|
1011
|
+
if (decoded.errors.length > 0 && decoded.warnings.length > 0) {
|
|
1012
|
+
return "IBKR returned both warnings and rejections for one submission";
|
|
1013
|
+
}
|
|
1014
|
+
if (decoded.warnings.length > 1) {
|
|
1015
|
+
return "IBKR returned multiple warning continuations for one submission";
|
|
1016
|
+
}
|
|
1017
|
+
if (orderCount !== expectedOrderCount) {
|
|
1018
|
+
return `IBKR returned ${String(orderCount)} of ${String(expectedOrderCount)} expected order acknowledgements`;
|
|
1019
|
+
}
|
|
1020
|
+
if (decoded.unrecognizedResponses.length > 0) {
|
|
1021
|
+
return "IBKR returned one or more unrecognized order responses";
|
|
1022
|
+
}
|
|
1023
|
+
return "IBKR returned mixed or incomplete order evidence";
|
|
1024
|
+
}
|
|
698
1025
|
normalizeBrokerError(error, response) {
|
|
699
1026
|
const nested = typeof error === "object" && error !== null ? error : undefined;
|
|
700
1027
|
const nestedMessage = nested ? nested.message : undefined;
|
|
@@ -714,6 +1041,20 @@ export class IbkrClient {
|
|
|
714
1041
|
details: response,
|
|
715
1042
|
};
|
|
716
1043
|
}
|
|
1044
|
+
isMeaningfulBrokerError(error, response) {
|
|
1045
|
+
const nested = typeof error === "object" && error !== null ? error : undefined;
|
|
1046
|
+
const nestedRecord = nested;
|
|
1047
|
+
const messages = [error, nestedRecord?.["message"], response["message"]];
|
|
1048
|
+
if (messages.some((value) => typeof value === "string" && value.trim()))
|
|
1049
|
+
return true;
|
|
1050
|
+
const code = nestedRecord?.["code"] ?? response["code"];
|
|
1051
|
+
if ((typeof code === "string" && code.trim()) ||
|
|
1052
|
+
(typeof code === "number" && Number.isFinite(code))) {
|
|
1053
|
+
return true;
|
|
1054
|
+
}
|
|
1055
|
+
const status = nestedRecord?.["statusCode"] ?? nestedRecord?.["status"] ?? response["statusCode"];
|
|
1056
|
+
return typeof status === "number" && Number.isFinite(status) && status >= 400;
|
|
1057
|
+
}
|
|
717
1058
|
normalizeDerivativeOrderLifecycle(accountId, orderId, order) {
|
|
718
1059
|
const quantity = this.firstPositiveNumber(order.total_size, order.totalSize, order.size);
|
|
719
1060
|
const filledQuantity = this.firstNumber(order.cum_fill, order.cumFill, order.filledQuantity, order.filled);
|
|
@@ -954,10 +1295,7 @@ export class IbkrClient {
|
|
|
954
1295
|
});
|
|
955
1296
|
}
|
|
956
1297
|
normalizeDerivativeOrderStatus(value, filledQuantity, remainingQuantity) {
|
|
957
|
-
const status = value
|
|
958
|
-
?.replace(/([a-z])([A-Z])/g, "$1_$2")
|
|
959
|
-
.replace(/\s+/g, "_")
|
|
960
|
-
.toUpperCase();
|
|
1298
|
+
const status = this.canonicalIbkrOrderStatus(value);
|
|
961
1299
|
if (filledQuantity > 0 && remainingQuantity > 0)
|
|
962
1300
|
return "PARTIALLY_FILLED";
|
|
963
1301
|
if (status === "FILLED")
|
|
@@ -972,6 +1310,12 @@ export class IbkrClient {
|
|
|
972
1310
|
return "WORKING";
|
|
973
1311
|
return "UNKNOWN";
|
|
974
1312
|
}
|
|
1313
|
+
canonicalIbkrOrderStatus(value) {
|
|
1314
|
+
return value
|
|
1315
|
+
?.replace(/([a-z])([A-Z])/g, "$1_$2")
|
|
1316
|
+
.replace(/\s+/g, "_")
|
|
1317
|
+
.toUpperCase();
|
|
1318
|
+
}
|
|
975
1319
|
normalizeComboPreview(accountId, diagnostics, response) {
|
|
976
1320
|
const commission = this.whatIfNumber(response.amount?.commission);
|
|
977
1321
|
const initialMargin = this.whatIfMargin(response.initial);
|