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..603e6463 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: undefined, + extOperator: "", + manualOrderIndicator: undefined, + }; + else if (typeof orderCancelParam == "string") + orderCancel = { + manualOrderCancelTime: orderCancelParam, + extOperator: "", + manualOrderIndicator: undefined, + }; + 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: undefined, + extOperator: "", + manualOrderIndicator: undefined, + }, + ), + ); return this; } diff --git a/src/api/order/orderCancel.ts b/src/api/order/orderCancel.ts index 998be13f..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; + manualOrderCancelTime?: string; 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..94d5d059 100644 --- a/src/core/io/encoder.ts +++ b/src/core/io/encoder.ts @@ -532,10 +532,10 @@ 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 + 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", @@ -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 && @@ -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); @@ -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.", @@ -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", @@ -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 ?? Integer_MAX_VALUE); } this.sendMsg(tokens); 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-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/market-data.test.ts b/src/tests/unit/api/market-data.test.ts index 87bec715..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(15 * 1000); + 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 diff --git a/src/tests/unit/api/order/cancelOrder.test.ts b/src/tests/unit/api/order/cancelOrder.test.ts index 5e7a4353..ed0b4bda 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, goodAfterTime: undefined }); + }) + .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); } } 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 1b29acf8..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: 5400, - 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/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..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,239 +37,186 @@ 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 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) => { 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, }; - 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) => { 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 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) => { 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 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/contracts.ts b/src/tests/unit/sample-data/contracts.ts index 0c6a8f11..4e5b42d2 100644 --- a/src/tests/unit/sample-data/contracts.ts +++ b/src/tests/unit/sample-data/contracts.ts @@ -15,10 +15,10 @@ 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"); +export const sample_crypto: Contract = new Crypto("BTC"); // This one will need to be updated sometimes export const sample_future: Contract = new Future( diff --git a/src/tests/unit/sample-data/orders.ts b/src/tests/unit/sample-data/orders.ts new file mode 100644 index 00000000..94338864 --- /dev/null +++ b/src/tests/unit/sample-data/orders.ts @@ -0,0 +1,13 @@ +import { Order, OrderAction, OrderType, TimeInForce } from "../../.."; + +export const sample_order: Order = { + orderType: OrderType.LMT, + action: OrderAction.BUY, + lmtPrice: 1, + totalQuantity: 1, + conditionsIgnoreRth: true, + conditionsCancelOrder: false, + tif: TimeInForce.DAY, + transmit: true, + goodAfterTime: "20300101-01:01:01", +};