src/core/io/encoder.ts
import { MarketDataType, OrderCancel, WhatToShow } from "../..";
import { ScanCode } from "../../api-next/market-scanner/market-scanner";
import { Contract } from "../../api/contract/contract";
import WshEventData from "../../api/contract/wsh";
import TagValue from "../../api/data/container/tag-value";
import FADataType from "../../api/data/enum/fa-data-type";
import LogLevel from "../../api/data/enum/log-level";
import MIN_SERVER_VER from "../../api/data/enum/min-server-version";
import OptionExerciseAction from "../../api/data/enum/option-exercise-action";
import SecType from "../../api/data/enum/sec-type";
import { BarSizeSetting } from "../../api/historical/bar-size-setting";
import { ScannerSubscription } from "../../api/market/scannerSubscription";
import { TickByTickDataType } from "../../api/market/tickByTickDataType";
import ExecutionCondition from "../../api/order/condition/execution-condition";
import MarginCondition from "../../api/order/condition/margin-condition";
import PercentChangeCondition from "../../api/order/condition/percent-change-condition";
import PriceCondition from "../../api/order/condition/price-condition";
import TimeCondition from "../../api/order/condition/time-condition";
import VolumeCondition from "../../api/order/condition/volume-condition";
import { OrderConditionType } from "../../api/order/enum/order-condition-type";
import {
isPegBenchOrder,
isPegBestOrder,
isPegMidOrder,
isVolOrder,
} from "../../api/order/enum/orderType";
import {
Order,
isCompeteAgainstBestOffsetUpToMid,
} from "../../api/order/order";
import { ExecutionFilter } from "../../api/report/executionFilter";
import { ErrorCode } from "../../common/errorCode";
/**
* @internal
*
* Outgoing message IDs.
*/
export enum OUT_MSG_ID {
REQ_MKT_DATA = 1,
CANCEL_MKT_DATA = 2,
PLACE_ORDER = 3,
CANCEL_ORDER = 4,
REQ_OPEN_ORDERS = 5,
REQ_ACCOUNT_DATA = 6,
REQ_EXECUTIONS = 7,
REQ_IDS = 8,
REQ_CONTRACT_DATA = 9,
REQ_MKT_DEPTH = 10,
CANCEL_MKT_DEPTH = 11,
REQ_NEWS_BULLETINS = 12,
CANCEL_NEWS_BULLETINS = 13,
SET_SERVER_LOGLEVEL = 14,
REQ_AUTO_OPEN_ORDERS = 15,
REQ_ALL_OPEN_ORDERS = 16,
REQ_MANAGED_ACCTS = 17,
REQ_FA = 18,
REPLACE_FA = 19,
REQ_HISTORICAL_DATA = 20,
EXERCISE_OPTIONS = 21,
REQ_SCANNER_SUBSCRIPTION = 22,
CANCEL_SCANNER_SUBSCRIPTION = 23,
REQ_SCANNER_PARAMETERS = 24,
CANCEL_HISTORICAL_DATA = 25,
REQ_CURRENT_TIME = 49,
REQ_REAL_TIME_BARS = 50,
CANCEL_REAL_TIME_BARS = 51,
REQ_FUNDAMENTAL_DATA = 52,
CANCEL_FUNDAMENTAL_DATA = 53,
REQ_CALC_IMPLIED_VOLAT = 54,
REQ_CALC_OPTION_PRICE = 55,
CANCEL_CALC_IMPLIED_VOLAT = 56,
CANCEL_CALC_OPTION_PRICE = 57,
REQ_GLOBAL_CANCEL = 58,
REQ_MARKET_DATA_TYPE = 59,
REQ_POSITIONS = 61,
REQ_ACCOUNT_SUMMARY = 62,
CANCEL_ACCOUNT_SUMMARY = 63,
CANCEL_POSITIONS = 64,
VERIFY_REQUEST = 65, // not implemented
VERIFY_MESSAGE = 66, // not implemented
QUERY_DISPLAY_GROUPS = 67,
SUBSCRIBE_TO_GROUP_EVENTS = 68,
UPDATE_DISPLAY_GROUP = 69,
UNSUBSCRIBE_FROM_GROUP_EVENTS = 70,
START_API = 71, // sent by [[Socket]]
VERIFY_AND_AUTH_REQUEST = 72, // not implemented
VERIFY_AND_AUTH_MESSAGE = 73, // not implemented
REQ_POSITIONS_MULTI = 74,
CANCEL_POSITIONS_MULTI = 75,
REQ_ACCOUNT_UPDATES_MULTI = 76,
CANCEL_ACCOUNT_UPDATES_MULTI = 77,
REQ_SEC_DEF_OPT_PARAMS = 78,
REQ_SOFT_DOLLAR_TIERS = 79,
REQ_FAMILY_CODES = 80,
REQ_MATCHING_SYMBOLS = 81,
REQ_MKT_DEPTH_EXCHANGES = 82,
REQ_SMART_COMPONENTS = 83,
REQ_NEWS_ARTICLE = 84,
REQ_NEWS_PROVIDERS = 85,
REQ_HISTORICAL_NEWS = 86,
REQ_HEAD_TIMESTAMP = 87,
REQ_HISTOGRAM_DATA = 88,
CANCEL_HISTOGRAM_DATA = 89,
CANCEL_HEAD_TIMESTAMP = 90,
REQ_MARKET_RULE = 91,
REQ_PNL = 92,
CANCEL_PNL = 93,
REQ_PNL_SINGLE = 94,
CANCEL_PNL_SINGLE = 95,
REQ_HISTORICAL_TICKS = 96,
REQ_TICK_BY_TICK_DATA = 97,
CANCEL_TICK_BY_TICK_DATA = 98,
REQ_COMPLETED_ORDERS = 99,
REQ_WSH_META_DATA = 100,
CANCEL_WSH_META_DATA = 101,
REQ_WSH_EVENT_DATA = 102,
CANCEL_WSH_EVENT_DATA = 103,
REQ_USER_INFO = 104,
}
/**
* @internal
* Undefined integer value
*/
const Integer_MAX_VALUE = 2147483647;
/**
* @internal
*
* Helper function to nullify a number of Number.MAX_VALUE
*
* Note that this is still there for legacy reason.
* With v1.2.1, Number.MAX_VALUE has been dropped and replaced with undefined on decoder-side,
* but we still support it on encoder-side to avoid any unreasonably large numbers submitted to
* TWS if any client code was not migrated.
*/
function nullifyMax(number?: number): number | null {
if (number === Number.MAX_VALUE) {
return null;
} else {
return number;
}
}
/**
* @internal
*
* Callback interface of the [[Encoder]].
*/
export interface EncoderCallbacks {
/** Get the IB API server version. */
readonly serverVersion: number;
/**
* Send a message to the server connection.
*
* @param args Array of tokens to send.
* Can contain nested arrays.
*/
sendMsg(...tokens: unknown[]): void;
/**
* Emit an error event to public API interface.
*
* @param errMsg The error test message.
* @param data Additional error data (optional).
*/
emitError(errMsg: string, code: number, reqId: number): void;
}
/**
* @internal
*
* Class for encoding messages and sending raw token data back to
*/
export class Encoder {
/**
* Create an [[Encoder]] object for encoding messages to token data.
*
* @param callback A [[EncoderCallbacks]] implementation.
*/
constructor(private callback: EncoderCallbacks) {}
/** Get the API server version. */
private get serverVersion(): number {
return this.callback.serverVersion;
}
/**
* Send a message to the server connection.
*
* @param args Array of tokens to send.
*/
private sendMsg(...args: unknown[]): void {
this.callback.sendMsg(args);
}
/**
* Emit an error event to public API interface.
*
* @param errMsg The error test message.
* @param data Additional error data (optional).
*/
private emitError(errMsg: string, code: ErrorCode, reqId: number): void {
this.callback.emitError(
`Server Version ${this.serverVersion}: ${errMsg}`,
code,
reqId,
);
}
/**
* Encode a [[Contract]] to an array of tokens.
*/
private encodeContract(contract: Contract): unknown[] {
return [
contract.conId,
contract.symbol,
contract.secType as string,
contract.lastTradeDateOrContractMonth,
contract.strike,
contract.right,
contract.multiplier,
contract.exchange,
contract.primaryExch,
contract.currency,
contract.localSymbol,
contract.tradingClass,
contract.includeExpired ? 1 : 0,
];
}
/**
* Encode a [[TagValue]] array to a string token.
*/
private encodeTagValues(tagValues: TagValue[] | undefined): string {
let result = "";
tagValues?.forEach((tv) => {
result += `${tv.tag}=${tv.value};`;
});
return result;
}
/**
* @@internal
*
* Helper function convert an array of [[TagValue]] to a flat [tag,value] tuple array.
*/
/*
function tagValuesToTokens(tagValues: TagValue[]): unknown[] {
const result: unknown[] = new Array(tagValues.length * 2);
let pos = 0;
for (let i = 0; i < tagValues.length; i++) {
result[pos++] = tagValues[i].tag;
result[pos++] = tagValues[i].tag;
}
return result;
}*/
/**
* Encode a calculateImpliedVolatility message to an array of tokens.
*/
calculateImpliedVolatility(
reqId: number,
contract: Contract,
optionPrice: number,
underPrice: number,
//reserved for future use, must be blank
impliedVolatilityOptions?: TagValue[],
): void {
if (this.serverVersion < MIN_SERVER_VER.REQ_CALC_IMPLIED_VOLAT) {
return this.emitError(
"It does not support calculate implied volatility requests.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
if (this.serverVersion < MIN_SERVER_VER.TRADING_CLASS) {
if (contract.tradingClass === undefined || contract.tradingClass === "") {
return this.emitError(
"It does not support tradingClass parameter in calculateImpliedVolatility.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
}
const version = 2;
const tokens: unknown[] = [
OUT_MSG_ID.REQ_CALC_IMPLIED_VOLAT,
version,
reqId,
];
// send contract fields
tokens.push(contract.conId);
tokens.push(contract.symbol);
tokens.push(contract.secType);
tokens.push(contract.lastTradeDateOrContractMonth);
tokens.push(contract.strike);
tokens.push(contract.right);
tokens.push(contract.multiplier);
tokens.push(contract.exchange);
tokens.push(contract.primaryExch);
tokens.push(contract.currency);
tokens.push(contract.localSymbol);
if (this.serverVersion >= MIN_SERVER_VER.TRADING_CLASS) {
tokens.push(contract.tradingClass);
}
tokens.push(optionPrice);
tokens.push(underPrice);
if (this.serverVersion >= MIN_SERVER_VER.LINKING) {
tokens.push(this.encodeTagValues(impliedVolatilityOptions));
}
this.sendMsg(tokens);
}
/**
* Encode a calculateOptionPrice message to an array of tokens.
*/
calculateOptionPrice(
reqId: number,
contract: Contract,
volatility: number,
underPrice: number,
//reserved for future use, must be blank
optionPriceOptions?: TagValue[],
): void {
if (this.serverVersion < MIN_SERVER_VER.REQ_CALC_OPTION_PRICE) {
return this.emitError(
"It does not support calculate option price requests.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
if (this.serverVersion < MIN_SERVER_VER.TRADING_CLASS) {
if (!!contract.tradingClass) {
return this.emitError(
"It does not support tradingClass parameter in calculateOptionPrice.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
}
const version = 2;
const tokens: unknown[] = [
OUT_MSG_ID.REQ_CALC_OPTION_PRICE,
version,
reqId,
];
// send contract fields
tokens.push(contract.conId);
tokens.push(contract.symbol);
tokens.push(contract.secType);
tokens.push(contract.lastTradeDateOrContractMonth);
tokens.push(contract.strike);
tokens.push(contract.right);
tokens.push(contract.multiplier);
tokens.push(contract.exchange);
tokens.push(contract.primaryExch);
tokens.push(contract.currency);
tokens.push(contract.localSymbol);
if (this.serverVersion >= MIN_SERVER_VER.TRADING_CLASS) {
tokens.push(contract.tradingClass);
}
tokens.push(volatility);
tokens.push(underPrice);
if (this.serverVersion >= MIN_SERVER_VER.LINKING) {
tokens.push(this.encodeTagValues(optionPriceOptions));
}
this.sendMsg(tokens);
}
/**
* Encode a CANCEL_ACCOUNT_SUMMARY message to an array of tokens.
*/
cancelAccountSummary(reqId: number): void {
if (this.serverVersion < MIN_SERVER_VER.ACCT_SUMMARY) {
return this.emitError(
"It not support account summary cancellation.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
const version = 1;
this.sendMsg(OUT_MSG_ID.CANCEL_ACCOUNT_SUMMARY, version, reqId);
}
/**
* Encode a CANCEL_ACCOUNT_UPDATES_MULTI message to an array of tokens.
*/
cancelAccountUpdatesMulti(reqId: number): void {
const version = 2;
this.sendMsg(OUT_MSG_ID.CANCEL_ACCOUNT_UPDATES_MULTI, version, reqId);
}
/**
* Encode a CANCEL_CALC_IMPLIED_VOLAT message to an array of tokens.
*/
cancelCalculateImpliedVolatility(reqId: number): void {
if (this.serverVersion < MIN_SERVER_VER.CANCEL_CALC_IMPLIED_VOLAT) {
return this.emitError(
"It does not support calculate implied volatility cancellation.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
const version = 1;
this.sendMsg(OUT_MSG_ID.CANCEL_CALC_IMPLIED_VOLAT, version, reqId);
}
/**
* Encode a CANCEL_CALC_OPTION_PRICE message to an array of tokens.
*/
cancelCalculateOptionPrice(reqId: number): void {
if (this.serverVersion < MIN_SERVER_VER.CANCEL_CALC_OPTION_PRICE) {
return this.emitError(
"It does not support calculate option price cancellation.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
const version = 1;
this.sendMsg(OUT_MSG_ID.CANCEL_CALC_OPTION_PRICE, version, reqId);
}
/**
* Encode a CANCEL_FUNDAMENTAL_DATA message to an array of tokens.
*/
cancelFundamentalData(reqId: number): void {
if (this.serverVersion < MIN_SERVER_VER.FUNDAMENTAL_DATA) {
return this.emitError(
"It does not support fundamental data requests.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
const version = 1;
this.sendMsg(OUT_MSG_ID.CANCEL_FUNDAMENTAL_DATA, version, reqId);
}
/**
* Encode a CANCEL_HISTORICAL_DATA message to an array of tokens.
*/
cancelHistoricalData(tickerId: number): void {
if (this.serverVersion < 24) {
return this.emitError(
"It does not support historical data query cancellation.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
const version = 1;
this.sendMsg(OUT_MSG_ID.CANCEL_HISTORICAL_DATA, version, tickerId);
}
/**
* Encode a CANCEL_MKT_DATA message to an array of tokens.
*/
cancelMktData(tickerId: number): void {
const version = 1;
this.sendMsg(OUT_MSG_ID.CANCEL_MKT_DATA, version, tickerId);
}
/**
* Encode a CANCEL_MKT_DEPTH message to an array of tokens.
*/
cancelMktDepth(tickerId: number, isSmartDepth: boolean): void {
if (this.serverVersion < 6) {
return this.emitError(
"This feature is only available for versions of TWS >=6.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
if (this.serverVersion < MIN_SERVER_VER.SMART_DEPTH && isSmartDepth) {
return this.emitError(
"It does not support SMART depth cancel.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
const version = 1;
const tokens: unknown[] = [OUT_MSG_ID.CANCEL_MKT_DEPTH, version, tickerId];
if (this.serverVersion >= MIN_SERVER_VER.SMART_DEPTH) {
tokens.push(isSmartDepth);
}
this.sendMsg(tokens);
}
/**
* Encode a CANCEL_NEWS_BULLETINS message to an array of tokens.
*/
cancelNewsBulletins(): void {
const version = 1;
this.sendMsg(OUT_MSG_ID.CANCEL_NEWS_BULLETINS, version);
}
/**
* Encode a CANCEL_ORDER message to an array of tokens.
*/
cancelOrder(orderId: number, orderCancel: OrderCancel): void {
if (
this.serverVersion < MIN_SERVER_VER.MANUAL_ORDER_TIME &&
orderCancel.manualOrderCancelTime?.length
) {
return this.emitError(
"It does not support manual order cancel time attribute",
ErrorCode.UPDATE_TWS,
orderId,
);
}
if (this.serverVersion < MIN_SERVER_VER.CME_TAGGING_FIELDS) {
if (
orderCancel.extOperator?.length ||
orderCancel.manualOrderIndicator != undefined
) {
return this.emitError(
"It does not support ext operator and manual order indicator parameters",
ErrorCode.UPDATE_TWS,
orderId,
);
}
}
const version = 1;
// send cancel order msg
const tokens: unknown[] = [OUT_MSG_ID.CANCEL_ORDER];
if (this.serverVersion < MIN_SERVER_VER.CME_TAGGING_FIELDS)
tokens.push(version);
tokens.push(orderId);
if (this.serverVersion >= MIN_SERVER_VER.MANUAL_ORDER_TIME)
tokens.push(orderCancel.manualOrderCancelTime);
if (
this.serverVersion >= MIN_SERVER_VER.RFQ_FIELDS &&
this.serverVersion < MIN_SERVER_VER.UNDO_RFQ_FIELDS
) {
tokens.push("");
tokens.push("");
tokens.push(Integer_MAX_VALUE);
}
if (this.serverVersion >= MIN_SERVER_VER.CME_TAGGING_FIELDS) {
tokens.push(orderCancel.extOperator);
tokens.push(orderCancel.manualOrderIndicator ?? Integer_MAX_VALUE);
}
this.sendMsg(tokens);
}
/**
* Encode a CANCEL_POSITIONS message to an array of tokens.
*/
cancelPositions(): void {
if (this.serverVersion < MIN_SERVER_VER.ACCT_SUMMARY) {
return this.emitError(
"It does not support position cancellation.",
ErrorCode.UPDATE_TWS,
-1,
);
}
const version = 1;
this.sendMsg(OUT_MSG_ID.CANCEL_POSITIONS, version);
}
/**
* Encode a CANCEL_REAL_TIME_BARS message to an array of tokens.
*/
cancelRealTimeBars(tickerId: number): void {
if (this.serverVersion < MIN_SERVER_VER.REAL_TIME_BARS) {
return this.emitError(
"It does not support realtime bar data query cancellation.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
const version = 1;
this.sendMsg(OUT_MSG_ID.CANCEL_REAL_TIME_BARS, version, tickerId);
}
/**
* Encode a CANCEL_SCANNER_SUBSCRIPTION message to an array of tokens.
*/
cancelScannerSubscription(tickerId: number): void {
if (this.serverVersion < 24) {
return this.emitError(
"It does not support API scanner subscription.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
const version = 1;
this.sendMsg(OUT_MSG_ID.CANCEL_SCANNER_SUBSCRIPTION, version, tickerId);
}
/**
* Encode a EXERCISE_OPTIONS message to an array of tokens.
*/
exerciseOptions(
tickerId: number,
contract: Contract,
exerciseAction: OptionExerciseAction,
exerciseQuantity: number,
account: string,
override: number,
manualOrderTime: string = "",
customerAccount: string = "",
professionalCustomer: boolean = false,
): void {
const version = 2;
if (this.serverVersion < 21) {
return this.emitError(
"It does not support options exercise from the API.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
if (this.serverVersion < MIN_SERVER_VER.TRADING_CLASS) {
if (!!contract.tradingClass || contract.conId != undefined) {
return this.emitError(
"It does not support conId and tradingClass parameters in exerciseOptions.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
}
if (
this.serverVersion < MIN_SERVER_VER.MANUAL_ORDER_TIME_EXERCISE_OPTIONS &&
manualOrderTime
) {
return this.emitError(
"It does not support manual order time parameter in exerciseOptions.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
if (this.serverVersion < MIN_SERVER_VER.CUSTOMER_ACCOUNT) {
if (customerAccount) {
return this.emitError(
"It does not support customer account parameter in exerciseOptions.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.PROFESSIONAL_CUSTOMER) {
if (professionalCustomer) {
return this.emitError(
"It does not support professional customer parameter in exerciseOptions.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
}
const tokens: unknown[] = [OUT_MSG_ID.EXERCISE_OPTIONS, version, tickerId];
// send contract fields
if (this.serverVersion >= MIN_SERVER_VER.TRADING_CLASS) {
tokens.push(contract.conId);
}
tokens.push(contract.symbol);
tokens.push(contract.secType);
tokens.push(contract.lastTradeDateOrContractMonth);
tokens.push(contract.strike);
tokens.push(contract.right);
tokens.push(contract.multiplier);
tokens.push(contract.exchange);
tokens.push(contract.currency);
tokens.push(contract.localSymbol);
if (this.serverVersion >= MIN_SERVER_VER.TRADING_CLASS) {
tokens.push(contract.tradingClass);
}
tokens.push(exerciseAction);
tokens.push(exerciseQuantity);
tokens.push(account);
tokens.push(override);
if (
this.serverVersion >= MIN_SERVER_VER.MANUAL_ORDER_TIME_EXERCISE_OPTIONS
) {
tokens.push(manualOrderTime);
}
if (this.serverVersion >= MIN_SERVER_VER.CUSTOMER_ACCOUNT) {
tokens.push(customerAccount);
}
if (this.serverVersion >= MIN_SERVER_VER.PROFESSIONAL_CUSTOMER) {
tokens.push(professionalCustomer);
}
this.sendMsg(tokens);
}
/**
* Encode a PLACE_ORDER message to an array of tokens.
*/
placeOrder(id: number, contract: Contract, order: Order): void {
if (this.serverVersion < MIN_SERVER_VER.SCALE_ORDERS) {
if (
order.scaleInitLevelSize !== undefined ||
order.scalePriceIncrement !== undefined
) {
return this.emitError(
"It does not support Scale orders.",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.SSHORT_COMBO_LEGS) {
contract.comboLegs?.forEach((comboLeg) => {
if (comboLeg.shortSaleSlot || !!comboLeg.designatedLocation) {
return this.emitError(
"It does not support SSHORT flag for combo legs.",
ErrorCode.UPDATE_TWS,
id,
);
}
});
}
if (this.serverVersion < MIN_SERVER_VER.WHAT_IF_ORDERS) {
if (order.whatIf) {
return this.emitError(
"It does not support what-if orders.",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.DELTA_NEUTRAL) {
if (contract.deltaNeutralContract) {
return this.emitError(
"It does not support delta-neutral orders.",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.SCALE_ORDERS2) {
if (order.scaleSubsLevelSize !== undefined) {
return this.emitError(
"It does not support Subsequent Level Size for Scale orders.",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.ALGO_ORDERS) {
if (!!order.algoStrategy) {
return this.emitError(
"It does not support algo orders.",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.NOT_HELD) {
if (order.notHeld) {
return this.emitError(
"It does not support notHeld parameter.",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.SEC_ID_TYPE) {
if (!!contract.secIdType || !!contract.secId) {
return this.emitError(
"It does not support secIdType and secId parameters.",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.PLACE_ORDER_CONID) {
if (contract.conId != undefined) {
return this.emitError(
"It does not support conId parameter.",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.SSHORTX) {
if (order.exemptCode !== -1) {
return this.emitError(
"It does not support exemptCode parameter.",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.SSHORTX) {
contract.comboLegs?.forEach((comboLeg) => {
if (comboLeg.exemptCode !== -1) {
return this.emitError(
"It does not support exemptCode parameter.",
ErrorCode.UPDATE_TWS,
id,
);
}
});
}
if (this.serverVersion < MIN_SERVER_VER.HEDGE_ORDERS) {
if (!!order.hedgeType) {
return this.emitError(
"It does not support hedge orders.",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.OPT_OUT_SMART_ROUTING) {
if (order.optOutSmartRouting) {
return this.emitError(
"It does not support optOutSmartRouting parameter.",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.DELTA_NEUTRAL_CONID) {
if (
order.deltaNeutralConId != undefined ||
!!order.deltaNeutralSettlingFirm ||
!!order.deltaNeutralClearingAccount ||
!!order.deltaNeutralClearingIntent
) {
return this.emitError(
"It does not support deltaNeutral parameters: ConId, SettlingFirm, ClearingAccount, ClearingIntent.",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.DELTA_NEUTRAL_OPEN_CLOSE) {
if (
!!order.deltaNeutralOpenClose ||
order.deltaNeutralShortSale ||
order.deltaNeutralShortSaleSlot != undefined ||
!!order.deltaNeutralDesignatedLocation
) {
return this.emitError(
"It does not support deltaNeutral parameters: OpenClose, ShortSale, ShortSaleSlot, DesignatedLocation.",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.SCALE_ORDERS3) {
if (
order.scalePriceIncrement != undefined &&
order.scalePriceIncrement !== undefined
) {
if (
order.scalePriceAdjustValue !== undefined ||
order.scalePriceAdjustInterval !== undefined ||
order.scaleProfitOffset !== undefined ||
order.scaleAutoReset ||
order.scaleInitPosition !== undefined ||
order.scaleInitFillQty !== undefined ||
order.scaleRandomPercent
) {
return this.emitError(
"It does not support Scale order parameters: PriceAdjustValue, PriceAdjustInterval, ProfitOffset, AutoReset, InitPosition, InitFillQty and RandomPercent",
ErrorCode.UPDATE_TWS,
id,
);
}
}
}
if (
this.serverVersion < MIN_SERVER_VER.ORDER_COMBO_LEGS_PRICE &&
SecType.BAG === contract.secType?.toUpperCase()
) {
order.orderComboLegs?.forEach((orderComboLeg) => {
if (orderComboLeg.price !== undefined) {
return this.emitError(
"It does not support per-leg prices for order combo legs.",
ErrorCode.UPDATE_TWS,
id,
);
}
});
}
if (this.serverVersion < MIN_SERVER_VER.TRAILING_PERCENT) {
if (order.trailingPercent !== undefined) {
return this.emitError(
"It does not support trailing percent parameter.",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.TRADING_CLASS) {
if (!!contract.tradingClass) {
return this.emitError(
"It does not support tradingClass parameters in placeOrder.",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.SCALE_TABLE) {
if (
!!order.scaleTable ||
!!order.activeStartTime ||
!!order.activeStopTime
) {
return this.emitError(
"It does not support scaleTable, activeStartTime and activeStopTime parameters.",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.ALGO_ID && !!order.algoId) {
return this.emitError(
"It does not support algoId parameter",
ErrorCode.UPDATE_TWS,
id,
);
}
if (this.serverVersion < MIN_SERVER_VER.SCALE_TABLE) {
if (
!!order.scaleTable ||
order.activeStartTime?.length ||
order.activeStopTime?.length
) {
return this.emitError(
"It does not support scaleTable, activeStartTime and activeStopTime parameters.",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.ORDER_SOLICITED) {
if (order.solicited) {
return this.emitError(
"It does not support order solicited parameter.",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.MODELS_SUPPORT) {
if (!!order.modelCode) {
return this.emitError(
"It does not support model code parameter.",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (
this.serverVersion < MIN_SERVER_VER.EXT_OPERATOR &&
!!order.extOperator
) {
return this.emitError(
"It does not support ext operator",
ErrorCode.UPDATE_TWS,
id,
);
}
if (
this.serverVersion < MIN_SERVER_VER.SOFT_DOLLAR_TIER &&
(!!order.softDollarTier?.name || !!order.softDollarTier?.value)
) {
return this.emitError(
"It does not support soft dollar tier",
ErrorCode.UPDATE_TWS,
id,
);
}
if (this.serverVersion < MIN_SERVER_VER.CASH_QTY) {
if (order.cashQty !== undefined) {
return this.emitError(
"It does not support cash quantity parameter",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (
this.serverVersion < MIN_SERVER_VER.DECISION_MAKER &&
(!!order.mifid2DecisionMaker || !!order.mifid2DecisionAlgo)
) {
return this.emitError(
"It does not support MIFID II decision maker parameters",
ErrorCode.UPDATE_TWS,
id,
);
}
if (
this.serverVersion < MIN_SERVER_VER.MIFID_EXECUTION &&
(!!order.mifid2ExecutionTrader || !!order.mifid2ExecutionAlgo)
) {
return this.emitError(
"It does not support MIFID II execution parameters",
ErrorCode.UPDATE_TWS,
id,
);
}
if (
this.serverVersion < MIN_SERVER_VER.AUTO_PRICE_FOR_HEDGE &&
order.dontUseAutoPriceForHedge
) {
return this.emitError(
"It does not support don't use auto price for hedge parameter.",
ErrorCode.UPDATE_TWS,
id,
);
}
if (
this.serverVersion < MIN_SERVER_VER.ORDER_CONTAINER &&
order.isOmsContainer
) {
return this.emitError(
"It does not support oms container parameter.",
ErrorCode.UPDATE_TWS,
id,
);
}
if (
this.serverVersion < MIN_SERVER_VER.D_PEG_ORDERS &&
order.discretionaryUpToLimitPrice
) {
return this.emitError(
"It does not support D-Peg orders.",
ErrorCode.UPDATE_TWS,
id,
);
}
if (
this.serverVersion < MIN_SERVER_VER.PRICE_MGMT_ALGO &&
order.usePriceMgmtAlgo != null
) {
return this.emitError(
"It does not support price management algo parameter",
ErrorCode.UPDATE_TWS,
id,
);
}
if (
this.serverVersion < MIN_SERVER_VER.DURATION &&
order.duration != undefined
) {
return this.emitError(
"It does not support duration attribute",
ErrorCode.UPDATE_TWS,
id,
);
}
if (
this.serverVersion < MIN_SERVER_VER.POST_TO_ATS &&
order.postToAts != undefined
) {
return this.emitError(
"It does not support postToAts attribute",
ErrorCode.UPDATE_TWS,
id,
);
}
if (
this.serverVersion < MIN_SERVER_VER.AUTO_CANCEL_PARENT &&
order.autoCancelParent != null
) {
return this.emitError(
"It does not support autoCancelParent attribute",
ErrorCode.UPDATE_TWS,
id,
);
}
if (
this.serverVersion < MIN_SERVER_VER.ADVANCED_ORDER_REJECT &&
order.advancedErrorOverride != undefined
) {
return this.emitError(
"It does not support advanced error override attribute",
ErrorCode.UPDATE_TWS,
id,
);
}
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,
);
}
if (
this.serverVersion < MIN_SERVER_VER.PEGBEST_PEGMID_OFFSETS &&
(order.minTradeQty !== undefined ||
order.minCompeteSize !== undefined ||
order.competeAgainstBestOffset !== undefined ||
order.midOffsetAtWhole !== undefined ||
order.midOffsetAtHalf !== undefined)
) {
return this.emitError(
"It does not support PEG BEST / PEG MID order parameters: minTradeQty, minCompeteSize, competeAgainstBestOffset, midOffsetAtWhole and midOffsetAtHalf",
ErrorCode.UPDATE_TWS,
id,
);
}
if (this.serverVersion < MIN_SERVER_VER.CUSTOMER_ACCOUNT) {
if (order.customerAccount) {
return this.emitError(
"It does not support customer account parameter",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.PROFESSIONAL_CUSTOMER) {
if (order.professionalCustomer) {
return this.emitError(
"It does not support professional customer parameter",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.INCLUDE_OVERNIGHT) {
if (order.includeOvernight) {
return this.emitError(
"It does not support include overnight parameter",
ErrorCode.UPDATE_TWS,
id,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.CME_TAGGING_FIELDS) {
if (order.manualOrderIndicator) {
return this.emitError(
"It does not support manual order indicator parameter",
ErrorCode.UPDATE_TWS,
id,
);
}
}
const version = this.serverVersion < MIN_SERVER_VER.NOT_HELD ? 27 : 45;
// send place order msg
const tokens: unknown[] = [OUT_MSG_ID.PLACE_ORDER];
if (this.serverVersion < MIN_SERVER_VER.ORDER_CONTAINER) {
tokens.push(version);
}
tokens.push(id);
// send contract fields
if (this.serverVersion >= MIN_SERVER_VER.PLACE_ORDER_CONID) {
tokens.push(contract.conId);
}
tokens.push(contract.symbol);
tokens.push(contract.secType);
tokens.push(contract.lastTradeDateOrContractMonth);
tokens.push(contract.strike);
tokens.push(contract.right);
if (this.serverVersion >= 15) {
tokens.push(contract.multiplier);
}
tokens.push(contract.exchange);
if (this.serverVersion >= 14) {
tokens.push(contract.primaryExch);
}
tokens.push(contract.currency);
if (this.serverVersion >= 2) {
tokens.push(contract.localSymbol);
}
if (this.serverVersion >= MIN_SERVER_VER.TRADING_CLASS) {
tokens.push(contract.tradingClass);
}
if (this.serverVersion >= MIN_SERVER_VER.SEC_ID_TYPE) {
tokens.push(contract.secIdType);
tokens.push(contract.secId);
}
// send main order fields
tokens.push(order.action);
if (this.serverVersion >= MIN_SERVER_VER.FRACTIONAL_POSITIONS) {
tokens.push(order.totalQuantity);
} else {
tokens.push(
order.totalQuantity ? Math.round(order.totalQuantity) : undefined,
);
}
tokens.push(order.orderType);
if (this.serverVersion < MIN_SERVER_VER.ORDER_COMBO_LEGS_PRICE) {
tokens.push(order.lmtPrice ?? 0);
} else {
tokens.push(nullifyMax(order.lmtPrice));
}
if (this.serverVersion < MIN_SERVER_VER.TRAILING_PERCENT) {
tokens.push(order.auxPrice ?? 0);
} else {
tokens.push(nullifyMax(order.auxPrice));
}
// send extended order fields
tokens.push(order.tif);
tokens.push(order.ocaGroup);
tokens.push(order.account);
tokens.push(order.openClose);
tokens.push(order.origin);
tokens.push(order.orderRef);
tokens.push(order.transmit);
if (this.serverVersion >= 4) {
tokens.push(order.parentId);
}
if (this.serverVersion >= 5) {
tokens.push(order.blockOrder);
tokens.push(order.sweepToFill);
tokens.push(order.displaySize);
tokens.push(order.triggerMethod);
if (this.serverVersion < 38) {
// will never happen
tokens.push(/* order.ignoreRth */ false);
} else {
tokens.push(order.outsideRth);
}
}
if (this.serverVersion >= 7) {
tokens.push(order.hidden);
}
// Send combo legs for BAG requests
if (
this.serverVersion >= 8 &&
SecType.BAG === contract.secType?.toUpperCase()
) {
if (!contract.comboLegs?.length) {
tokens.push(0);
} else {
tokens.push(contract.comboLegs.length);
contract.comboLegs.forEach((comboLeg) => {
tokens.push(comboLeg.conId);
tokens.push(comboLeg.ratio);
tokens.push(comboLeg.action);
tokens.push(comboLeg.exchange);
tokens.push(comboLeg.openClose);
if (this.serverVersion >= MIN_SERVER_VER.SSHORT_COMBO_LEGS) {
tokens.push(comboLeg.shortSaleSlot);
tokens.push(comboLeg.designatedLocation);
}
if (this.serverVersion >= MIN_SERVER_VER.SSHORTX_OLD) {
tokens.push(comboLeg.exemptCode);
}
});
}
}
// Send order combo legs for BAG requests
if (
this.serverVersion >= MIN_SERVER_VER.ORDER_COMBO_LEGS_PRICE &&
SecType.BAG === contract.secType?.toUpperCase()
) {
if (!order.orderComboLegs?.length) {
tokens.push(0);
} else {
tokens.push(order.orderComboLegs.length);
order.orderComboLegs.forEach(function (orderComboLeg) {
tokens.push(nullifyMax(orderComboLeg.price));
});
}
}
if (
this.serverVersion >= MIN_SERVER_VER.SMART_COMBO_ROUTING_PARAMS &&
SecType.BAG === contract.secType?.toUpperCase()
) {
const smartComboRoutingParamsCount = !order.smartComboRoutingParams
? 0
: order.smartComboRoutingParams.length;
tokens.push(smartComboRoutingParamsCount);
if (smartComboRoutingParamsCount > 0) {
order.smartComboRoutingParams?.forEach((param) => {
tokens.push(param.tag);
tokens.push(param.value);
});
}
}
if (this.serverVersion >= 9) {
// send deprecated sharesAllocation field
tokens.push("");
}
if (this.serverVersion >= 10) {
tokens.push(order.discretionaryAmt);
}
if (this.serverVersion >= 11) {
tokens.push(order.goodAfterTime);
}
if (this.serverVersion >= 12) {
tokens.push(order.goodTillDate);
}
if (this.serverVersion >= 13) {
// srv v13 and above
tokens.push(order.faGroup);
tokens.push(order.faMethod);
tokens.push(order.faPercentage);
if (this.serverVersion < MIN_SERVER_VER.FA_PROFILE_DESUPPORT)
tokens.push(""); // send deprecated faProfile field
}
if (this.serverVersion >= MIN_SERVER_VER.MODELS_SUPPORT) {
tokens.push(order.modelCode);
}
if (this.serverVersion >= 18) {
// institutional short sale slot fields.
tokens.push(order.shortSaleSlot); // 0 only for retail, 1 or 2 only for institution.
tokens.push(order.designatedLocation); // only populate when order.shortSaleSlot = 2.
}
if (this.serverVersion >= MIN_SERVER_VER.SSHORTX_OLD) {
tokens.push(order.exemptCode);
}
if (this.serverVersion >= 19) {
tokens.push(order.ocaType);
if (this.serverVersion < 38) {
// will never happen
tokens.push(/* order.rthOnly */ false);
}
tokens.push(order.rule80A);
tokens.push(order.settlingFirm);
tokens.push(order.allOrNone);
tokens.push(nullifyMax(order.minQty));
tokens.push(nullifyMax(order.percentOffset));
tokens.push(order.eTradeOnly);
tokens.push(order.firmQuoteOnly);
tokens.push(nullifyMax(order.nbboPriceCap));
tokens.push(nullifyMax(order.auctionStrategy));
tokens.push(nullifyMax(order.startingPrice));
tokens.push(nullifyMax(order.stockRefPrice));
tokens.push(nullifyMax(order.delta));
// Volatility orders had specific watermark price attribs in server version 26
const lower =
this.serverVersion === 26 && isVolOrder(order.orderType)
? undefined
: order.stockRangeLower;
const upper =
this.serverVersion === 26 && isVolOrder(order.orderType)
? undefined
: order.stockRangeUpper;
tokens.push(lower);
tokens.push(upper);
}
if (this.serverVersion >= 22) {
tokens.push(order.overridePercentageConstraints);
}
if (this.serverVersion >= 26) {
// Volatility orders
tokens.push(nullifyMax(order.volatility));
tokens.push(nullifyMax(order.volatilityType));
if (this.serverVersion < 28) {
tokens.push(order.deltaNeutralOrderType?.toUpperCase() === "MKT");
} else {
tokens.push(order.deltaNeutralOrderType);
tokens.push(nullifyMax(order.deltaNeutralAuxPrice));
if (
this.serverVersion >= MIN_SERVER_VER.DELTA_NEUTRAL_CONID &&
!!order.deltaNeutralOrderType
) {
tokens.push(order.deltaNeutralConId);
tokens.push(order.deltaNeutralSettlingFirm);
tokens.push(order.deltaNeutralClearingAccount);
tokens.push(order.deltaNeutralClearingIntent);
}
if (
this.serverVersion >= MIN_SERVER_VER.DELTA_NEUTRAL_OPEN_CLOSE &&
!!order.deltaNeutralOrderType
) {
tokens.push(order.deltaNeutralOpenClose);
tokens.push(order.deltaNeutralShortSale);
tokens.push(order.deltaNeutralShortSaleSlot);
tokens.push(order.deltaNeutralDesignatedLocation);
}
}
tokens.push(order.continuousUpdate);
if (this.serverVersion === 26) {
// Volatility orders had specific watermark price attribs in server version 26
const lower = isVolOrder(order.orderType)
? order.stockRangeLower
: undefined;
const upper = isVolOrder(order.orderType)
? order.stockRangeUpper
: undefined;
tokens.push(nullifyMax(lower));
tokens.push(nullifyMax(upper));
}
tokens.push(nullifyMax(order.referencePriceType));
}
if (this.serverVersion >= 30) {
// TRAIL_STOP_LIMIT stop price
tokens.push(nullifyMax(order.trailStopPrice));
}
if (this.serverVersion >= MIN_SERVER_VER.TRAILING_PERCENT) {
tokens.push(nullifyMax(order.trailingPercent));
}
if (this.serverVersion >= MIN_SERVER_VER.SCALE_ORDERS) {
if (this.serverVersion >= MIN_SERVER_VER.SCALE_ORDERS2) {
tokens.push(nullifyMax(order.scaleInitLevelSize));
tokens.push(nullifyMax(order.scaleSubsLevelSize));
} else {
tokens.push("");
tokens.push(nullifyMax(order.scaleInitLevelSize));
}
tokens.push(nullifyMax(order.scalePriceIncrement));
}
if (
this.serverVersion >= MIN_SERVER_VER.SCALE_ORDERS3 &&
order.scalePriceIncrement !== undefined
) {
tokens.push(nullifyMax(order.scalePriceAdjustValue));
tokens.push(nullifyMax(order.scalePriceAdjustInterval));
tokens.push(nullifyMax(order.scaleProfitOffset));
tokens.push(order.scaleAutoReset);
tokens.push(nullifyMax(order.scaleInitPosition));
tokens.push(nullifyMax(order.scaleInitFillQty));
tokens.push(order.scaleRandomPercent);
}
if (this.serverVersion >= MIN_SERVER_VER.SCALE_TABLE) {
tokens.push(order.scaleTable);
tokens.push(order.activeStartTime);
tokens.push(order.activeStopTime);
}
if (this.serverVersion >= MIN_SERVER_VER.HEDGE_ORDERS) {
tokens.push(order.hedgeType);
if (!!order.hedgeType) {
tokens.push(order.hedgeParam);
}
}
if (this.serverVersion >= MIN_SERVER_VER.OPT_OUT_SMART_ROUTING) {
tokens.push(order.optOutSmartRouting);
}
if (this.serverVersion >= MIN_SERVER_VER.PTA_ORDERS) {
tokens.push(order.clearingAccount);
tokens.push(order.clearingIntent);
}
if (this.serverVersion >= MIN_SERVER_VER.NOT_HELD) {
tokens.push(order.notHeld);
}
if (this.serverVersion >= MIN_SERVER_VER.DELTA_NEUTRAL) {
if (contract.deltaNeutralContract) {
tokens.push(true);
tokens.push(contract.deltaNeutralContract.conId);
tokens.push(contract.deltaNeutralContract.delta);
tokens.push(contract.deltaNeutralContract.price);
} else {
tokens.push(false);
}
}
if (this.serverVersion >= MIN_SERVER_VER.ALGO_ORDERS) {
tokens.push(order.algoStrategy);
if (!!order.algoStrategy) {
const algoParamsCount = order.algoParams?.length
? order.algoParams.length
: 0;
tokens.push(algoParamsCount);
if (algoParamsCount > 0) {
order.algoParams?.forEach((param) => {
tokens.push(param.tag);
tokens.push(param.value);
});
}
}
}
if (this.serverVersion >= MIN_SERVER_VER.ALGO_ID) {
tokens.push(order.algoId);
}
if (this.serverVersion >= MIN_SERVER_VER.WHAT_IF_ORDERS) {
tokens.push(order.whatIf);
}
// send orderMiscOptions parameter
if (this.serverVersion >= MIN_SERVER_VER.LINKING) {
tokens.push(order.orderMiscOptions);
}
if (this.serverVersion >= MIN_SERVER_VER.ORDER_SOLICITED) {
tokens.push(order.solicited);
}
if (this.serverVersion >= MIN_SERVER_VER.RANDOMIZE_SIZE_AND_PRICE) {
tokens.push(order.randomizeSize);
tokens.push(order.randomizePrice);
}
if (this.serverVersion >= MIN_SERVER_VER.PEGGED_TO_BENCHMARK) {
if (isPegBenchOrder(order.orderType)) {
tokens.push(order.referenceContractId);
tokens.push(order.isPeggedChangeAmountDecrease);
tokens.push(order.peggedChangeAmount);
tokens.push(order.referenceChangeAmount);
tokens.push(order.referenceExchangeId);
}
const nConditions = order.conditions ? order.conditions.length : 0;
tokens.push(nConditions);
if (nConditions > 0) {
order.conditions?.forEach((cond) => {
tokens.push(cond.type);
tokens.push(cond.conjunctionConnection);
switch (cond.type) {
case OrderConditionType.Execution: {
const execCond = cond as ExecutionCondition;
tokens.push(execCond.secType);
tokens.push(execCond.exchange);
tokens.push(execCond.symbol);
break;
}
case OrderConditionType.Margin: {
const marginCond = cond as MarginCondition;
// OperatorCondition
tokens.push(marginCond.isMore);
tokens.push(marginCond.strValue);
break;
}
case OrderConditionType.PercentChange: {
const percentChangeCond = cond as PercentChangeCondition;
// OperatorCondition
tokens.push(percentChangeCond.isMore);
tokens.push(percentChangeCond.strValue);
// ContractCondition
tokens.push(percentChangeCond.conId);
tokens.push(percentChangeCond.exchange);
break;
}
case OrderConditionType.Price: {
const priceConditionCond = cond as PriceCondition;
// OperatorCondition
tokens.push(priceConditionCond.isMore);
tokens.push(priceConditionCond.strValue);
// ContractCondition
tokens.push(priceConditionCond.conId);
tokens.push(priceConditionCond.exchange);
// PriceCondition
tokens.push(priceConditionCond.triggerMethod);
break;
}
case OrderConditionType.Time: {
const timeConditionCond = cond as TimeCondition;
// OperatorCondition
tokens.push(timeConditionCond.isMore);
tokens.push(timeConditionCond.strValue);
break;
}
case OrderConditionType.Volume: {
const timeConditionCond = cond as VolumeCondition;
// OperatorCondition
tokens.push(timeConditionCond.isMore);
tokens.push(timeConditionCond.strValue);
// ContractCondition
tokens.push(timeConditionCond.conId);
tokens.push(timeConditionCond.exchange);
break;
}
}
});
tokens.push(order.conditionsIgnoreRth);
tokens.push(order.conditionsCancelOrder);
}
tokens.push(order.adjustedOrderType);
tokens.push(order.triggerPrice);
tokens.push(order.lmtPriceOffset);
tokens.push(order.adjustedStopPrice);
tokens.push(order.adjustedStopLimitPrice);
tokens.push(order.adjustedTrailingAmount);
tokens.push(order.adjustableTrailingUnit);
}
if (this.serverVersion >= MIN_SERVER_VER.EXT_OPERATOR) {
tokens.push(order.extOperator);
}
if (this.serverVersion >= MIN_SERVER_VER.SOFT_DOLLAR_TIER) {
tokens.push(order.softDollarTier?.name ? order.softDollarTier.name : "");
tokens.push(
order.softDollarTier?.value ? order.softDollarTier.value : "",
);
}
if (this.serverVersion >= MIN_SERVER_VER.CASH_QTY) {
tokens.push(nullifyMax(order.cashQty));
}
if (this.serverVersion >= MIN_SERVER_VER.DECISION_MAKER) {
tokens.push(order.mifid2DecisionMaker);
tokens.push(order.mifid2DecisionAlgo);
}
if (this.serverVersion >= MIN_SERVER_VER.MIFID_EXECUTION) {
tokens.push(order.mifid2ExecutionTrader);
tokens.push(order.mifid2ExecutionAlgo);
}
if (this.serverVersion >= MIN_SERVER_VER.AUTO_PRICE_FOR_HEDGE) {
tokens.push(order.dontUseAutoPriceForHedge);
}
if (this.serverVersion >= MIN_SERVER_VER.ORDER_CONTAINER) {
tokens.push(order.isOmsContainer);
}
if (this.serverVersion >= MIN_SERVER_VER.D_PEG_ORDERS) {
tokens.push(order.discretionaryUpToLimitPrice);
}
if (this.serverVersion >= MIN_SERVER_VER.PRICE_MGMT_ALGO) {
tokens.push(order.usePriceMgmtAlgo);
}
if (this.serverVersion >= MIN_SERVER_VER.DURATION) {
tokens.push(order.duration);
}
if (this.serverVersion >= MIN_SERVER_VER.POST_TO_ATS) {
tokens.push(order.postToAts);
}
if (this.serverVersion >= MIN_SERVER_VER.AUTO_CANCEL_PARENT) {
tokens.push(order.autoCancelParent);
}
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);
if (this.serverVersion >= MIN_SERVER_VER.PEGBEST_PEGMID_OFFSETS) {
let sendMidOffsets = false;
if (contract.exchange == "IBKRATS") {
tokens.push(order.minTradeQty);
}
if (isPegBestOrder(order.orderType)) {
tokens.push(order.minCompeteSize);
tokens.push(order.competeAgainstBestOffset);
if (isCompeteAgainstBestOffsetUpToMid(order)) sendMidOffsets = true;
} else if (isPegMidOrder(order.orderType)) {
sendMidOffsets = true;
}
if (sendMidOffsets) {
tokens.push(order.midOffsetAtWhole);
tokens.push(order.midOffsetAtHalf);
}
}
if (this.serverVersion >= MIN_SERVER_VER.CUSTOMER_ACCOUNT) {
tokens.push(order.customerAccount);
}
if (this.serverVersion >= MIN_SERVER_VER.PROFESSIONAL_CUSTOMER) {
tokens.push(order.professionalCustomer);
}
if (
this.serverVersion >= MIN_SERVER_VER.RFQ_FIELDS &&
this.serverVersion < MIN_SERVER_VER.UNDO_RFQ_FIELDS
) {
tokens.push("");
tokens.push(Integer_MAX_VALUE);
}
if (this.serverVersion >= MIN_SERVER_VER.INCLUDE_OVERNIGHT) {
tokens.push(order.includeOvernight);
}
if (this.serverVersion >= MIN_SERVER_VER.CME_TAGGING_FIELDS) {
tokens.push(order.manualOrderIndicator);
}
this.sendMsg(tokens);
}
/**
* Encode a REPLACE_FA message to an array of tokens.
*/
replaceFA(reqId: number, faDataType: FADataType, xml: string): void {
if (
this.serverVersion >= MIN_SERVER_VER.FA_PROFILE_DESUPPORT &&
faDataType == FADataType.PROFILES
) {
return this.emitError(
"FA Profile is not supported anymore, use FA Group instead.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
const version = 1;
const tokens: unknown[] = [OUT_MSG_ID.REPLACE_FA, version, faDataType, xml];
if (this.serverVersion >= MIN_SERVER_VER.REPLACE_FA_END) {
tokens.push(reqId);
}
this.sendMsg(tokens);
}
/**
* Encode a REQ_ACCOUNT_SUMMARY message to an array of tokens.
*/
reqAccountSummary(reqId: number, group: string, tags: string): void {
if (this.serverVersion < MIN_SERVER_VER.ACCT_SUMMARY) {
return this.emitError(
"It does not support account summary requests.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
const version = 1;
this.sendMsg(OUT_MSG_ID.REQ_ACCOUNT_SUMMARY, version, reqId, group, tags);
}
/**
* Encode a REQ_PNL message to an array of tokens.
*/
reqPnL(reqId: number, account: string, modelCode: string | null): void {
if (this.serverVersion < MIN_SERVER_VER.PNL) {
return this.emitError(
"It does not support pnl requests.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
this.sendMsg(OUT_MSG_ID.REQ_PNL, reqId, account, modelCode);
}
/**
* Encode a REQ_PNL message.
*/
cancelPnL(reqId: number): void {
if (this.serverVersion < MIN_SERVER_VER.PNL) {
return this.emitError(
"It does not support pnl requests.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
this.sendMsg(OUT_MSG_ID.CANCEL_PNL, reqId);
}
/**
* Encode a REQ_PNL_SINGLE message.
*/
reqPnLSingle(
reqId: number,
account: string,
modelCode: string | null,
conId: number,
): void {
if (this.serverVersion < MIN_SERVER_VER.PNL) {
return this.emitError(
"It does not support pnl requests.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
this.sendMsg(OUT_MSG_ID.REQ_PNL_SINGLE, reqId, account, modelCode, conId);
}
/**
* Encode a CANCEL_PNL_SINGLE message.
*/
cancelPnLSingle(reqId: number): void {
if (this.serverVersion < MIN_SERVER_VER.PNL) {
return this.emitError(
"It does not support pnl requests.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
this.sendMsg(OUT_MSG_ID.CANCEL_PNL_SINGLE, reqId);
}
/**
* Encode a REQ_ACCOUNT_DATA message.
*/
reqAccountUpdates(subscribe: boolean, acctCode: string): void {
const version = 2;
const tokens: unknown[] = [OUT_MSG_ID.REQ_ACCOUNT_DATA, version, subscribe];
// Send the account code. This will only be used for FA clients
if (this.serverVersion >= 9) {
tokens.push(acctCode);
}
this.sendMsg(tokens);
}
/**
* Encode a REQ_ACCOUNT_UPDATES_MULTI message.
*/
reqAccountUpdatesMulti(
reqId: number,
acctCode: string,
modelCode: string,
ledgerAndNLV: boolean,
): void {
const version = 2;
this.sendMsg(
OUT_MSG_ID.REQ_ACCOUNT_UPDATES_MULTI,
version,
reqId,
acctCode,
modelCode,
ledgerAndNLV,
);
}
/**
* Encode a REQ_ALL_OPEN_ORDERS message.
*/
reqAllOpenOrders(): void {
const version = 1;
this.sendMsg(OUT_MSG_ID.REQ_ALL_OPEN_ORDERS, version);
}
/**
* Encode a REQ_AUTO_OPEN_ORDERS message.
*/
reqAutoOpenOrders(bAutoBind: boolean): void {
const version = 1;
this.sendMsg(OUT_MSG_ID.REQ_AUTO_OPEN_ORDERS, version, bAutoBind);
}
/**
* Encode a REQ_HEAD_TIMESTAMP message.
*/
reqHeadTimestamp(
reqId: number,
contract: Contract,
whatToShow: WhatToShow,
useRTH: boolean,
formatDate: number,
): void {
if (this.serverVersion < MIN_SERVER_VER.REQ_HEAD_TIMESTAMP) {
return this.emitError(
"It does not support reqHeadTimeStamp",
ErrorCode.UPDATE_TWS,
reqId,
);
}
this.sendMsg(
OUT_MSG_ID.REQ_HEAD_TIMESTAMP,
reqId,
this.encodeContract(contract),
useRTH ? 1 : 0,
whatToShow,
formatDate,
);
}
/**
* Encode a REQ_CONTRACT_DATA message.
*/
reqContractDetails(reqId: number, contract: Contract): void {
if (this.serverVersion < 4) {
return this.emitError(
"This feature is only available for versions of TWS >=4",
ErrorCode.UPDATE_TWS,
reqId,
);
}
if (this.serverVersion < MIN_SERVER_VER.SEC_ID_TYPE) {
if (!!contract.secIdType || !!contract.secId) {
return this.emitError(
"It does not support secIdType and secId parameters.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.TRADING_CLASS) {
if (!!contract.tradingClass) {
return this.emitError(
"It does not support tradingClass parameter in reqContractDetails.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.LINKING) {
if (!!contract.primaryExch) {
return this.emitError(
"It does not support primaryExchange parameter in reqContractDetails.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.BOND_ISSUERID) {
if (contract.issuerId) {
return this.emitError(
"It does not support issuerId parameter in reqContractDetails.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
}
const version = 8;
// send req mkt data msg
const args: unknown[] = [OUT_MSG_ID.REQ_CONTRACT_DATA, version];
if (this.serverVersion >= MIN_SERVER_VER.CONTRACT_DATA_CHAIN) {
args.push(reqId);
}
// send contract fields
if (this.serverVersion >= MIN_SERVER_VER.CONTRACT_CONID) {
args.push(contract.conId);
}
args.push(contract.symbol);
args.push(contract.secType);
args.push(contract.lastTradeDateOrContractMonth);
args.push(contract.strike);
args.push(contract.right);
if (this.serverVersion >= 15) {
args.push(contract.multiplier);
}
if (this.serverVersion >= MIN_SERVER_VER.PRIMARYEXCH) {
args.push(contract.exchange);
args.push(contract.primaryExch);
} else if (this.serverVersion >= MIN_SERVER_VER.LINKING) {
if (
!!contract.primaryExch &&
("BEST" === contract.exchange || "SMART" === contract.exchange)
) {
args.push(contract.exchange + ":" + contract.primaryExch);
} else {
args.push(contract.exchange);
}
}
args.push(contract.currency);
args.push(contract.localSymbol);
if (this.serverVersion >= MIN_SERVER_VER.TRADING_CLASS) {
args.push(contract.tradingClass);
}
if (this.serverVersion >= 31) {
args.push(contract.includeExpired);
}
if (this.serverVersion >= MIN_SERVER_VER.SEC_ID_TYPE) {
args.push(contract.secIdType);
args.push(contract.secId);
}
if (this.serverVersion >= MIN_SERVER_VER.BOND_ISSUERID)
args.push(contract.issuerId);
this.sendMsg(args);
}
/**
* Encode a REQ_CURRENT_TIME message.
*/
reqCurrentTime(): void {
if (this.serverVersion < 33) {
return this.emitError(
"It does not support current time requests.",
ErrorCode.UPDATE_TWS,
-1,
);
}
const version = 1;
this.sendMsg(OUT_MSG_ID.REQ_CURRENT_TIME, version);
}
/**
* Encode a REQ_EXECUTIONS message.
*/
reqExecutions(reqId: number, filter: ExecutionFilter): void {
// NOTE: Time format must be 'yyyymmdd-hh:mm:ss' E.g. '20030702-14:55'
const version = 3;
// send req open orders msg
const args: unknown[] = [OUT_MSG_ID.REQ_EXECUTIONS, version];
if (this.serverVersion >= MIN_SERVER_VER.EXECUTION_DATA_CHAIN) {
args.push(reqId);
}
// Send the execution rpt filter data
if (this.serverVersion >= 9) {
args.push(filter.clientId);
args.push(filter.acctCode);
// Note that the valid format for filter.time is "yyyymmdd-hh:mm:ss"
args.push(filter.time);
args.push(filter.symbol);
args.push(filter.secType);
args.push(filter.exchange);
args.push(filter.side);
}
this.sendMsg(args);
}
/**
* Encode a REQ_FUNDAMENTAL_DATA message.
*/
reqFundamentalData(
reqId: number,
contract: Contract,
reportType: string,
fundamentalDataOptions: TagValue[],
): void {
if (this.serverVersion < MIN_SERVER_VER.FUNDAMENTAL_DATA) {
return this.emitError(
"It does not support fundamental data requests.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
if (this.serverVersion < MIN_SERVER_VER.TRADING_CLASS) {
if (contract.conId != undefined) {
return this.emitError(
"It does not support conId parameter in reqFundamentalData.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
}
const version = 2;
// send req fund data msg
const tokens: unknown[] = [OUT_MSG_ID.REQ_FUNDAMENTAL_DATA, version, reqId];
// send contract fields
if (this.serverVersion >= MIN_SERVER_VER.TRADING_CLASS) {
tokens.push(contract.conId);
}
tokens.push(contract.symbol);
tokens.push(contract.secType);
tokens.push(contract.exchange);
tokens.push(contract.primaryExch);
tokens.push(contract.currency);
tokens.push(contract.localSymbol);
tokens.push(reportType);
if (this.serverVersion >= MIN_SERVER_VER.LINKING) {
tokens.push(this.encodeTagValues(fundamentalDataOptions));
}
this.sendMsg(tokens);
}
/**
* Encode a REQ_GLOBAL_CANCEL message.
*/
reqGlobalCancel(orderCancel: OrderCancel): void {
if (this.serverVersion < MIN_SERVER_VER.REQ_GLOBAL_CANCEL) {
return this.emitError(
"It does not support globalCancel requests.",
ErrorCode.UPDATE_TWS,
ErrorCode.NO_VALID_ID,
);
}
if (this.serverVersion < MIN_SERVER_VER.CME_TAGGING_FIELDS) {
if (
orderCancel.extOperator?.length ||
orderCancel.manualOrderIndicator != undefined
) {
return this.emitError(
"It does not support ext operator and manual order indicator parameters",
ErrorCode.UPDATE_TWS,
ErrorCode.NO_VALID_ID,
);
}
}
const version = 1;
// send request global cancel msg
const tokens: unknown[] = [OUT_MSG_ID.REQ_GLOBAL_CANCEL];
if (this.serverVersion < MIN_SERVER_VER.CME_TAGGING_FIELDS) {
tokens.push(version);
}
if (this.serverVersion >= MIN_SERVER_VER.CME_TAGGING_FIELDS) {
tokens.push(orderCancel.extOperator);
tokens.push(orderCancel.manualOrderIndicator ?? Integer_MAX_VALUE);
}
this.sendMsg(tokens);
}
/**
* Encode a REQ_HISTORICAL_DATA message.
*/
reqHistoricalData(
tickerId: number,
contract: Contract,
endDateTime: string,
durationStr: string,
barSizeSetting: BarSizeSetting,
whatToShow: WhatToShow,
useRTH: number | boolean,
formatDate: number,
keepUpToDate: boolean,
chartOptions?: TagValue[],
): void {
const version = 6;
if (this.serverVersion < 16) {
return this.emitError(
"It does not support historical data backfill.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
if (this.serverVersion < MIN_SERVER_VER.TRADING_CLASS) {
if (!!contract.tradingClass || contract.conId != undefined) {
return this.emitError(
"It does not support conId and tradingClass parameters in reqHistoricalData.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.HISTORICAL_SCHEDULE) {
if (
typeof whatToShow === "string" &&
whatToShow.toUpperCase() === "SCHEDULE"
) {
return this.emitError(
"It does not support requesting of historical schedule.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
}
const tokens: unknown[] = [OUT_MSG_ID.REQ_HISTORICAL_DATA];
if (this.serverVersion < MIN_SERVER_VER.SYNT_REALTIME_BARS) {
tokens.push(version);
}
tokens.push(tickerId);
// send contract fields
if (this.serverVersion >= MIN_SERVER_VER.TRADING_CLASS) {
tokens.push(contract.conId);
}
tokens.push(contract.symbol);
tokens.push(contract.secType);
tokens.push(contract.lastTradeDateOrContractMonth);
tokens.push(contract.strike);
tokens.push(contract.right);
tokens.push(contract.multiplier);
tokens.push(contract.exchange);
tokens.push(contract.primaryExch);
tokens.push(contract.currency);
tokens.push(contract.localSymbol);
if (this.serverVersion >= MIN_SERVER_VER.TRADING_CLASS) {
tokens.push(contract.tradingClass);
}
if (this.serverVersion >= 31) {
tokens.push(!!contract.includeExpired);
}
if (this.serverVersion >= 20) {
tokens.push(endDateTime);
tokens.push(barSizeSetting);
}
tokens.push(durationStr);
tokens.push(useRTH);
tokens.push(whatToShow);
if (this.serverVersion > 16) {
tokens.push(formatDate);
}
if (SecType.BAG === contract.secType?.toUpperCase()) {
if (!contract.comboLegs) {
tokens.push(0);
} else {
tokens.push(contract.comboLegs.length);
contract.comboLegs.forEach((comboLeg) => {
tokens.push(comboLeg.conId);
tokens.push(comboLeg.ratio);
tokens.push(comboLeg.action);
tokens.push(comboLeg.exchange);
});
}
}
if (this.serverVersion >= MIN_SERVER_VER.SYNT_REALTIME_BARS) {
tokens.push(keepUpToDate);
}
if (this.serverVersion >= MIN_SERVER_VER.LINKING) {
tokens.push(this.encodeTagValues(chartOptions));
}
this.sendMsg(tokens);
}
/**
* Encode a REQ_HISTORICAL_TICKS message.
*/
reqHistoricalTicks(
tickerId: number,
contract: Contract,
startDateTime: string,
endDateTime: string,
numberOfTicks: number,
whatToShow: WhatToShow,
useRth: number | boolean,
ignoreSize: boolean,
miscOptions?: TagValue[],
): void {
if (this.serverVersion < MIN_SERVER_VER.HISTORICAL_TICKS) {
return this.emitError(
"It does not support historical ticks request.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
const args: unknown[] = [
OUT_MSG_ID.REQ_HISTORICAL_TICKS,
tickerId,
...this.encodeContract(contract),
];
args.push(startDateTime);
args.push(endDateTime);
args.push(numberOfTicks);
args.push(whatToShow);
args.push(useRth);
args.push(ignoreSize);
args.push(this.encodeTagValues(miscOptions));
this.sendMsg(args);
}
/**
* Encode a REQ_TICK_BY_TICK_DATA message.
*/
encodeReqTickByTickData(
reqId: number,
contract: Contract,
tickType: TickByTickDataType,
numberOfTicks: number,
ignoreSize: boolean,
): unknown[] {
const args: unknown[] = [OUT_MSG_ID.REQ_TICK_BY_TICK_DATA, reqId];
args.push(contract.conId);
args.push(contract.symbol);
args.push(contract.secType);
args.push(contract.lastTradeDateOrContractMonth);
args.push(contract.strike);
args.push(contract.right);
args.push(contract.multiplier);
args.push(contract.exchange);
args.push(contract.primaryExch);
args.push(contract.currency);
args.push(contract.localSymbol);
args.push(contract.tradingClass);
args.push(tickType);
if (this.serverVersion >= MIN_SERVER_VER.TICK_BY_TICK_IGNORE_SIZE) {
args.push(numberOfTicks);
args.push(ignoreSize);
}
return args;
}
reqTickByTickData(
reqId: number,
contract: Contract,
tickType: TickByTickDataType,
numberOfTicks: number,
ignoreSize: boolean,
): void {
if (this.serverVersion < MIN_SERVER_VER.TICK_BY_TICK) {
this.emitError(
"It does not support tick-by-tick data requests.",
ErrorCode.UPDATE_TWS,
reqId,
);
} else if (
this.serverVersion < MIN_SERVER_VER.TICK_BY_TICK_IGNORE_SIZE &&
(numberOfTicks != 0 || ignoreSize)
) {
this.emitError(
"It does not support ignoreSize and numberOfTicks parameters in tick-by-tick data requests.",
ErrorCode.UPDATE_TWS,
reqId,
);
} else {
const args: unknown[] = this.encodeReqTickByTickData(
reqId,
contract,
tickType,
numberOfTicks,
ignoreSize,
);
this.sendMsg(args);
}
}
/**
* Encode a CANCEL_TICK_BY_TICK_DATA message.
*/
cancelTickByTickData(reqId: number): void {
if (this.serverVersion < MIN_SERVER_VER.TICK_BY_TICK) {
return this.emitError(
"It does not support tick-by-tick data cancels.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
this.sendMsg(OUT_MSG_ID.CANCEL_TICK_BY_TICK_DATA, reqId);
}
/**
* Encode a REQ_IDS message.
*/
reqIds(numIds: number): void {
const version = 1;
this.sendMsg(OUT_MSG_ID.REQ_IDS, version, numIds);
}
/**
* Encode a REQ_IDS message.
*/
reqManagedAccts(): void {
const version = 1;
this.sendMsg(OUT_MSG_ID.REQ_MANAGED_ACCTS, version);
}
/**
* Encode a REQ_MARKET_DATA_TYPE message.
*/
reqMarketDataType(marketDataType: MarketDataType): void {
if (this.serverVersion < MIN_SERVER_VER.REQ_MARKET_DATA_TYPE) {
return this.emitError(
"It does not support marketDataType requests.",
ErrorCode.UPDATE_TWS,
ErrorCode.NO_VALID_ID,
);
}
const version = 1;
this.sendMsg(OUT_MSG_ID.REQ_MARKET_DATA_TYPE, version, marketDataType);
}
/**
* Encode a REQ_MKT_DATA message.
*/
reqMktData(
tickerId: number,
contract: Contract,
genericTickList: string,
snapshot: boolean,
regulatorySnapshot: boolean,
): void {
if (this.serverVersion < MIN_SERVER_VER.SNAPSHOT_MKT_DATA && snapshot) {
return this.emitError(
"It does not support snapshot market data requests.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
if (this.serverVersion < MIN_SERVER_VER.DELTA_NEUTRAL) {
return this.emitError(
"It does not support delta-neutral orders.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
if (this.serverVersion < MIN_SERVER_VER.REQ_MKT_DATA_CONID) {
return this.emitError(
"It does not support conId parameter.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
if (this.serverVersion < MIN_SERVER_VER.TRADING_CLASS) {
if (!!contract.tradingClass) {
return this.emitError(
"It does not support tradingClass parameter in reqMarketData.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
}
const version = 11;
const args: unknown[] = [OUT_MSG_ID.REQ_MKT_DATA, version, tickerId];
// send contract fields
if (this.serverVersion >= MIN_SERVER_VER.REQ_MKT_DATA_CONID) {
args.push(contract.conId);
}
args.push(contract.symbol);
args.push(contract.secType);
args.push(contract.lastTradeDateOrContractMonth);
args.push(contract.strike);
args.push(contract.right);
if (this.serverVersion >= 15) {
args.push(contract.multiplier);
}
args.push(contract.exchange);
if (this.serverVersion >= 14) {
args.push(contract.primaryExch);
}
args.push(contract.currency);
if (this.serverVersion >= 2) {
args.push(contract.localSymbol);
}
if (this.serverVersion >= MIN_SERVER_VER.TRADING_CLASS) {
args.push(contract.tradingClass);
}
if (
this.serverVersion >= 8 &&
SecType.BAG === contract.secType?.toUpperCase()
) {
if (!contract.comboLegs) {
args.push(0);
} else {
args.push(contract.comboLegs.length);
contract.comboLegs.forEach((comboLeg) => {
args.push(comboLeg.conId);
args.push(comboLeg.ratio);
args.push(comboLeg.action);
args.push(comboLeg.exchange);
});
}
}
if (this.serverVersion >= MIN_SERVER_VER.DELTA_NEUTRAL) {
if (contract.deltaNeutralContract) {
args.push(true);
args.push(contract.deltaNeutralContract.conId);
args.push(contract.deltaNeutralContract.delta);
args.push(contract.deltaNeutralContract.price);
} else {
args.push(false);
}
}
if (this.serverVersion >= 31) {
/*
* Note: Even though SHORTABLE tick type supported only
* starting server version 33 it would be relatively
* expensive to expose this restriction here.
*
* Therefore we are relying on TWS doing validation.
*/
args.push(genericTickList);
}
if (this.serverVersion >= MIN_SERVER_VER.SNAPSHOT_MKT_DATA) {
args.push(snapshot);
}
if (this.serverVersion >= MIN_SERVER_VER.REQ_SMART_COMPONENTS) {
args.push(regulatorySnapshot);
}
if (this.serverVersion >= MIN_SERVER_VER.LINKING) {
args.push("");
}
this.sendMsg(args);
}
/**
* Encode a REQ_MKT_DEPTH message.
*/
reqMktDepth(
tickerId: number,
contract: Contract,
numRows: number,
isSmartDepth: boolean,
mktDepthOptions?: TagValue[],
): void {
if (this.serverVersion < 6) {
return this.emitError(
"This feature is only available for versions of TWS >=6",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
if (this.serverVersion < MIN_SERVER_VER.TRADING_CLASS) {
if (!!contract.tradingClass || contract.conId != undefined) {
return this.emitError(
"It does not support conId and tradingClass parameters in reqMktDepth.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
}
if (this.serverVersion < MIN_SERVER_VER.SMART_DEPTH && isSmartDepth) {
return this.emitError(
"It does not support SMART depth request.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
if (
this.serverVersion < MIN_SERVER_VER.MKT_DEPTH_PRIM_EXCHANGE &&
!!contract.primaryExch
) {
return this.emitError(
"It does not support primaryExch parameter in reqMktDepth.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
const version = 5;
// send req mkt data msg
const tokens: unknown[] = [OUT_MSG_ID.REQ_MKT_DEPTH, version, tickerId];
// send contract fields
if (this.serverVersion >= MIN_SERVER_VER.TRADING_CLASS) {
tokens.push(contract.conId);
}
tokens.push(contract.symbol);
tokens.push(contract.secType);
tokens.push(contract.lastTradeDateOrContractMonth);
tokens.push(contract.strike);
tokens.push(contract.right);
if (this.serverVersion >= 15) {
tokens.push(contract.multiplier);
}
tokens.push(contract.exchange);
if (this.serverVersion >= MIN_SERVER_VER.MKT_DEPTH_PRIM_EXCHANGE) {
tokens.push(contract.primaryExch);
}
tokens.push(contract.currency);
tokens.push(contract.localSymbol);
if (this.serverVersion >= MIN_SERVER_VER.TRADING_CLASS) {
tokens.push(contract.tradingClass);
}
if (this.serverVersion >= 19) {
tokens.push(numRows);
}
if (this.serverVersion >= MIN_SERVER_VER.SMART_DEPTH) {
tokens.push(isSmartDepth);
}
if (this.serverVersion >= MIN_SERVER_VER.LINKING) {
tokens.push(this.encodeTagValues(mktDepthOptions));
}
this.sendMsg(tokens);
}
/**
* Encode a REQ_NEWS_BULLETINS message.
*/
reqNewsBulletins(allMsgs: boolean): void {
const version = 1;
this.sendMsg(OUT_MSG_ID.REQ_NEWS_BULLETINS, version, allMsgs);
}
/**
* Encode a REQ_OPEN_ORDERS message.
*/
reqOpenOrders(): void {
const version = 1;
this.sendMsg(OUT_MSG_ID.REQ_OPEN_ORDERS, version);
}
/**
* Encode a REQ_POSITIONS message.
*/
reqPositions(): void {
if (this.serverVersion < MIN_SERVER_VER.ACCT_SUMMARY) {
return this.emitError(
"It does not support position requests.",
ErrorCode.UPDATE_TWS,
ErrorCode.NO_VALID_ID,
);
}
const version = 1;
this.sendMsg(OUT_MSG_ID.REQ_POSITIONS, version);
}
/**
* Encode a REQ_POSITIONS_MULTI message.
*/
reqPositionsMulti(
reqId: number,
account: string,
modelCode: string | null,
): void {
if (this.serverVersion < MIN_SERVER_VER.MODELS_SUPPORT) {
return this.emitError(
"It does not support position requests.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
const version = 1;
this.sendMsg(
OUT_MSG_ID.REQ_POSITIONS_MULTI,
version,
reqId,
account,
modelCode,
);
}
/**
* Encode a CANCEL_POSITIONS_MULTI message.
*/
cancelPositionsMulti(reqId: number): void {
if (this.serverVersion < MIN_SERVER_VER.MODELS_SUPPORT) {
return this.emitError(
"It does not support positions multi cancellation.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
const version = 1;
this.sendMsg(OUT_MSG_ID.CANCEL_POSITIONS_MULTI, version, reqId);
}
/**
* Encode a REQ_REAL_TIME_BARS message.
*/
reqRealTimeBars(
tickerId: number,
contract: Contract,
barSize: number,
whatToShow: WhatToShow,
useRTH: boolean,
realTimeBarsOptions: TagValue[],
): void {
if (this.serverVersion < MIN_SERVER_VER.REAL_TIME_BARS) {
return this.emitError(
"It does not support real time bars.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
if (this.serverVersion < MIN_SERVER_VER.TRADING_CLASS) {
if (!!contract.tradingClass || contract.conId != undefined) {
return this.emitError(
"It does not support conId and tradingClass parameters in reqRealTimeBars.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
}
const version = 3;
// send req mkt data msg
const tokens: unknown[] = [
OUT_MSG_ID.REQ_REAL_TIME_BARS,
version,
tickerId,
];
// send contract fields
if (this.serverVersion >= MIN_SERVER_VER.TRADING_CLASS) {
tokens.push(contract.conId);
}
tokens.push(contract.symbol);
tokens.push(contract.secType);
tokens.push(contract.lastTradeDateOrContractMonth);
tokens.push(contract.strike);
tokens.push(contract.right);
tokens.push(contract.multiplier);
tokens.push(contract.exchange);
tokens.push(contract.primaryExch);
tokens.push(contract.currency);
tokens.push(contract.localSymbol);
if (this.serverVersion >= MIN_SERVER_VER.TRADING_CLASS) {
tokens.push(contract.tradingClass);
}
tokens.push(barSize); // this parameter is not currently used
tokens.push(whatToShow);
tokens.push(useRTH);
// send realTimeBarsOptions parameter
if (this.serverVersion >= MIN_SERVER_VER.LINKING) {
tokens.push(this.encodeTagValues(realTimeBarsOptions));
}
this.sendMsg(tokens);
}
/**
* Encode a REQ_SCANNER_PARAMETERS message.
*/
reqScannerParameters(): void {
if (this.serverVersion < 24) {
return this.emitError(
"It does not support API scanner subscription.",
ErrorCode.UPDATE_TWS,
ErrorCode.NO_VALID_ID,
);
}
const version = 1;
this.sendMsg(OUT_MSG_ID.REQ_SCANNER_PARAMETERS, version);
}
/**
* Encode a REQ_SCANNER_SUBSCRIPTION message.
*/
reqScannerSubscription(
reqId: number,
subscription: ScannerSubscription,
scannerSubscriptionOptions: TagValue[],
scannerSubscriptionFilterOptions?: TagValue[],
): void {
if (this.serverVersion < 24) {
return this.emitError(
"It does not support API scanner subscription.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
if (
this.serverVersion < MIN_SERVER_VER.SCANNER_GENERIC_OPTS &&
scannerSubscriptionFilterOptions
) {
return this.emitError(
"It does not support API scanner subscription generic filter options.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
const version = 4;
const tokens: unknown[] = [OUT_MSG_ID.REQ_SCANNER_SUBSCRIPTION];
if (this.serverVersion < MIN_SERVER_VER.SCANNER_GENERIC_OPTS) {
tokens.push(version);
}
tokens.push(reqId);
tokens.push(nullifyMax(subscription.numberOfRows));
tokens.push(subscription.instrument);
tokens.push(subscription.locationCode);
tokens.push(ScanCode[subscription.scanCode]);
tokens.push(nullifyMax(subscription.abovePrice));
tokens.push(nullifyMax(subscription.belowPrice));
tokens.push(nullifyMax(subscription.aboveVolume));
tokens.push(nullifyMax(subscription.marketCapAbove));
tokens.push(nullifyMax(subscription.marketCapBelow));
tokens.push(subscription.moodyRatingAbove);
tokens.push(subscription.moodyRatingBelow);
tokens.push(subscription.spRatingAbove);
tokens.push(subscription.spRatingBelow);
tokens.push(subscription.maturityDateAbove);
tokens.push(subscription.maturityDateBelow);
tokens.push(nullifyMax(subscription.couponRateAbove));
tokens.push(nullifyMax(subscription.couponRateBelow));
tokens.push(subscription.excludeConvertible);
if (this.serverVersion >= 25) {
tokens.push(nullifyMax(subscription.averageOptionVolumeAbove));
tokens.push(subscription.scannerSettingPairs);
}
if (this.serverVersion >= 27) {
tokens.push(subscription.stockTypeFilter);
}
if (this.serverVersion >= MIN_SERVER_VER.SCANNER_GENERIC_OPTS) {
tokens.push(this.encodeTagValues(scannerSubscriptionFilterOptions));
}
// send scannerSubscriptionOptions parameter
if (this.serverVersion >= MIN_SERVER_VER.LINKING) {
tokens.push(this.encodeTagValues(scannerSubscriptionOptions));
}
this.sendMsg(tokens);
}
/**
* Encode a REQ_FA message.
*/
requestFA(faDataType: FADataType): void {
if (
this.serverVersion >= MIN_SERVER_VER.FA_PROFILE_DESUPPORT &&
faDataType == FADataType.PROFILES
) {
return this.emitError(
"FA Profile is not supported anymore, use FA Group instead.",
ErrorCode.UPDATE_TWS,
ErrorCode.NO_VALID_ID,
);
}
const version = 1;
this.sendMsg(OUT_MSG_ID.REQ_FA, version, faDataType);
}
/**
* Encode a SET_SERVER_LOGLEVEL message.
*/
setServerLogLevel(logLevel: LogLevel): void {
const version = 1;
this.sendMsg(OUT_MSG_ID.SET_SERVER_LOGLEVEL, version, logLevel);
}
/**
* Encode a QUERY_DISPLAY_GROUPS message.
*/
queryDisplayGroups(reqId: number): void {
const version = 1;
this.sendMsg(OUT_MSG_ID.QUERY_DISPLAY_GROUPS, version, reqId);
}
/**
* Encode a UPDATE_DISPLAY_GROUP message.
*/
updateDisplayGroup(reqId: number, contractInfo: string): void {
const version = 1;
this.sendMsg(OUT_MSG_ID.UPDATE_DISPLAY_GROUP, version, reqId, contractInfo);
}
/**
* Encode a SUBSCRIBE_TO_GROUP_EVENTS message.
*/
subscribeToGroupEvents(reqId: number, groupId: number): void {
const version = 1;
this.sendMsg(OUT_MSG_ID.SUBSCRIBE_TO_GROUP_EVENTS, version, reqId, groupId);
}
/**
* Encode a UNSUBSCRIBE_FROM_GROUP_EVENTS message.
*/
unsubscribeToGroupEvents(reqId: number): void {
const version = 1;
this.sendMsg(OUT_MSG_ID.UNSUBSCRIBE_FROM_GROUP_EVENTS, version, reqId);
}
/**
* Encode a REQ_SEC_DEF_OPT_PARAMS message.
*/
reqSecDefOptParams(
reqId: number,
underlyingSymbol: string,
futFopExchange: string,
underlyingSecType: string,
underlyingConId: number,
): void {
if (this.serverVersion < MIN_SERVER_VER.SEC_DEF_OPT_PARAMS_REQ) {
return this.emitError(
"It does not support reqSecDefOptParams.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
this.sendMsg(
OUT_MSG_ID.REQ_SEC_DEF_OPT_PARAMS,
reqId,
underlyingSymbol,
futFopExchange,
underlyingSecType,
underlyingConId,
);
}
/**
* Encode a REQ_SOFT_DOLLAR_TIERS message.
*/
reqSoftDollarTiers(reqId: number): void {
if (this.serverVersion < MIN_SERVER_VER.SOFT_DOLLAR_TIER) {
return this.emitError(
"It does not support soft dollar tier requests.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
this.sendMsg(OUT_MSG_ID.REQ_SOFT_DOLLAR_TIERS, reqId);
}
/**
* Encode a REQ_FAMILY_CODES message.
*/
reqFamilyCodes(): void {
if (this.serverVersion < MIN_SERVER_VER.REQ_FAMILY_CODES) {
return this.emitError(
"It does not support family codes request.",
ErrorCode.UPDATE_TWS,
ErrorCode.NO_VALID_ID,
);
}
this.sendMsg(OUT_MSG_ID.REQ_FAMILY_CODES);
}
/**
* Encode a REQ_MATCHING_SYMBOLS message.
*/
reqMatchingSymbols(reqId: number, pattern: string): void {
if (this.serverVersion < MIN_SERVER_VER.REQ_MATCHING_SYMBOLS) {
return this.emitError(
"It does not support matching symbols request.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
this.sendMsg(OUT_MSG_ID.REQ_MATCHING_SYMBOLS, reqId, pattern);
}
/**
* Encode a REQ_MKT_DEPTH_EXCHANGES message.
*/
reqMktDepthExchanges(): void {
if (this.serverVersion < MIN_SERVER_VER.REQ_MKT_DEPTH_EXCHANGES) {
return this.emitError(
"It does not support market depth exchanges request.",
ErrorCode.UPDATE_TWS,
ErrorCode.NO_VALID_ID,
);
}
this.sendMsg(OUT_MSG_ID.REQ_MKT_DEPTH_EXCHANGES);
}
/**
* Encode a REQ_SMART_COMPONENTS message.
*/
reqSmartComponents(reqId: number, bboExchange: string): void {
if (this.serverVersion < MIN_SERVER_VER.REQ_SMART_COMPONENTS) {
return this.emitError(
"It does not support smart components request.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
this.sendMsg(OUT_MSG_ID.REQ_SMART_COMPONENTS, reqId, bboExchange);
}
/**
* Encode a REQ_NEWS_ARTICLE message.
*/
reqNewsArticle(
reqId: number,
providerCode: string,
articleId: string,
newsArticleOptions: TagValue[],
): void {
if (this.serverVersion < MIN_SERVER_VER.REQ_NEWS_ARTICLE) {
return this.emitError(
"It does not support news article request.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
const tokens: unknown[] = [
OUT_MSG_ID.REQ_NEWS_ARTICLE,
reqId,
providerCode,
articleId,
];
if (this.serverVersion >= MIN_SERVER_VER.NEWS_QUERY_ORIGINS) {
tokens.push(this.encodeTagValues(newsArticleOptions));
}
this.sendMsg(tokens);
}
/**
* Encode a REQ_NEWS_PROVIDERS message.
*/
reqNewsProviders(): void {
if (this.serverVersion < MIN_SERVER_VER.REQ_SMART_COMPONENTS) {
return this.emitError(
"It does not support smart components request.",
ErrorCode.UPDATE_TWS,
ErrorCode.NO_VALID_ID,
);
}
this.sendMsg(OUT_MSG_ID.REQ_NEWS_PROVIDERS);
}
/**
* Encode a REQ_HISTORICAL_NEWS message.
*/
reqHistoricalNews(
reqId: number,
conId: number,
providerCodes: string,
startDateTime: string,
endDateTime: string,
totalResults: number,
historicalNewsOptions: TagValue[],
): void {
if (this.serverVersion < MIN_SERVER_VER.REQ_HISTORICAL_NEWS) {
return this.emitError(
"It does not support historical news request.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
const tokens: unknown[] = [
OUT_MSG_ID.REQ_NEWS_PROVIDERS,
reqId,
conId,
providerCodes,
startDateTime,
endDateTime,
endDateTime,
totalResults,
];
// send historicalNewsOptions parameter
if (this.serverVersion >= MIN_SERVER_VER.NEWS_QUERY_ORIGINS) {
tokens.push(this.encodeTagValues(historicalNewsOptions));
}
this.sendMsg(tokens);
}
/**
* Encode a REQ_HISTOGRAM_DATA message.
*/
reqHistogramData(
tickerId: number,
contract: Contract,
useRTH: boolean,
timePeriod: string,
): void {
if (this.serverVersion < MIN_SERVER_VER.REQ_HISTOGRAM) {
return this.emitError(
"It does not support histogram requests.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
this.sendMsg(
OUT_MSG_ID.REQ_HISTOGRAM_DATA,
tickerId,
this.encodeContract(contract),
useRTH ? 1 : 0,
timePeriod,
);
}
/**
* Encode a CANCEL_HISTOGRAM_DATA message.
*/
cancelHistogramData(tickerId: number): void {
if (this.serverVersion < MIN_SERVER_VER.REQ_HISTOGRAM) {
return this.emitError(
"It does not support head time stamp requests.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
this.sendMsg(OUT_MSG_ID.CANCEL_HISTOGRAM_DATA, tickerId);
}
/**
* Encode a CANCEL_HISTOGRAM_DATA message.
*/
cancelHeadTimestamp(tickerId: number): void {
if (this.serverVersion < MIN_SERVER_VER.CANCEL_HEADTIMESTAMP) {
return this.emitError(
"It does not support head time stamp requests canceling.",
ErrorCode.UPDATE_TWS,
tickerId,
);
}
this.sendMsg(OUT_MSG_ID.CANCEL_HEAD_TIMESTAMP, tickerId);
}
/**
* Encode a REQ_MARKET_RULE message.
*/
reqMarketRule(marketRuleId: number): void {
if (this.serverVersion < MIN_SERVER_VER.MARKET_RULES) {
return this.emitError(
"It does not support market rule requests.",
ErrorCode.UPDATE_TWS,
marketRuleId,
);
}
this.sendMsg(OUT_MSG_ID.REQ_MARKET_RULE, marketRuleId);
}
/**
* Encode a REQ_MARKET_RULE message.
*/
reqCompletedOrders(apiOnly: boolean): void {
if (this.serverVersion < MIN_SERVER_VER.REQ_COMPLETED_ORDERS) {
return this.emitError(
"It does not support completed orders requests.",
ErrorCode.UPDATE_TWS,
ErrorCode.NO_VALID_ID,
);
}
this.sendMsg(OUT_MSG_ID.REQ_COMPLETED_ORDERS, apiOnly);
}
reqWshMetaData(reqId: number): void {
if (this.serverVersion < MIN_SERVER_VER.WSHE_CALENDAR) {
return this.emitError(
"It does not support WSHE Calendar API.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
this.sendMsg(OUT_MSG_ID.REQ_WSH_META_DATA, reqId);
}
reqCancelWshMetaData(reqId: number): void {
if (this.serverVersion < MIN_SERVER_VER.WSHE_CALENDAR) {
return this.emitError(
"It does not support WSHE Calendar API.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
this.sendMsg(OUT_MSG_ID.CANCEL_WSH_META_DATA, reqId);
}
reqWshEventData(reqId: number, wshEventData: WshEventData): void {
if (this.serverVersion < MIN_SERVER_VER.WSHE_CALENDAR) {
return this.emitError(
"It does not support WSHE Calendar API.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
if (
this.serverVersion < MIN_SERVER_VER.WSH_EVENT_DATA_FILTERS &&
(wshEventData.filter?.length ||
wshEventData.fillWatchlist ||
wshEventData.fillPortfolio ||
wshEventData.fillCompetitors)
) {
return this.emitError(
"It does not support WSH event data filters.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
if (
this.serverVersion < MIN_SERVER_VER.WSH_EVENT_DATA_FILTERS_DATE &&
(wshEventData.startDate ||
wshEventData.endDate ||
wshEventData.totalLimit)
) {
return this.emitError(
"It does not support WSH event data date filters.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
const tokens: unknown[] = [
OUT_MSG_ID.REQ_WSH_EVENT_DATA,
reqId,
wshEventData.conId,
];
if (this.serverVersion >= MIN_SERVER_VER.WSH_EVENT_DATA_FILTERS) {
tokens.push(wshEventData.filter);
tokens.push(wshEventData.fillWatchlist);
tokens.push(wshEventData.fillPortfolio);
tokens.push(wshEventData.fillCompetitors);
}
if (this.serverVersion >= MIN_SERVER_VER.WSH_EVENT_DATA_FILTERS_DATE) {
tokens.push(wshEventData.startDate);
tokens.push(wshEventData.endDate);
tokens.push(wshEventData.totalLimit);
}
this.sendMsg(tokens);
}
reqCancelWshEventData(reqId: number): void {
if (this.serverVersion < MIN_SERVER_VER.WSHE_CALENDAR) {
return this.emitError(
"It does not support WSHE Calendar API.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
this.sendMsg(OUT_MSG_ID.CANCEL_WSH_EVENT_DATA, reqId);
}
reqUserInfo(reqId: number): void {
if (this.serverVersion < MIN_SERVER_VER.USER_INFO) {
return this.emitError(
"It does not support user info requests.",
ErrorCode.UPDATE_TWS,
reqId,
);
}
this.sendMsg(OUT_MSG_ID.REQ_USER_INFO, reqId);
}
}