From 3fefe16f77538efbc9c7ac25330e1cbe0653bcab Mon Sep 17 00:00:00 2001 From: Ronan-Yann Lorin Date: Sat, 16 Nov 2024 10:43:07 +0100 Subject: [PATCH 1/9] cancelOrder bug fix --- README.md | 2 +- src/api-next/api-next.ts | 10 +- src/api/api.ts | 28 ++- src/api/order/orderCancel.ts | 4 +- src/core/io/decoder.ts | 2 +- src/core/io/encoder.ts | 10 +- src/tests/unit/api/order/cancelOrder.test.ts | 172 ++++++++++++++++--- 7 files changed, 188 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index c40a490e..7b3dc177 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ `@stoqey/ib` is an [Interactive Brokers](http://interactivebrokers.com/) TWS (or IB Gateway) Typescript API client library for [Node.js](http://nodejs.org/). It is a port of Interactive Brokers' Java Client Version 10.32.01 ("latest" relased on Oct 9, 2024). -Refer to the [Trader Workstation API](https://interactivebrokers.github.io/tws-api/) for the official documentation and the C#/Java/VB/C++/Python client. +Refer to [IBKRCampus](https://ibkrcampus.com/campus/ibkr-api-page/twsapi-doc/) for the official documentation and the C#/Java/VB/C++/Python client. The module makes a socket connection to TWS (or IB Gateway) using the [net](http://nodejs.org/api/net.html) module and all messages are entirely processed in Typescript. It uses [EventEmitter](http://nodejs.org/api/events.html) to pass the result back to user. diff --git a/src/api-next/api-next.ts b/src/api-next/api-next.ts index 32b8ace7..d17c4322 100644 --- a/src/api-next/api-next.ts +++ b/src/api-next/api-next.ts @@ -2890,11 +2890,7 @@ export class IBApiNext { * @param orderId Specify which order should be cancelled by its identifier. * @param orderCancel Specify the time the order should be cancelled. An empty string will cancel the order immediately. */ - cancelOrder(orderId: number, orderCancelParam?: string | OrderCancel): void { - let orderCancel: OrderCancel; - if (typeof orderCancelParam == "string") - orderCancel = { manualOrderCancelTime: orderCancelParam }; - else orderCancel = orderCancelParam; + cancelOrder(orderId: number, orderCancel?: string | OrderCancel): void { this.api.cancelOrder(orderId, orderCancel); } @@ -2904,8 +2900,8 @@ export class IBApiNext { * * @see [[cancelOrder]] */ - cancelAllOrders(): void { - this.api.reqGlobalCancel(); + cancelAllOrders(orderCancel?: OrderCancel): void { + this.api.reqGlobalCancel(orderCancel); } /** diff --git a/src/api/api.ts b/src/api/api.ts index 4d35d08e..98aa0583 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -381,7 +381,21 @@ export class IBApi extends EventEmitter { * * @see [[placeOrder]], [[reqGlobalCancel]] */ - cancelOrder(orderId: number, orderCancel?: OrderCancel): IBApi { + cancelOrder(orderId: number, orderCancelParam?: string | OrderCancel): IBApi { + let orderCancel: OrderCancel; + if (orderCancelParam == undefined) + orderCancel = { + manualOrderCancelTime: "", + extOperator: "", + manualOrderIndicator: 0, + }; + else if (typeof orderCancelParam == "string") + orderCancel = { + manualOrderCancelTime: orderCancelParam, + extOperator: "", + manualOrderIndicator: 0, + }; + else orderCancel = orderCancelParam; this.controller.schedule(() => this.controller.encoder.cancelOrder(orderId, orderCancel), ); @@ -863,8 +877,16 @@ export class IBApi extends EventEmitter { * * @see [[cancelOrder]] */ - reqGlobalCancel(): IBApi { - this.controller.schedule(() => this.controller.encoder.reqGlobalCancel()); + reqGlobalCancel(orderCancel?: OrderCancel): IBApi { + this.controller.schedule(() => + this.controller.encoder.reqGlobalCancel( + orderCancel || { + manualOrderCancelTime: "", + extOperator: "", + manualOrderIndicator: 0, + }, + ), + ); return this; } diff --git a/src/api/order/orderCancel.ts b/src/api/order/orderCancel.ts index 998be13f..64a58446 100644 --- a/src/api/order/orderCancel.ts +++ b/src/api/order/orderCancel.ts @@ -3,6 +3,6 @@ */ export interface OrderCancel { manualOrderCancelTime: string; - extOperator?: string; - manualOrderIndicator?: number; + extOperator: string; + manualOrderIndicator: number; } diff --git a/src/core/io/decoder.ts b/src/core/io/decoder.ts index c87c3505..70d5cbe5 100644 --- a/src/core/io/decoder.ts +++ b/src/core/io/decoder.ts @@ -464,7 +464,7 @@ export class Decoder { * Read a token from queue and return it as boolean value. */ readBool(): boolean { - return parseInt(this.readStr(), 10) != 0; + return !!parseInt(this.readStr()); } /** diff --git a/src/core/io/encoder.ts b/src/core/io/encoder.ts index 31c5d103..a8582004 100644 --- a/src/core/io/encoder.ts +++ b/src/core/io/encoder.ts @@ -532,7 +532,7 @@ function tagValuesToTokens(tagValues: TagValue[]): unknown[] { /** * Encode a CANCEL_ORDER message to an array of tokens. */ - cancelOrder(orderId: number, orderCancel?: OrderCancel): void { + cancelOrder(orderId: number, orderCancel: OrderCancel): void { if ( this.serverVersion < MIN_SERVER_VER.MANUAL_ORDER_TIME && orderCancel?.manualOrderCancelTime.length @@ -568,7 +568,7 @@ function tagValuesToTokens(tagValues: TagValue[]): unknown[] { tokens.push(orderId); if (this.serverVersion >= MIN_SERVER_VER.MANUAL_ORDER_TIME) - tokens.push(orderCancel?.manualOrderCancelTime); + tokens.push(orderCancel.manualOrderCancelTime); if ( this.serverVersion >= MIN_SERVER_VER.RFQ_FIELDS && @@ -2219,7 +2219,7 @@ function tagValuesToTokens(tagValues: TagValue[]): unknown[] { /** * Encode a REQ_GLOBAL_CANCEL message. */ - reqGlobalCancel(orderCancel?: OrderCancel): void { + reqGlobalCancel(orderCancel: OrderCancel): void { if (this.serverVersion < MIN_SERVER_VER.REQ_GLOBAL_CANCEL) { return this.emitError( "It does not support globalCancel requests.", @@ -2251,8 +2251,8 @@ function tagValuesToTokens(tagValues: TagValue[]): unknown[] { } if (this.serverVersion >= MIN_SERVER_VER.CME_TAGGING_FIELDS) { - tokens.push(orderCancel?.extOperator); - tokens.push(orderCancel?.manualOrderIndicator); + tokens.push(orderCancel.extOperator); + tokens.push(orderCancel.manualOrderIndicator); } this.sendMsg(tokens); diff --git a/src/tests/unit/api/order/cancelOrder.test.ts b/src/tests/unit/api/order/cancelOrder.test.ts index 5e7a4353..9c6f37d8 100644 --- a/src/tests/unit/api/order/cancelOrder.test.ts +++ b/src/tests/unit/api/order/cancelOrder.test.ts @@ -22,6 +22,18 @@ describe("CancelOrder", () => { let ib: IBApi; let clientId = Math.floor(Math.random() * 32766) + 1; // ensure unique client + const contract: Contract = sample_etf; + const order: Order = { + orderType: OrderType.LMT, + action: OrderAction.BUY, + lmtPrice: 3, + totalQuantity: 1, + tif: TimeInForce.DAY, + outsideRth: true, + transmit: true, + goodAfterTime: "20300101-01:01:01", + }; + beforeEach(() => { ib = new IBApi({ host: configuration.ib_host, @@ -42,18 +54,8 @@ describe("CancelOrder", () => { test("cancelOrder", (done) => { let refId: number; - const contract: Contract = sample_etf; - const order: Order = { - orderType: OrderType.LMT, - action: OrderAction.BUY, - lmtPrice: 3, - totalQuantity: 3, - tif: TimeInForce.DAY, - outsideRth: false, - transmit: true, - }; - - let cancelling = false; + let isCancelling = false; + let isDone = false; ib.once(EventName.nextValidId, (orderId: number) => { refId = orderId; ib.placeOrder(refId, contract, order); @@ -74,14 +76,26 @@ describe("CancelOrder", () => { _mktCapPrice, ) => { if (orderId === refId) { - if ( - !cancelling && - [OrderStatus.PreSubmitted, OrderStatus.Submitted].includes( - status as OrderStatus, - ) - ) { - cancelling = true; - ib.cancelOrder(refId); + // console.log(orderId, status); + if (isDone) { + // ignore any message + } else if (!isCancelling) { + // [OrderStatus.PreSubmitted, OrderStatus.Submitted].includes( + // status as OrderStatus, + // ) + isCancelling = true; + ib.cancelOrder(orderId); + } else { + if ( + [ + OrderStatus.PendingCancel, + OrderStatus.ApiCancelled, + OrderStatus.Cancelled, + ].includes(status as OrderStatus) + ) { + isDone = true; + done(); + } } } }, @@ -103,11 +117,127 @@ describe("CancelOrder", () => { } else if ( code == ErrorCode.ORDER_CANCELLED && reqId == refId && - cancelling + isCancelling + ) { + // isDone = true; + logger.info(msg); + // done(); + } else { + isDone = true; + done(msg); + } + } + }, + ); + + ib.connect().reqOpenOrders(); + }); + + test("cancelOrder immediate", (done) => { + let refId: number; + + let isCancelling = false; + let isDone = false; + ib.once(EventName.nextValidId, (orderId: number) => { + refId = orderId; + ib.placeOrder(refId, contract, order); + }) + .on(EventName.orderStatus, (orderId) => { + if (orderId === refId) { + if (isDone) { + // ignore any message + } else if (!isCancelling) { + isCancelling = true; + ib.cancelOrder(orderId, ""); + } + } + }) + .on( + EventName.error, + ( + error: Error, + code: ErrorCode, + reqId: number, + _advancedOrderReject?: unknown, + ) => { + if (reqId === -1) { + logger.info(error.message); + } else { + const msg = `[${reqId}] ${error.message} (Error #${code})`; + if (error.message.includes("Warning:")) { + logger.warn(msg); + } else if ( + code == ErrorCode.ORDER_CANCELLED && + reqId == refId && + isCancelling ) { + isDone = true; logger.info(msg); done(); } else { + isDone = true; + done(msg); + } + } + }, + ); + + ib.connect().reqOpenOrders(); + }); + + test("cancelOrder later", (done) => { + // NOTE: this test is not correctly written, but the API doesn't behave as expected neither + let refId: number; + + let isCancelling = false; + let isDone = false; + ib.once(EventName.nextValidId, (orderId: number) => { + refId = orderId; + ib.placeOrder(refId, contract, order); + }) + .on(EventName.orderStatus, (orderId, status) => { + if (orderId === refId) { + if (isDone) { + // ignore any message + } else if (!isCancelling) { + isCancelling = true; + ib.cancelOrder(orderId, "20260101-23:59:59"); + } else { + if ( + [ + OrderStatus.PendingCancel, + OrderStatus.ApiCancelled, + OrderStatus.Cancelled, + ].includes(status as OrderStatus) + ) { + isDone = true; + done(); + } + } + } + }) + .on( + EventName.error, + ( + error: Error, + code: ErrorCode, + reqId: number, + _advancedOrderReject?: unknown, + ) => { + if (reqId === -1) { + logger.info(error.message); + } else { + const msg = `[${reqId}] ${error.message} (Error #${code})`; + if (error.message.includes("Warning:")) { + logger.warn(msg); + } else if ( + code == ErrorCode.ORDER_CANCELLED && + reqId == refId && + isCancelling + ) { + logger.info(msg); + } else { + isDone = true; done(msg); } } From 8f4f78feab07685608e05ba4877510a5e21dd3a9 Mon Sep 17 00:00:00 2001 From: Ronan-Yann Lorin Date: Sat, 16 Nov 2024 22:57:03 +0100 Subject: [PATCH 2/9] allow for undefined manualOrderIndicator --- src/api/api.ts | 6 +++--- src/core/io/encoder.ts | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/api/api.ts b/src/api/api.ts index 98aa0583..1a4dfc71 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -387,13 +387,13 @@ export class IBApi extends EventEmitter { orderCancel = { manualOrderCancelTime: "", extOperator: "", - manualOrderIndicator: 0, + manualOrderIndicator: undefined, }; else if (typeof orderCancelParam == "string") orderCancel = { manualOrderCancelTime: orderCancelParam, extOperator: "", - manualOrderIndicator: 0, + manualOrderIndicator: undefined, }; else orderCancel = orderCancelParam; this.controller.schedule(() => @@ -883,7 +883,7 @@ export class IBApi extends EventEmitter { orderCancel || { manualOrderCancelTime: "", extOperator: "", - manualOrderIndicator: 0, + manualOrderIndicator: undefined, }, ), ); diff --git a/src/core/io/encoder.ts b/src/core/io/encoder.ts index a8582004..ef9c1397 100644 --- a/src/core/io/encoder.ts +++ b/src/core/io/encoder.ts @@ -535,7 +535,7 @@ function tagValuesToTokens(tagValues: TagValue[]): unknown[] { cancelOrder(orderId: number, orderCancel: OrderCancel): void { if ( this.serverVersion < MIN_SERVER_VER.MANUAL_ORDER_TIME && - orderCancel?.manualOrderCancelTime.length + orderCancel.manualOrderCancelTime.length ) { return this.emitError( "It does not support manual order cancel time attribute", @@ -546,8 +546,8 @@ function tagValuesToTokens(tagValues: TagValue[]): unknown[] { if (this.serverVersion < MIN_SERVER_VER.CME_TAGGING_FIELDS) { if ( - (orderCancel?.extOperator && orderCancel?.extOperator != "") || - orderCancel?.manualOrderIndicator + orderCancel.extOperator?.length || + orderCancel.manualOrderIndicator != undefined ) { return this.emitError( "It does not support ext operator and manual order indicator parameters", @@ -581,7 +581,7 @@ function tagValuesToTokens(tagValues: TagValue[]): unknown[] { if (this.serverVersion >= MIN_SERVER_VER.CME_TAGGING_FIELDS) { tokens.push(orderCancel.extOperator); - tokens.push(orderCancel.manualOrderIndicator); + tokens.push(orderCancel.manualOrderIndicator ?? Integer_MAX_VALUE); } this.sendMsg(tokens); @@ -2230,8 +2230,8 @@ function tagValuesToTokens(tagValues: TagValue[]): unknown[] { if (this.serverVersion < MIN_SERVER_VER.CME_TAGGING_FIELDS) { if ( - (orderCancel?.extOperator && orderCancel?.extOperator != "") || - orderCancel?.manualOrderIndicator + orderCancel.extOperator?.length || + orderCancel.manualOrderIndicator != undefined ) { return this.emitError( "It does not support ext operator and manual order indicator parameters", @@ -2252,7 +2252,7 @@ function tagValuesToTokens(tagValues: TagValue[]): unknown[] { if (this.serverVersion >= MIN_SERVER_VER.CME_TAGGING_FIELDS) { tokens.push(orderCancel.extOperator); - tokens.push(orderCancel.manualOrderIndicator); + tokens.push(orderCancel.manualOrderIndicator ?? Integer_MAX_VALUE); } this.sendMsg(tokens); From 73bcccf864217fc882455906d122a924fb26b983 Mon Sep 17 00:00:00 2001 From: Ronan-Yann Lorin Date: Sat, 16 Nov 2024 23:07:10 +0100 Subject: [PATCH 3/9] Allow undefined for all OrderCancel properties --- src/api/api.ts | 4 ++-- src/api/order/orderCancel.ts | 6 +++--- src/core/io/encoder.ts | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/api/api.ts b/src/api/api.ts index 1a4dfc71..603e6463 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -385,7 +385,7 @@ export class IBApi extends EventEmitter { let orderCancel: OrderCancel; if (orderCancelParam == undefined) orderCancel = { - manualOrderCancelTime: "", + manualOrderCancelTime: undefined, extOperator: "", manualOrderIndicator: undefined, }; @@ -881,7 +881,7 @@ export class IBApi extends EventEmitter { this.controller.schedule(() => this.controller.encoder.reqGlobalCancel( orderCancel || { - manualOrderCancelTime: "", + manualOrderCancelTime: undefined, extOperator: "", manualOrderIndicator: undefined, }, diff --git a/src/api/order/orderCancel.ts b/src/api/order/orderCancel.ts index 64a58446..57b89748 100644 --- a/src/api/order/orderCancel.ts +++ b/src/api/order/orderCancel.ts @@ -2,7 +2,7 @@ * Type describing the parameters of an order cancel */ export interface OrderCancel { - manualOrderCancelTime: string; - extOperator: string; - manualOrderIndicator: number; + manualOrderCancelTime?: string; + extOperator?: string; + manualOrderIndicator?: number; } diff --git a/src/core/io/encoder.ts b/src/core/io/encoder.ts index ef9c1397..7878eee8 100644 --- a/src/core/io/encoder.ts +++ b/src/core/io/encoder.ts @@ -535,7 +535,7 @@ function tagValuesToTokens(tagValues: TagValue[]): unknown[] { cancelOrder(orderId: number, orderCancel: OrderCancel): void { if ( this.serverVersion < MIN_SERVER_VER.MANUAL_ORDER_TIME && - orderCancel.manualOrderCancelTime.length + orderCancel.manualOrderCancelTime?.length ) { return this.emitError( "It does not support manual order cancel time attribute", @@ -568,7 +568,7 @@ function tagValuesToTokens(tagValues: TagValue[]): unknown[] { tokens.push(orderId); if (this.serverVersion >= MIN_SERVER_VER.MANUAL_ORDER_TIME) - tokens.push(orderCancel.manualOrderCancelTime); + tokens.push(orderCancel.manualOrderCancelTime ?? ""); if ( this.serverVersion >= MIN_SERVER_VER.RFQ_FIELDS && @@ -580,7 +580,7 @@ function tagValuesToTokens(tagValues: TagValue[]): unknown[] { } if (this.serverVersion >= MIN_SERVER_VER.CME_TAGGING_FIELDS) { - tokens.push(orderCancel.extOperator); + tokens.push(orderCancel.extOperator ?? ""); tokens.push(orderCancel.manualOrderIndicator ?? Integer_MAX_VALUE); } @@ -2251,7 +2251,7 @@ function tagValuesToTokens(tagValues: TagValue[]): unknown[] { } if (this.serverVersion >= MIN_SERVER_VER.CME_TAGGING_FIELDS) { - tokens.push(orderCancel.extOperator); + tokens.push(orderCancel.extOperator ?? ""); tokens.push(orderCancel.manualOrderIndicator ?? Integer_MAX_VALUE); } From 9e2fce508e1edc9c159954369e7c32f5e1792349 Mon Sep 17 00:00:00 2001 From: Ronan-Yann Lorin Date: Sun, 17 Nov 2024 10:27:49 +0100 Subject: [PATCH 4/9] Unnecessary undefined tests removed --- src/core/io/encoder.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/io/encoder.ts b/src/core/io/encoder.ts index 7878eee8..94d5d059 100644 --- a/src/core/io/encoder.ts +++ b/src/core/io/encoder.ts @@ -568,7 +568,7 @@ function tagValuesToTokens(tagValues: TagValue[]): unknown[] { tokens.push(orderId); if (this.serverVersion >= MIN_SERVER_VER.MANUAL_ORDER_TIME) - tokens.push(orderCancel.manualOrderCancelTime ?? ""); + tokens.push(orderCancel.manualOrderCancelTime); if ( this.serverVersion >= MIN_SERVER_VER.RFQ_FIELDS && @@ -580,7 +580,7 @@ function tagValuesToTokens(tagValues: TagValue[]): unknown[] { } if (this.serverVersion >= MIN_SERVER_VER.CME_TAGGING_FIELDS) { - tokens.push(orderCancel.extOperator ?? ""); + tokens.push(orderCancel.extOperator); tokens.push(orderCancel.manualOrderIndicator ?? Integer_MAX_VALUE); } @@ -2251,7 +2251,7 @@ function tagValuesToTokens(tagValues: TagValue[]): unknown[] { } if (this.serverVersion >= MIN_SERVER_VER.CME_TAGGING_FIELDS) { - tokens.push(orderCancel.extOperator ?? ""); + tokens.push(orderCancel.extOperator); tokens.push(orderCancel.manualOrderIndicator ?? Integer_MAX_VALUE); } From 13773d502c447d0bcefd52fc53a7b7bbca72947a Mon Sep 17 00:00:00 2001 From: Ronan-Yann Lorin Date: Sun, 17 Nov 2024 15:52:23 +0100 Subject: [PATCH 5/9] orders tests refactoring --- .../subscription-registry.test.ts | 2 +- .../api/order/placeConditionalOrder.test.ts | 584 +++++++----------- src/tests/unit/api/order/placeOrder.test.ts | 260 ++++---- src/tests/unit/sample-data/orders.ts | 12 + 4 files changed, 351 insertions(+), 507 deletions(-) create mode 100644 src/tests/unit/sample-data/orders.ts diff --git a/src/tests/unit/api-next-live/subscription-registry.test.ts b/src/tests/unit/api-next-live/subscription-registry.test.ts index b60e3c65..0898796d 100644 --- a/src/tests/unit/api-next-live/subscription-registry.test.ts +++ b/src/tests/unit/api-next-live/subscription-registry.test.ts @@ -2,7 +2,7 @@ import { Subscription } from "rxjs"; import { IBApiNext, IBApiNextError } from "../../.."; describe("Subscription registry Tests", () => { - jest.setTimeout(20000); + jest.setTimeout(2_000); const clientId = Math.floor(Math.random() * 32766) + 1; // ensure unique client diff --git a/src/tests/unit/api/order/placeConditionalOrder.test.ts b/src/tests/unit/api/order/placeConditionalOrder.test.ts index 3d41eec6..fb3a8337 100644 --- a/src/tests/unit/api/order/placeConditionalOrder.test.ts +++ b/src/tests/unit/api/order/placeConditionalOrder.test.ts @@ -10,19 +10,20 @@ import { IBApi, MarginCondition, Order, - OrderAction, OrderCondition, - OrderType, PercentChangeCondition, PriceCondition, TimeCondition, - TimeInForce, TriggerMethod, VolumeCondition, } from "../../../.."; import configuration from "../../../../common/configuration"; import logger from "../../../../common/logger"; import { aapl_contract, sample_stock } from "../../sample-data/contracts"; +import { sample_order } from "../../sample-data/orders"; + +const refContract: Contract = sample_stock; +const refOrder: Order = sample_order; const sample_price_condition: OrderCondition = new PriceCondition( 29, @@ -87,450 +88,315 @@ describe("Place Conditional Orders", () => { test("placeOrder with PriceCondition", (done) => { let refId: number; - const refContract: Contract = sample_stock; - const refOrder: Order = { - orderType: OrderType.LMT, - action: OrderAction.BUY, - lmtPrice: 0.01, - totalQuantity: 1, - conditionsIgnoreRth: true, - conditionsCancelOrder: false, - conditions: [sample_price_condition], - tif: TimeInForce.DAY, - transmit: true, - }; - - let isDone = false; + refOrder.conditions = [sample_price_condition]; + + let isSuccess = false; ib.once(EventName.nextValidId, (orderId: number) => { refId = orderId; - ib.placeOrder(refId, refContract, refOrder).reqOpenOrders(); + ib.reqOpenOrders().placeOrder(refId, refContract, refOrder); }) .on(EventName.openOrder, (orderId, contract, order, _orderState) => { - if (orderId == refId && !isDone) { - isDone = true; + if (orderId == refId) { expect(contract.symbol).toEqual(refContract.symbol); expect(order.totalQuantity).toEqual(refOrder.totalQuantity); - done(); } }) - .on( - EventName.error, - ( - error: Error, - code: ErrorCode, - reqId: number, - _advancedOrderReject?: unknown, - ) => { - if (reqId === -1) { - logger.info(error.message); - } else { - const msg = `[${reqId}] ${error.message} (Error #${code})`; - if ( - error.message.includes("Warning:") || - error.message.includes("Order Message:") - ) { - logger.warn(msg); - } else { - ib.disconnect(); - done(msg); - } - } - }, - ); - - ib.connect(); + .on(EventName.orderStatus, (orderId, _status, filled, remaining) => { + if (!isSuccess && orderId == refId) { + expect(filled).toEqual(0); + expect(remaining).toEqual(refOrder.totalQuantity); + isSuccess = true; + ib.cancelOrder(orderId); + done(); + } + }); + + ib.connect().on(EventName.error, (error, code, reqId) => { + if (reqId === ErrorCode.NO_VALID_ID) { + done(error.message); + } else { + const msg = `[${reqId}] ${error.message} (Error #${code})`; + if ( + error.message.includes("Warning:") || + error.message.includes("Order Message:") + ) { + logger.warn(msg); + } else { + ib.disconnect(); + done(msg); + } + } + }); }); test("placeOrder with ExecutionCondition", (done) => { let refId: number; - const refContract: Contract = sample_stock; - const refOrder: Order = { - orderType: OrderType.LMT, - action: OrderAction.BUY, - lmtPrice: 0.01, - totalQuantity: 1, - conditionsIgnoreRth: true, - conditionsCancelOrder: false, - conditions: [sample_execution_condition], - tif: TimeInForce.DAY, - transmit: true, - }; - - let isDone = false; + refOrder.conditions = [sample_execution_condition]; + + let isSuccess = false; ib.once(EventName.nextValidId, (orderId: number) => { refId = orderId; - ib.placeOrder(refId, refContract, refOrder).reqOpenOrders(); + ib.reqOpenOrders().placeOrder(refId, refContract, refOrder); }) .on(EventName.openOrder, (orderId, contract, order, _orderState) => { - if (orderId == refId && !isDone) { - isDone = true; + if (orderId == refId) { expect(contract.symbol).toEqual(refContract.symbol); expect(order.totalQuantity).toEqual(refOrder.totalQuantity); - done(); } }) - .on( - EventName.error, - ( - error: Error, - code: ErrorCode, - reqId: number, - _advancedOrderReject?: unknown, - ) => { - if (reqId === -1) { - logger.info(error.message); - } else { - const msg = `[${reqId}] ${error.message} (Error #${code})`; - if ( - error.message.includes("Warning:") || - error.message.includes("Order Message:") - ) { - logger.warn(msg); - } else { - ib.disconnect(); - done(msg); - } - } - }, - ); - - ib.connect(); + .on(EventName.orderStatus, (orderId, _status, filled, remaining) => { + if (!isSuccess && orderId == refId) { + expect(filled).toEqual(0); + expect(remaining).toEqual(refOrder.totalQuantity); + isSuccess = true; + ib.cancelOrder(orderId); + done(); + } + }); + + ib.connect().on(EventName.error, (error, code, reqId) => { + if (reqId === ErrorCode.NO_VALID_ID) { + done(error.message); + } else { + const msg = `[${reqId}] ${error.message} (Error #${code})`; + if ( + error.message.includes("Warning:") || + error.message.includes("Order Message:") + ) { + logger.warn(msg); + } else { + ib.disconnect(); + done(msg); + } + } + }); }); test("placeOrder with MarginCondition", (done) => { let refId: number; - const refContract: Contract = sample_stock; - const refOrder: Order = { - orderType: OrderType.LMT, - action: OrderAction.BUY, - lmtPrice: 0.01, - totalQuantity: 1, - conditionsIgnoreRth: true, - conditionsCancelOrder: false, - conditions: [sample_margin_condition], - tif: TimeInForce.DAY, - transmit: true, - }; - - let isDone = false; + refOrder.conditions = [sample_margin_condition]; + + let isSuccess = false; ib.once(EventName.nextValidId, (orderId: number) => { refId = orderId; - ib.placeOrder(refId, refContract, refOrder).reqOpenOrders(); + ib.reqOpenOrders().placeOrder(refId, refContract, refOrder); }) .on(EventName.openOrder, (orderId, contract, order, _orderState) => { - if (orderId == refId && !isDone) { - isDone = true; + if (orderId == refId) { expect(contract.symbol).toEqual(refContract.symbol); expect(order.totalQuantity).toEqual(refOrder.totalQuantity); - done(); } }) - .on( - EventName.error, - ( - error: Error, - code: ErrorCode, - reqId: number, - _advancedOrderReject?: unknown, - ) => { - if (reqId === -1) { - logger.info(error.message); - } else { - const msg = `[${reqId}] ${error.message} (Error #${code})`; - if ( - error.message.includes("Warning:") || - error.message.includes("Order Message:") - ) { - logger.warn(msg); - } else { - ib.disconnect(); - done(msg); - } - } - }, - ); - - ib.connect(); + .on(EventName.orderStatus, (orderId, _status, filled, remaining) => { + if (!isSuccess && orderId == refId) { + expect(filled).toEqual(0); + expect(remaining).toEqual(refOrder.totalQuantity); + isSuccess = true; + ib.cancelOrder(orderId); + done(); + } + }); + + ib.connect().on(EventName.error, (error, code, reqId) => { + if (reqId === ErrorCode.NO_VALID_ID) { + done(error.message); + } else { + const msg = `[${reqId}] ${error.message} (Error #${code})`; + if ( + error.message.includes("Warning:") || + error.message.includes("Order Message:") + ) { + logger.warn(msg); + } else { + ib.disconnect(); + done(msg); + } + } + }); }); test("placeOrder with PercentChangeCondition", (done) => { let refId: number; - const refContract: Contract = sample_stock; - const refOrder: Order = { - orderType: OrderType.LMT, - action: OrderAction.BUY, - lmtPrice: 0.01, - totalQuantity: 1, - conditionsIgnoreRth: true, - conditionsCancelOrder: false, - conditions: [sample_percent_condition], - tif: TimeInForce.DAY, - transmit: true, - }; - - let isDone = false; + refOrder.conditions = [sample_percent_condition]; + + let isSuccess = false; ib.once(EventName.nextValidId, (orderId: number) => { refId = orderId; - ib.placeOrder(refId, refContract, refOrder).reqOpenOrders(); + ib.reqOpenOrders().placeOrder(refId, refContract, refOrder); }) .on(EventName.openOrder, (orderId, contract, order, _orderState) => { - if (orderId == refId && !isDone) { - isDone = true; + if (orderId == refId) { expect(contract.symbol).toEqual(refContract.symbol); expect(order.totalQuantity).toEqual(refOrder.totalQuantity); - done(); } }) - .on( - EventName.error, - ( - error: Error, - code: ErrorCode, - reqId: number, - _advancedOrderReject?: unknown, - ) => { - if (reqId === -1) { - logger.info(error.message); - } else { - const msg = `[${reqId}] ${error.message} (Error #${code})`; - if ( - error.message.includes("Warning:") || - error.message.includes("Order Message:") - ) { - logger.warn(msg); - } else { - ib.disconnect(); - done(msg); - } - } - }, - ); - - ib.connect(); + .on(EventName.orderStatus, (orderId, _status, filled, remaining) => { + if (!isSuccess && orderId == refId) { + expect(filled).toEqual(0); + expect(remaining).toEqual(refOrder.totalQuantity); + isSuccess = true; + ib.cancelOrder(orderId); + done(); + } + }); + + ib.connect().on(EventName.error, (error, code, reqId) => { + if (reqId === ErrorCode.NO_VALID_ID) { + done(error.message); + } else { + const msg = `[${reqId}] ${error.message} (Error #${code})`; + if ( + error.message.includes("Warning:") || + error.message.includes("Order Message:") + ) { + logger.warn(msg); + } else { + ib.disconnect(); + done(msg); + } + } + }); }); test("placeOrder with TimeCondition", (done) => { let refId: number; - const refContract: Contract = sample_stock; - const refOrder: Order = { - orderType: OrderType.LMT, - action: OrderAction.BUY, - lmtPrice: 0.01, - totalQuantity: 1, - conditionsIgnoreRth: true, - conditionsCancelOrder: false, - conditions: [sample_time_condition], - tif: TimeInForce.DAY, - transmit: true, - }; - - let isDone = false; + refOrder.conditions = [sample_time_condition]; + + let isSuccess = false; ib.once(EventName.nextValidId, (orderId: number) => { refId = orderId; - ib.placeOrder(refId, refContract, refOrder).reqOpenOrders(); + ib.reqOpenOrders().placeOrder(refId, refContract, refOrder); }) .on(EventName.openOrder, (orderId, contract, order, _orderState) => { if (orderId == refId) { - isDone = true; expect(contract.symbol).toEqual(refContract.symbol); expect(order.totalQuantity).toEqual(refOrder.totalQuantity); } }) - .on(EventName.openOrderEnd, () => { - if (isDone) done(); - }) - .on( - EventName.orderStatus, - ( - orderId, - status, - filled, - remaining, - _avgFillPrice, - _permId, - _parentId, - _lastFillPrice, - _clientId, - _whyHeld, - _mktCapPrice, - ) => { - if (!isDone && orderId == refId) { - isDone = true; - expect(status).toMatch(/Pending/); - expect(filled).toEqual(0); - expect(remaining).toEqual(refOrder.totalQuantity); - done(); - } - }, - ) - .on( - EventName.error, - ( - error: Error, - code: ErrorCode, - reqId: number, - _advancedOrderReject?: unknown, - ) => { - if (reqId === -1) { - logger.info(error.message); - } else { - const msg = `[${reqId}] ${error.message} (Error #${code})`; - if ( - error.message.includes("Warning:") || - error.message.includes("Order Message:") - ) { - logger.warn(msg); - } else { - ib.disconnect(); - done(msg); - } - } - }, - ); - - ib.connect() - .on(EventName.error, (error, code, reqId) => { - if (reqId > 0) { - const msg = `[${reqId}] ${error.message} (Error #${code})`; - if ( - error.message.includes("Warning:") || - error.message.includes("Order Message:") - ) { - logger.warn(msg); - } else { - ib.disconnect(); - done(msg); - } + .on(EventName.orderStatus, (orderId, _status, filled, remaining) => { + if (!isSuccess && orderId == refId) { + expect(filled).toEqual(0); + expect(remaining).toEqual(refOrder.totalQuantity); + isSuccess = true; + ib.cancelOrder(orderId); + done(); + } + }); + + ib.connect().on(EventName.error, (error, code, reqId) => { + if (reqId === ErrorCode.NO_VALID_ID) { + done(error.message); + } else { + const msg = `[${reqId}] ${error.message} (Error #${code})`; + if ( + error.message.includes("Warning:") || + error.message.includes("Order Message:") + ) { + logger.warn(msg); } else { - console.error("ERROR", error.message, code, reqId); + ib.disconnect(); + done(msg); } - }) - .on(EventName.info, (msg, code) => console.info("INFO", code, msg)) - .on(EventName.disconnected, () => done()); + } + }); }); test("placeOrder with VolumeCondition", (done) => { let refId: number; - const refContract: Contract = sample_stock; - const refOrder: Order = { - orderType: OrderType.LMT, - action: OrderAction.BUY, - lmtPrice: 0.01, - totalQuantity: 1, - conditionsIgnoreRth: true, - conditionsCancelOrder: false, - conditions: [sample_volume_condition], - tif: TimeInForce.DAY, - transmit: true, - }; - - let isDone = false; + refOrder.conditions = [sample_volume_condition]; + + let isSuccess = false; ib.once(EventName.nextValidId, (orderId: number) => { refId = orderId; - ib.placeOrder(refId, refContract, refOrder).reqOpenOrders(); + ib.reqOpenOrders().placeOrder(refId, refContract, refOrder); }) .on(EventName.openOrder, (orderId, contract, order, _orderState) => { - if (orderId == refId && !isDone) { - isDone = true; + if (orderId == refId) { expect(contract.symbol).toEqual(refContract.symbol); expect(order.totalQuantity).toEqual(refOrder.totalQuantity); - done(); } }) - .on( - EventName.error, - ( - error: Error, - code: ErrorCode, - reqId: number, - _advancedOrderReject?: unknown, - ) => { - if (reqId === -1) { - logger.info(error.message); - } else { - const msg = `[${reqId}] ${error.message} (Error #${code})`; - if ( - error.message.includes("Warning:") || - error.message.includes("Order Message:") - ) { - logger.warn(msg); - } else { - ib.disconnect(); - done(msg); - } - } - }, - ); - - ib.connect(); + .on(EventName.orderStatus, (orderId, _status, filled, remaining) => { + if (!isSuccess && orderId == refId) { + expect(filled).toEqual(0); + expect(remaining).toEqual(refOrder.totalQuantity); + isSuccess = true; + ib.cancelOrder(orderId); + done(); + } + }); + + ib.connect().on(EventName.error, (error, code, reqId) => { + if (reqId === ErrorCode.NO_VALID_ID) { + done(error.message); + } else { + const msg = `[${reqId}] ${error.message} (Error #${code})`; + if ( + error.message.includes("Warning:") || + error.message.includes("Order Message:") + ) { + logger.warn(msg); + } else { + ib.disconnect(); + done(msg); + } + } + }); }); test("placeOrder with all conditions", (done) => { let refId: number; - const refContract: Contract = sample_stock; - const refOrder: Order = { - orderType: OrderType.LMT, - action: OrderAction.BUY, - lmtPrice: 0.01, - totalQuantity: 1, - conditionsIgnoreRth: true, - conditionsCancelOrder: false, - conditions: [ - sample_price_condition, - sample_execution_condition, - sample_margin_condition, - sample_percent_condition, - sample_time_condition, - sample_volume_condition, - ], - tif: TimeInForce.DAY, - transmit: true, - }; - - let isDone = false; + refOrder.conditions = [ + sample_price_condition, + sample_execution_condition, + sample_margin_condition, + sample_percent_condition, + sample_time_condition, + sample_volume_condition, + ]; + + let isSuccess = false; ib.once(EventName.nextValidId, (orderId: number) => { refId = orderId; - ib.placeOrder(refId, refContract, refOrder).reqOpenOrders(); + ib.reqOpenOrders().placeOrder(refId, refContract, refOrder); }) .on(EventName.openOrder, (orderId, contract, order, _orderState) => { - if (orderId == refId && !isDone) { - isDone = true; + if (orderId == refId) { expect(contract.symbol).toEqual(refContract.symbol); expect(order.totalQuantity).toEqual(refOrder.totalQuantity); - done(); } }) - .on( - EventName.error, - ( - error: Error, - code: ErrorCode, - reqId: number, - _advancedOrderReject?: unknown, - ) => { - if (reqId === -1) { - logger.info(error.message); - } else { - const msg = `[${reqId}] ${error.message} (Error #${code})`; - if ( - error.message.includes("Warning:") || - error.message.includes("Order Message:") - ) { - logger.warn(msg); - } else { - ib.disconnect(); - done(msg); - } - } - }, - ); - - ib.connect(); + .on(EventName.orderStatus, (orderId, _status, filled, remaining) => { + if (!isSuccess && orderId == refId) { + expect(filled).toEqual(0); + expect(remaining).toEqual(refOrder.totalQuantity); + isSuccess = true; + ib.cancelOrder(orderId); + done(); + } + }); + + ib.connect().on(EventName.error, (error, code, reqId) => { + if (reqId === ErrorCode.NO_VALID_ID) { + done(error.message); + } else { + const msg = `[${reqId}] ${error.message} (Error #${code})`; + if ( + error.message.includes("Warning:") || + error.message.includes("Order Message:") + ) { + logger.warn(msg); + } else { + ib.disconnect(); + done(msg); + } + } + }); }); }); diff --git a/src/tests/unit/api/order/placeOrder.test.ts b/src/tests/unit/api/order/placeOrder.test.ts index 3b8e82fb..9df69d71 100644 --- a/src/tests/unit/api/order/placeOrder.test.ts +++ b/src/tests/unit/api/order/placeOrder.test.ts @@ -55,49 +55,43 @@ describe("Place Orders", () => { transmit: true, }; - let isDone = false; + let isSuccess = false; ib.once(EventName.nextValidId, (orderId: number) => { refId = orderId; - ib.placeOrder(refId, refContract, refOrder); + ib.reqOpenOrders().placeOrder(refId, refContract, refOrder); }) .on(EventName.openOrder, (orderId, contract, order, _orderState) => { - if (orderId == refId && !isDone) { - isDone = true; + if (orderId == refId) { expect(contract.symbol).toEqual(refContract.symbol); expect(order.totalQuantity).toEqual(refOrder.totalQuantity); - done(); } }) - .on( - EventName.error, - ( - error: Error, - code: ErrorCode, - reqId: number, - _advancedOrderReject?: unknown, - ) => { - if (reqId === -1) { - logger.info(error.message); - } else { - const msg = `[${reqId}] ${error.message} (Error #${code})`; - if ( - error.message.includes("Warning:") || - error.message.includes("Order Message:") - ) { - logger.warn(msg); - } else if (code == ErrorCode.NO_TRADING_PERMISSIONS) { - // Ignore this error for tests - logger.warn(msg); - done(); - } else { - ib.disconnect(); - done(msg); - } - } - }, - ); - - ib.connect().reqOpenOrders(); + .on(EventName.orderStatus, (orderId, _status, filled, remaining) => { + if (!isSuccess && orderId == refId) { + expect(filled).toEqual(0); + expect(remaining).toEqual(refOrder.totalQuantity); + isSuccess = true; + ib.cancelOrder(orderId); + done(); + } + }); + + ib.connect().on(EventName.error, (error, code, reqId) => { + if (reqId === ErrorCode.NO_VALID_ID) { + done(error.message); + } else { + const msg = `[${reqId}] ${error.message} (Error #${code})`; + if ( + error.message.includes("Warning:") || + error.message.includes("Order Message:") + ) { + logger.warn(msg); + } else { + ib.disconnect(); + done(msg); + } + } + }); }); test("What if Order", (done) => { @@ -115,52 +109,36 @@ describe("Place Orders", () => { whatIf: true, }; - let isDone = false; ib.once(EventName.nextValidId, (orderId: number) => { refId = orderId; - ib.placeOrder(refId, refContract, refOrder); - }) - .on(EventName.openOrder, (orderId, contract, order, orderState) => { - if (orderId == refId && !isDone) { - expect(contract.symbol).toEqual(refContract.symbol); - expect(order.totalQuantity).toEqual(refOrder.totalQuantity); - if (orderState.minCommission || orderState.maxCommission) { - expect(orderState.commissionCurrency).toEqual(refContract.currency); - isDone = true; - done(); - } + ib.reqOpenOrders().placeOrder(refId, refContract, refOrder); + }).on(EventName.openOrder, (orderId, contract, order, orderState) => { + if (orderId == refId) { + expect(contract.symbol).toEqual(refContract.symbol); + expect(order.totalQuantity).toEqual(refOrder.totalQuantity); + if (orderState.minCommission || orderState.maxCommission) { + expect(orderState.commissionCurrency).toEqual(refContract.currency); + done(); } - }) - .on( - EventName.error, - ( - error: Error, - code: ErrorCode, - reqId: number, - _advancedOrderReject?: unknown, - ) => { - if (reqId === -1) { - logger.info(error.message); - } else { - const msg = `[${reqId}] ${error.message} (Error #${code})`; - if ( - error.message.includes("Warning:") || - error.message.includes("Order Message:") - ) { - logger.warn(msg); - } else if (code == ErrorCode.NO_TRADING_PERMISSIONS) { - // Ignore this error for tests - logger.warn(msg); - done(); - } else { - ib.disconnect(); - done(msg); - } - } - }, - ); + } + }); - ib.connect().reqOpenOrders(); + ib.connect().on(EventName.error, (error, code, reqId) => { + if (reqId === ErrorCode.NO_VALID_ID) { + done(error.message); + } else { + const msg = `[${reqId}] ${error.message} (Error #${code})`; + if ( + error.message.includes("Warning:") || + error.message.includes("Order Message:") + ) { + logger.warn(msg); + } else { + ib.disconnect(); + done(msg); + } + } + }); }); test("Crypto placeOrder", (done) => { @@ -177,49 +155,43 @@ describe("Place Orders", () => { transmit: true, }; - let isDone = false; + let isSuccess = false; ib.once(EventName.nextValidId, (orderId: number) => { refId = orderId; - ib.placeOrder(refId, refContract, refOrder); + ib.reqOpenOrders().placeOrder(refId, refContract, refOrder); }) .on(EventName.openOrder, (orderId, contract, order, _orderState) => { - if (orderId == refId && !isDone) { - isDone = true; + if (orderId == refId) { expect(contract.symbol).toEqual(refContract.symbol); expect(order.totalQuantity).toEqual(refOrder.totalQuantity); - done(); } }) - .on( - EventName.error, - ( - error: Error, - code: ErrorCode, - reqId: number, - _advancedOrderReject?: unknown, - ) => { - if (reqId === -1) { - logger.info(error.message); - } else { - const msg = `[${reqId}] ${error.message} (Error #${code})`; - if ( - error.message.includes("Warning:") || - error.message.includes("Order Message:") - ) { - logger.warn(msg); - } else if (code == ErrorCode.NO_TRADING_PERMISSIONS) { - // Ignore this error for tests - logger.warn(msg); - done(); - } else { - ib.disconnect(); - done(msg); - } - } - }, - ); - - ib.connect().reqOpenOrders(); + .on(EventName.orderStatus, (orderId, _status, filled, remaining) => { + if (!isSuccess && orderId == refId) { + expect(filled).toEqual(0); + expect(remaining).toEqual(refOrder.totalQuantity); + isSuccess = true; + ib.cancelOrder(orderId); + done(); + } + }); + + ib.connect().on(EventName.error, (error, code, reqId) => { + if (reqId === ErrorCode.NO_VALID_ID) { + done(error.message); + } else { + const msg = `[${reqId}] ${error.message} (Error #${code})`; + if ( + error.message.includes("Warning:") || + error.message.includes("Order Message:") + ) { + logger.warn(msg); + } else { + ib.disconnect(); + done(msg); + } + } + }); }); test("Option placeOrder", (done) => { @@ -236,48 +208,42 @@ describe("Place Orders", () => { transmit: true, }; - let isDone = false; + let isSuccess = false; ib.once(EventName.nextValidId, (orderId: number) => { refId = orderId; - ib.placeOrder(refId, refContract, refOrder); + ib.reqOpenOrders().placeOrder(refId, refContract, refOrder); }) .on(EventName.openOrder, (orderId, contract, order, _orderState) => { - if (orderId == refId && !isDone) { - isDone = true; + if (orderId == refId) { expect(contract.symbol).toEqual(refContract.symbol); expect(order.totalQuantity).toEqual(refOrder.totalQuantity); - done(); } }) - .on( - EventName.error, - ( - error: Error, - code: ErrorCode, - reqId: number, - _advancedOrderReject?: unknown, - ) => { - if (reqId === -1) { - logger.info(error.message); - } else { - const msg = `[${reqId}] ${error.message} (Error #${code})`; - if ( - error.message.includes("Warning:") || - error.message.includes("Order Message:") - ) { - logger.warn(msg); - } else if (code == ErrorCode.NO_TRADING_PERMISSIONS) { - // Ignore this error for tests - logger.warn(msg); - done(); - } else { - ib.disconnect(); - done(msg); - } - } - }, - ); - - ib.connect().reqOpenOrders(); + .on(EventName.orderStatus, (orderId, _status, filled, remaining) => { + if (!isSuccess && orderId == refId) { + expect(filled).toEqual(0); + expect(remaining).toEqual(refOrder.totalQuantity); + isSuccess = true; + ib.cancelOrder(orderId); + done(); + } + }); + + ib.connect().on(EventName.error, (error, code, reqId) => { + if (reqId === ErrorCode.NO_VALID_ID) { + done(error.message); + } else { + const msg = `[${reqId}] ${error.message} (Error #${code})`; + if ( + error.message.includes("Warning:") || + error.message.includes("Order Message:") + ) { + logger.warn(msg); + } else { + ib.disconnect(); + done(msg); + } + } + }); }); }); diff --git a/src/tests/unit/sample-data/orders.ts b/src/tests/unit/sample-data/orders.ts new file mode 100644 index 00000000..4a805881 --- /dev/null +++ b/src/tests/unit/sample-data/orders.ts @@ -0,0 +1,12 @@ +import { Order, OrderAction, OrderType, TimeInForce } from "../../../.."; + +export const sample_order: Order = { + orderType: OrderType.LMT, + action: OrderAction.BUY, + lmtPrice: 0.01, + totalQuantity: 1, + conditionsIgnoreRth: true, + conditionsCancelOrder: false, + tif: TimeInForce.DAY, + transmit: true, +}; From ba623ad5ac7c8a26985063e24016561ad2b54792 Mon Sep 17 00:00:00 2001 From: Ronan-Yann Lorin Date: Sun, 17 Nov 2024 15:56:04 +0100 Subject: [PATCH 6/9] Build fix --- src/tests/unit/sample-data/orders.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/unit/sample-data/orders.ts b/src/tests/unit/sample-data/orders.ts index 4a805881..de0cd4ff 100644 --- a/src/tests/unit/sample-data/orders.ts +++ b/src/tests/unit/sample-data/orders.ts @@ -1,4 +1,4 @@ -import { Order, OrderAction, OrderType, TimeInForce } from "../../../.."; +import { Order, OrderAction, OrderType, TimeInForce } from "../../.."; export const sample_order: Order = { orderType: OrderType.LMT, From 4103da402b81b2cac31004a7379e7bd9cc3dc081 Mon Sep 17 00:00:00 2001 From: Ronan-Yann Lorin Date: Sun, 17 Nov 2024 23:00:57 +0100 Subject: [PATCH 7/9] getContractDetails test refactored --- .../get-contract-details.test.ts | 75 +++++++++++-------- src/tests/unit/api/market-data.test.ts | 2 +- src/tests/unit/api/order/issue203.test.ts | 2 +- src/tests/unit/api/order/placeOrder.test.ts | 61 +++++---------- src/tests/unit/sample-data/contracts.ts | 2 +- src/tests/unit/sample-data/orders.ts | 3 +- 6 files changed, 67 insertions(+), 78 deletions(-) diff --git a/src/tests/unit/api-next-live/get-contract-details.test.ts b/src/tests/unit/api-next-live/get-contract-details.test.ts index ddc91411..ec6f7c0d 100644 --- a/src/tests/unit/api-next-live/get-contract-details.test.ts +++ b/src/tests/unit/api-next-live/get-contract-details.test.ts @@ -7,12 +7,13 @@ import { IBApiNext, IBApiNextError } from "../../.."; import { sample_bond, sample_crypto, + sample_future, sample_option, sample_stock, } from "../sample-data/contracts"; describe("ApiNext: getContractDetails()", () => { - jest.setTimeout(10 * 1000); + jest.setTimeout(5_000); const clientId = Math.floor(Math.random() * 32766) + 1; // ensure unique client @@ -51,15 +52,14 @@ describe("ApiNext: getContractDetails()", () => { }); test("Stock contract details", (done) => { + const ref_contract = sample_stock; + api - .getContractDetails(sample_stock) + .getContractDetails(ref_contract) .then((result) => { - // console.log(result); expect(result.length).toBeGreaterThan(0); - if (result.length) { - expect(result[0].contract.symbol).toEqual(sample_stock.symbol); - expect(result[0].contract.secType).toEqual(sample_stock.secType); - } + expect(result[0].contract.symbol).toEqual(ref_contract.symbol); + expect(result[0].contract.secType).toEqual(ref_contract.secType); done(); }) .catch((err: IBApiNextError) => { @@ -70,15 +70,32 @@ describe("ApiNext: getContractDetails()", () => { }); test("Future contract details", (done) => { + const ref_contract = sample_future; + api - .getContractDetails(sample_crypto) + .getContractDetails(ref_contract) .then((result) => { - // console.log(result); expect(result.length).toBeGreaterThan(0); - if (result.length) { - expect(result[0].contract.symbol).toEqual(sample_crypto.symbol); - expect(result[0].contract.secType).toEqual(sample_crypto.secType); - } + expect(result[0].contract.symbol).toEqual(ref_contract.symbol); + expect(result[0].contract.secType).toEqual(ref_contract.secType); + done(); + }) + .catch((err: IBApiNextError) => { + done( + `getContractDetails failed with '${err.error.message}' (Error #${err.code})`, + ); + }); + }); + + test("Crypto contract details", (done) => { + const ref_contract = sample_crypto; + + api + .getContractDetails(ref_contract) + .then((result) => { + expect(result.length).toBeGreaterThan(0); + expect(result[0].contract.symbol).toEqual(ref_contract.symbol); + expect(result[0].contract.secType).toEqual(ref_contract.secType); done(); }) .catch((err: IBApiNextError) => { @@ -89,15 +106,14 @@ describe("ApiNext: getContractDetails()", () => { }); test("Option contract details", (done) => { + const ref_contract = sample_option; + api - .getContractDetails(sample_option) + .getContractDetails(ref_contract) .then((result) => { - // console.log(result); expect(result.length).toBeGreaterThan(0); - if (result.length) { - expect(result[0].contract.symbol).toEqual(sample_option.symbol); - expect(result[0].contract.secType).toEqual(sample_option.secType); - } + expect(result[0].contract.symbol).toEqual(ref_contract.symbol); + expect(result[0].contract.secType).toEqual(ref_contract.secType); done(); }) .catch((err: IBApiNextError) => { @@ -108,14 +124,14 @@ describe("ApiNext: getContractDetails()", () => { }); test("Bond contract details", (done) => { + const ref_contract = sample_bond; + api - .getContractDetails(sample_bond) + .getContractDetails(ref_contract) .then((result) => { - // console.log(result); expect(result.length).toBeGreaterThan(0); - if (result.length) { - expect(result[0].contract.secType).toEqual(sample_bond.secType); - } + // expect(result[0].contract.symbol).toEqual(ref_contract.symbol); + expect(result[0].contract.secType).toEqual(ref_contract.secType); done(); }) .catch((err: IBApiNextError) => { @@ -126,15 +142,14 @@ describe("ApiNext: getContractDetails()", () => { }); test("Crypto contract details", (done) => { + const ref_contract = sample_crypto; + api - .getContractDetails(sample_crypto) + .getContractDetails(ref_contract) .then((result) => { - // console.log(result); expect(result.length).toBeGreaterThan(0); - if (result.length) { - expect(result[0].contract.symbol).toEqual(sample_crypto.symbol); - expect(result[0].contract.secType).toEqual(sample_crypto.secType); - } + expect(result[0].contract.symbol).toEqual(ref_contract.symbol); + expect(result[0].contract.secType).toEqual(ref_contract.secType); done(); }) .catch((err: IBApiNextError) => { diff --git a/src/tests/unit/api/market-data.test.ts b/src/tests/unit/api/market-data.test.ts index 87bec715..40f8c0a3 100644 --- a/src/tests/unit/api/market-data.test.ts +++ b/src/tests/unit/api/market-data.test.ts @@ -20,7 +20,7 @@ import { } from "../sample-data/contracts"; describe("IBApi Market data Tests", () => { - jest.setTimeout(15 * 1000); + jest.setTimeout(10_000); let ib: IBApi; const clientId = Math.floor(Math.random() * 32766) + 1; // ensure unique client diff --git a/src/tests/unit/api/order/issue203.test.ts b/src/tests/unit/api/order/issue203.test.ts index 1b29acf8..b74a0f3e 100644 --- a/src/tests/unit/api/order/issue203.test.ts +++ b/src/tests/unit/api/order/issue203.test.ts @@ -111,7 +111,7 @@ describe("Issue #203", () => { const refOrder: Order = { orderType: OrderType.LMT, action: OrderAction.BUY, - lmtPrice: 5400, + lmtPrice: 2400, orderId, totalQuantity: 1, transmit: true, diff --git a/src/tests/unit/api/order/placeOrder.test.ts b/src/tests/unit/api/order/placeOrder.test.ts index 9df69d71..48bb9624 100644 --- a/src/tests/unit/api/order/placeOrder.test.ts +++ b/src/tests/unit/api/order/placeOrder.test.ts @@ -1,16 +1,7 @@ /** * This file implement test code for the placeOrder function . */ -import { - Contract, - ErrorCode, - EventName, - IBApi, - Order, - OrderAction, - OrderType, - TimeInForce, -} from "../../../.."; +import { Contract, ErrorCode, EventName, IBApi, Order } from "../../../.."; import configuration from "../../../../common/configuration"; import logger from "../../../../common/logger"; import { @@ -19,6 +10,7 @@ import { sample_option, sample_stock, } from "../../sample-data/contracts"; +import { sample_order } from "../../sample-data/orders"; describe("Place Orders", () => { jest.setTimeout(20 * 1000); @@ -45,15 +37,7 @@ describe("Place Orders", () => { let refId: number; const refContract: Contract = sample_stock; - const refOrder: Order = { - orderType: OrderType.LMT, - action: OrderAction.BUY, - lmtPrice: 1, - orderId: refId, - totalQuantity: 2, - tif: TimeInForce.DAY, - transmit: true, - }; + const refOrder: Order = sample_order; let isSuccess = false; ib.once(EventName.nextValidId, (orderId: number) => { @@ -98,14 +82,19 @@ describe("Place Orders", () => { let refId: number; const refContract: Contract = sample_etf; + // const refOrder: Order = { + // orderType: OrderType.LMT, + // action: OrderAction.BUY, + // lmtPrice: 1, + // orderId: refId, + // totalQuantity: 2, + // tif: TimeInForce.DAY, + // transmit: true, + // whatIf: true, + // }; const refOrder: Order = { - orderType: OrderType.LMT, - action: OrderAction.BUY, - lmtPrice: 1, - orderId: refId, - totalQuantity: 2, - tif: TimeInForce.DAY, - transmit: true, + ...sample_order, + goodAfterTime: undefined, whatIf: true, }; @@ -145,15 +134,7 @@ describe("Place Orders", () => { let refId: number; const refContract: Contract = sample_crypto; - const refOrder: Order = { - orderType: OrderType.LMT, - action: OrderAction.BUY, - lmtPrice: 1, - orderId: refId, - totalQuantity: 2, - tif: TimeInForce.DAY, - transmit: true, - }; + const refOrder: Order = sample_order; let isSuccess = false; ib.once(EventName.nextValidId, (orderId: number) => { @@ -198,15 +179,7 @@ describe("Place Orders", () => { let refId: number; const refContract: Contract = sample_option; - const refOrder: Order = { - orderType: OrderType.LMT, - action: OrderAction.BUY, - lmtPrice: 1, - orderId: refId, - totalQuantity: 2, - tif: TimeInForce.DAY, - transmit: true, - }; + const refOrder: Order = sample_order; let isSuccess = false; ib.once(EventName.nextValidId, (orderId: number) => { diff --git a/src/tests/unit/sample-data/contracts.ts b/src/tests/unit/sample-data/contracts.ts index 0c6a8f11..adfdb930 100644 --- a/src/tests/unit/sample-data/contracts.ts +++ b/src/tests/unit/sample-data/contracts.ts @@ -15,7 +15,7 @@ import Crypto from "../../../api/contract/crypto"; export const sample_stock: Contract = new Stock("AAPL"); export const sample_etf: Contract = new Stock("SPY"); -export const sample_bond: Contract = new Bond("US279158AE95"); +export const sample_bond: Contract = new Bond("US064159KJ44"); export const sample_index: Contract = new Index("ES", "USD"); export const sample_dax_index: Contract = new Index("DAX", "EUR", "EUREX"); export const sample_crypto: Contract = new Crypto("ETH"); diff --git a/src/tests/unit/sample-data/orders.ts b/src/tests/unit/sample-data/orders.ts index de0cd4ff..94338864 100644 --- a/src/tests/unit/sample-data/orders.ts +++ b/src/tests/unit/sample-data/orders.ts @@ -3,10 +3,11 @@ import { Order, OrderAction, OrderType, TimeInForce } from "../../.."; export const sample_order: Order = { orderType: OrderType.LMT, action: OrderAction.BUY, - lmtPrice: 0.01, + lmtPrice: 1, totalQuantity: 1, conditionsIgnoreRth: true, conditionsCancelOrder: false, tif: TimeInForce.DAY, transmit: true, + goodAfterTime: "20300101-01:01:01", }; From de2f0d1e2ca901fd1923bd824d045588cfa1ebb0 Mon Sep 17 00:00:00 2001 From: Ronan-Yann Lorin Date: Mon, 18 Nov 2024 07:23:40 +0100 Subject: [PATCH 8/9] reqMktData requires up te 11 secs to run --- src/tests/unit/api/market-data.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/unit/api/market-data.test.ts b/src/tests/unit/api/market-data.test.ts index 40f8c0a3..6de95d2d 100644 --- a/src/tests/unit/api/market-data.test.ts +++ b/src/tests/unit/api/market-data.test.ts @@ -20,7 +20,7 @@ import { } from "../sample-data/contracts"; describe("IBApi Market data Tests", () => { - jest.setTimeout(10_000); + jest.setTimeout(20_000); // reqMktData requires up te 11 secs to run let ib: IBApi; const clientId = Math.floor(Math.random() * 32766) + 1; // ensure unique client From 27a9a4bf342f814fb0e564c10ab7873319488762 Mon Sep 17 00:00:00 2001 From: Ronan-Yann Lorin Date: Tue, 19 Nov 2024 21:27:55 +0100 Subject: [PATCH 9/9] Changed crypto used for test suite to BTC (ETH wan't working) --- src/tests/unit/api/order/cancelOrder.test.ts | 2 +- src/tests/unit/api/order/issue203.test.ts | 166 ------------------- src/tests/unit/sample-data/contracts.ts | 2 +- 3 files changed, 2 insertions(+), 168 deletions(-) delete mode 100644 src/tests/unit/api/order/issue203.test.ts diff --git a/src/tests/unit/api/order/cancelOrder.test.ts b/src/tests/unit/api/order/cancelOrder.test.ts index 9c6f37d8..ed0b4bda 100644 --- a/src/tests/unit/api/order/cancelOrder.test.ts +++ b/src/tests/unit/api/order/cancelOrder.test.ts @@ -193,7 +193,7 @@ describe("CancelOrder", () => { let isDone = false; ib.once(EventName.nextValidId, (orderId: number) => { refId = orderId; - ib.placeOrder(refId, contract, order); + ib.placeOrder(refId, contract, { ...order, goodAfterTime: undefined }); }) .on(EventName.orderStatus, (orderId, status) => { if (orderId === refId) { diff --git a/src/tests/unit/api/order/issue203.test.ts b/src/tests/unit/api/order/issue203.test.ts deleted file mode 100644 index b74a0f3e..00000000 --- a/src/tests/unit/api/order/issue203.test.ts +++ /dev/null @@ -1,166 +0,0 @@ -/** - * This file implement test code for the placeOrder function . - */ -import { - Contract, - ErrorCode, - EventName, - IBApi, - Order, - OrderAction, - OrderType, - SecType, -} from "../../../.."; -import configuration from "../../../../common/configuration"; -import logger from "../../../../common/logger"; -import { sample_future } from "../../sample-data/contracts"; - -describe("Issue #203", () => { - jest.setTimeout(20 * 1000); - - let ib: IBApi; - let clientId = Math.floor(Math.random() * 32766) + 1; // ensure unique client - - beforeEach(() => { - ib = new IBApi({ - host: configuration.ib_host, - port: configuration.ib_port, - clientId: clientId++, // increment clientId for each test so they don't interfere on each other - }); - }); - - afterEach(() => { - if (ib) { - ib.disconnect(); - ib = undefined; - } - }); - - test("mafianekcek", (done) => { - let refId: number; - - const refContract: Contract = sample_future; - // const refContract: Contract = sample_stock; - const refOrder: Order = { - action: OrderAction.BUY, - lmtPrice: 1, - totalQuantity: 1, - transmit: true, - orderType: OrderType.LMT, - account: "DU5784856", - tif: "DAY", - orderRef: "RS/3/9", - }; - - let isDone = false; - ib.once(EventName.nextValidId, (orderId: number) => { - refId = orderId; - ib.placeOrder(refId, refContract, refOrder); - }) - .on(EventName.openOrder, (orderId, contract, order, _orderState) => { - if (orderId == refId && !isDone) { - isDone = true; - expect(contract.symbol).toEqual(refContract.symbol); - expect(order.totalQuantity).toEqual(refOrder.totalQuantity); - done(); - } - }) - .on( - EventName.error, - ( - error: Error, - code: ErrorCode, - reqId: number, - _advancedOrderReject?: unknown, - ) => { - if (reqId === -1) { - logger.info(error.message); - } else { - const msg = `[${reqId}] ${error.message} (Error #${code})`; - if ( - error.message.includes("Warning:") || - error.message.includes("Order Message:") - ) { - logger.warn(msg); - } else if (code == ErrorCode.NO_TRADING_PERMISSIONS) { - // Ignore this error for tests - logger.warn(msg); - done(); - } else { - ib.disconnect(); - done(msg); - } - } - }, - ); - - ib.connect().reqOpenOrders(); - }); - - test("fanazhe", (done) => { - let refId: number; - - const orderId = 2; - const refContract: Contract = { - symbol: "ES", - exchange: "CME", - currency: "USD", - lastTradeDateOrContractMonth: "20250620", - secType: SecType.FUT, - }; - const refOrder: Order = { - orderType: OrderType.LMT, - action: OrderAction.BUY, - lmtPrice: 2400, - orderId, - totalQuantity: 1, - transmit: true, - }; - - let isDone = false; - ib.once(EventName.nextValidId, (orderId: number) => { - console.log(orderId); - refId = orderId; - ib.placeOrder(refId, refContract, refOrder).reqOpenOrders(); - }) - .on(EventName.openOrder, (orderId, contract, order, _orderState) => { - console.log(orderId, contract, order, _orderState); - if (orderId == refId && !isDone) { - isDone = true; - expect(contract.symbol).toEqual(refContract.symbol); - expect(order.totalQuantity).toEqual(refOrder.totalQuantity); - done(); - } - }) - .on( - EventName.error, - ( - error: Error, - code: ErrorCode, - reqId: number, - _advancedOrderReject?: unknown, - ) => { - if (reqId === -1) { - logger.info(error.message); - } else { - const msg = `[${reqId}] ${error.message} (Error #${code})`; - if ( - error.message.includes("Warning:") || - error.message.includes("Order Message:") - ) { - logger.warn(msg); - } else if (code == ErrorCode.NO_TRADING_PERMISSIONS) { - // Ignore this error for tests - logger.warn(msg); - done(); - } else { - ib.disconnect(); - done(msg); - } - } - }, - ); - - ib.connect(); - }); -}); diff --git a/src/tests/unit/sample-data/contracts.ts b/src/tests/unit/sample-data/contracts.ts index adfdb930..4e5b42d2 100644 --- a/src/tests/unit/sample-data/contracts.ts +++ b/src/tests/unit/sample-data/contracts.ts @@ -18,7 +18,7 @@ export const sample_etf: Contract = new Stock("SPY"); export const sample_bond: Contract = new Bond("US064159KJ44"); export const sample_index: Contract = new Index("ES", "USD"); export const sample_dax_index: Contract = new Index("DAX", "EUR", "EUREX"); -export const sample_crypto: Contract = new Crypto("ETH"); +export const sample_crypto: Contract = new Crypto("BTC"); // This one will need to be updated sometimes export const sample_future: Contract = new Future(