Skip to content

Commit

Permalink
Upgrade server to v169
Browse files Browse the repository at this point in the history
  • Loading branch information
rylorin committed Sep 20, 2023
1 parent 62abae2 commit 91988f7
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 129 deletions.
2 changes: 1 addition & 1 deletion src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export interface IBApiCreationOptions {
}

/** Maximum supported version. */
export const MAX_SUPPORTED_SERVER_VERSION = MIN_SERVER_VER.USER_INFO;
export const MAX_SUPPORTED_SERVER_VERSION = MIN_SERVER_VER.MANUAL_ORDER_TIME;

/** Minimum supported version. */
export const MIN_SERVER_VER_SUPPORTED = 38;
Expand Down
19 changes: 19 additions & 0 deletions src/api/order/order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,26 @@ export interface Order {
/** Value must be positive, and it is number of seconds that SMART order would be parked for at IBKRATS before being routed to exchange. */
postToAts?: number;

/** Accepts a list with parameters obtained from advancedOrderRejectJson */
advancedErrorOverride?: string;

/** Used by brokers and advisors when manually entering, modifying or cancelling orders at the direction of a client. Only used when allocating orders to specific groups or accounts. Excluding "All" group. */
manualOrderTime?: string;

/** Defines the minimum trade quantity to fill. For IBKRATS orders. */
minTradeQty?: number;

/** Defines the minimum size to compete. For IBKRATS orders. */
minCompeteSize?: number;

/** Dpecifies the offset Off The Midpoint that will be applied to the order. For IBKRATS orders. */
competeAgainstBestOffset?: number;

/** This offset is applied when the spread is an even number of cents wide. This offset must be in whole-penny increments or zero. For IBKRATS orders. */
midOffsetAtWhole?: number;

/** This offset is applied when the spread is an odd number of cents wide. This offset must be in half-penny increments. For IBKRATS orders. */
midOffsetAtHalf?: number;
}

export default Order;
3 changes: 3 additions & 0 deletions src/common/errorCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
/* eslint-disable @typescript-eslint/no-duplicate-enum-values */

export enum ErrorCode {
/** Order Canceled - reason: */
ORDER_CANCELLED = 202,

/** Already connected. */
ALREADY_CONNECTED = 501,

Expand Down
36 changes: 33 additions & 3 deletions src/core/io/encoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,10 +518,26 @@ function tagValuesToTokens(tagValues: TagValue[]): unknown[] {
/**
* Encode a CANCEL_ORDER message to an array of tokens.
*/
cancelOrder(id: number): void {
cancelOrder(id: number, manualCancelOrderTime?: string): void {
const version = 1;

this.sendMsg(OUT_MSG_ID.CANCEL_ORDER, version, id);
if (
this.serverVersion < MIN_SERVER_VER.MANUAL_ORDER_TIME &&
manualCancelOrderTime?.length
) {
return this.emitError(
"It does not support manual order cancel time attribute",
ErrorCode.UPDATE_TWS,
id,
);
}

const tokens: unknown[] = [OUT_MSG_ID.CANCEL_ORDER, version, id];

if (this.serverVersion >= MIN_SERVER_VER.MANUAL_ORDER_TIME)
tokens.push(manualCancelOrderTime);

this.sendMsg(tokens);
}

/**
Expand Down Expand Up @@ -1053,7 +1069,7 @@ function tagValuesToTokens(tagValues: TagValue[]): unknown[] {

if (
this.serverVersion < MIN_SERVER_VER.ADVANCED_ORDER_REJECT &&
order.advancedErrorOverride != null
order.advancedErrorOverride != undefined
) {
return this.emitError(
"It does not support advanced error override attribute",
Expand All @@ -1062,6 +1078,17 @@ function tagValuesToTokens(tagValues: TagValue[]): unknown[] {
);
}

if (
this.serverVersion < MIN_SERVER_VER.MANUAL_ORDER_TIME &&
order.manualOrderTime?.length
) {
return this.emitError(
"It does not support manual order time attribute",
ErrorCode.UPDATE_TWS,
id,
);
}

const version = this.serverVersion < MIN_SERVER_VER.NOT_HELD ? 27 : 45;

// send place order msg
Expand Down Expand Up @@ -1586,6 +1613,9 @@ function tagValuesToTokens(tagValues: TagValue[]): unknown[] {
if (this.serverVersion >= MIN_SERVER_VER.ADVANCED_ORDER_REJECT)
tokens.push(order.advancedErrorOverride);

if (this.serverVersion >= MIN_SERVER_VER.MANUAL_ORDER_TIME)
tokens.push(order.manualOrderTime);

this.sendMsg(tokens);
}

Expand Down
2 changes: 1 addition & 1 deletion src/tests/unit/api/market-scanner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe("IBApi market scanner tests", () => {

test("Scanner parameters", (done) => {
ib.on(EventName.scannerParameters, (xml: string) => {
const match = '<?xml version="1.0" encoding="UTF-8"?>';
const match = '<?xml version="1.0" encoding="UTF-8"?>'; // eslint-disable-line quotes
expect(xml.substring(0, match.length)).toEqual(match);
ib.disconnect();
})
Expand Down
124 changes: 124 additions & 0 deletions src/tests/unit/api/order/cancelOrder.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* This file implement test code for the placeOrder function .
*/
import {
Contract,
ErrorCode,
EventName,
IBApi,
Order,
OrderAction,
OrderType,
SecType,
} from "../../../..";
// import OptionType from "../../../../api/data/enum/option-type";
import configuration from "../../../../common/configuration";
import logger from "../../../../common/logger";

const awaitTimeout = (delay: number): Promise<unknown> =>

Check warning on line 18 in src/tests/unit/api/order/cancelOrder.test.ts

View workflow job for this annotation

GitHub Actions / job

'awaitTimeout' is assigned a value but never used. Allowed unused vars must match /^_/u
new Promise((resolve): NodeJS.Timeout => setTimeout(resolve, delay * 1000));

describe("CancelOrder", () => {
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
});
// logger.info("IBApi created");
});

afterEach(() => {
if (ib) {
ib.disconnect();
ib = undefined;
}
// logger.info("IBApi disconnected");
});

test("cancelOrder", (done) => {
let refId: number;

const contract: Contract = {
symbol: "AAPL",
exchange: "SMART",
currency: "USD",
secType: SecType.STK,
};
const order: Order = {
orderType: OrderType.LMT,
action: OrderAction.BUY,
lmtPrice: 1,
orderId: refId,
totalQuantity: 1,
// account: "DU123567",
tif: "DAY",
transmit: true,
};

let order_found = false;
ib.once(EventName.nextValidId, (orderId: number) => {
refId = orderId;
ib.placeOrder(refId, contract, order);
})
.on(EventName.openOrder, (orderId, _contract, _order, _orderState) => {
expect(orderId).toEqual(refId);
if (orderId === refId) {
order_found = true;
ib.cancelOrder(refId);
}
})
.on(
EventName.orderStatus,
(
orderId,
_status,
_filled,
_remaining,
_avgFillPrice,
_permId?,
_parentId?,
_lastFillPrice?,
_clientId?,
_whyHeld?,
_mktCapPrice?,
) => {
expect(orderId).toEqual(refId);
},
)
.on(EventName.openOrderEnd, () => {})
.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 &&
order_found
) {
done();
} else {
done(msg);
}
}
},
);

ib.connect().reqOpenOrders();
});
});
Loading

0 comments on commit 91988f7

Please sign in to comment.