Skip to content

Commit

Permalink
Merge pull request #228 from stoqey/cancelOrder
Browse files Browse the repository at this point in the history
cancelOrder bug fix
  • Loading branch information
rylorin authored Nov 19, 2024
2 parents a9f310a + 27a9a4b commit d96948c
Show file tree
Hide file tree
Showing 15 changed files with 610 additions and 795 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
10 changes: 3 additions & 7 deletions src/api-next/api-next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -2904,8 +2900,8 @@ export class IBApiNext {
*
* @see [[cancelOrder]]
*/
cancelAllOrders(): void {
this.api.reqGlobalCancel();
cancelAllOrders(orderCancel?: OrderCancel): void {
this.api.reqGlobalCancel(orderCancel);
}

/**
Expand Down
28 changes: 25 additions & 3 deletions src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
);
Expand Down Expand Up @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion src/api/order/orderCancel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Type describing the parameters of an order cancel
*/
export interface OrderCancel {
manualOrderCancelTime: string;
manualOrderCancelTime?: string;
extOperator?: string;
manualOrderIndicator?: number;
}
2 changes: 1 addition & 1 deletion src/core/io/decoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

/**
Expand Down
22 changes: 11 additions & 11 deletions src/core/io/encoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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 &&
Expand All @@ -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);
Expand Down Expand Up @@ -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.",
Expand All @@ -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",
Expand All @@ -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);
Expand Down
75 changes: 45 additions & 30 deletions src/tests/unit/api-next-live/get-contract-details.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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) => {
Expand All @@ -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) => {
Expand All @@ -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) => {
Expand All @@ -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) => {
Expand All @@ -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) => {
Expand Down
2 changes: 1 addition & 1 deletion src/tests/unit/api-next-live/subscription-registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/tests/unit/api/market-data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit d96948c

Please sign in to comment.