UNPKG

@stoqey/ibkr

Version:

NodeJS Interactive Brokers wrapper & utilities using @stoqey/ib

367 lines 21.9 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Orders = void 0; var rxjs_1 = require("rxjs"); var ib_1 = require("@stoqey/ib"); var IBKRConnection_1 = __importDefault(require("../connection/IBKRConnection")); var compact_1 = __importDefault(require("lodash/compact")); var identity_1 = __importDefault(require("lodash/identity")); var omit_1 = __importDefault(require("lodash/omit")); var pickBy_1 = __importDefault(require("lodash/pickBy")); var log_1 = require("../utils/log"); var awaitP_1 = __importDefault(require("../utils/awaitP")); var Portfolios_1 = __importDefault(require("../portfolios/Portfolios")); var instrument_utils_1 = require("../utils/instrument.utils"); var async_mutex_1 = require("async-mutex"); var log_utils_1 = require("../utils/log.utils"); var events_1 = require("../events"); var ibkrEvents = events_1.IBKREvents.Instance; var Orders = /** @class */ (function () { function Orders() { var _this = this; this.ib = null; this.openOrders = new Map(); this.cancelledOrders = new Map(); this.completedTrades = new Map(); this.openOrderQueue = []; this.mutex = new async_mutex_1.Mutex(); /** * getOrders */ this.getOrders = function () { return _this.asyncOpenOrders(); }; this.logOpenOrder = function (title, openOrder) { var _a, _b, _c, _d; try { var contract = openOrder.contract, order = openOrder.order; var symbol = (0, instrument_utils_1.getSymbolKey)(contract); var _e = order || {}, _f = _e.orderId, orderId = _f === void 0 ? "" : _f, _g = _e.action, action = _g === void 0 ? "" : _g, _h = _e.totalQuantity, totalQuantity = _h === void 0 ? 0 : _h, _j = _e.orderType, orderType = _j === void 0 ? "" : _j, lmtPrice = _e.lmtPrice, auxPrice = _e.auxPrice; var avgFillPrice = (_a = openOrder.orderStatus) === null || _a === void 0 ? void 0 : _a.avgFillPrice; var status = (_b = openOrder.orderStatus) === null || _b === void 0 ? void 0 : _b.status; (0, log_1.log)("Orders.".concat(title, " symbol=").concat(symbol, " orderId=").concat(orderId), "".concat(action, " ").concat(totalQuantity, " ").concat(orderType, " @").concat((_d = (_c = avgFillPrice !== null && avgFillPrice !== void 0 ? avgFillPrice : auxPrice) !== null && _c !== void 0 ? _c : lmtPrice) !== null && _d !== void 0 ? _d : 0, " => ").concat(status)); } catch (error) { } }; this.processOrderQueue = function () { return __awaiter(_this, void 0, void 0, function () { var portfoliosManager; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: portfoliosManager = Portfolios_1.default.Instance; return [4 /*yield*/, portfoliosManager.init()]; case 1: _a.sent(); return [4 /*yield*/, this.mutex.runExclusive(function () { return __awaiter(_this, void 0, void 0, function () { var order, contractId, orderId, entryPrice, trade; var _a, _b, _c, _d, _e, _f, _g, _h; return __generator(this, function (_j) { while (this.openOrderQueue.length > 0) { order = this.openOrderQueue.shift(); contractId = (_a = order === null || order === void 0 ? void 0 : order.contract) === null || _a === void 0 ? void 0 : _a.conId; orderId = (_b = order === null || order === void 0 ? void 0 : order.order) === null || _b === void 0 ? void 0 : _b.orderId; // Ignore if already Filled processed / completed if (this.completedTrades.has("".concat(orderId))) { continue; } ; // ignore cancelled orders if (this.cancelledOrders.has(orderId)) { continue; } // ignore pre - submitted orders but update it in openOrders if (((_c = order === null || order === void 0 ? void 0 : order.orderState) === null || _c === void 0 ? void 0 : _c.status) === ib_1.OrderStatus.PreSubmitted) { this.openOrders.set(contractId, order); continue; } ; // no logs here ------------------------------------------------ // no logs here ------------------------------------------------ this.logOpenOrder("processOrderQueue", order); if (contractId) { this.openOrders.set(contractId, order); } ; switch (((_d = order.orderState) === null || _d === void 0 ? void 0 : _d.status) || ((_e = order.orderStatus) === null || _e === void 0 ? void 0 : _e.status)) { case ib_1.OrderStatus.Filled: entryPrice = (_h = (_f = portfoliosManager.getEntryPrice(contractId)) !== null && _f !== void 0 ? _f : (_g = portfoliosManager.getLatestClosedPosition(contractId)) === null || _g === void 0 ? void 0 : _g.avgCost) !== null && _h !== void 0 ? _h : order.orderStatus.avgFillPrice; trade = { id: "".concat(order.order.orderId), instrument: order.contract, entryPrice: entryPrice, type: order.order.orderType, price: order.orderStatus.avgFillPrice, quantity: order.order.totalQuantity, action: order.order.action, date: new Date(), }; if (!this.completedTrades.has(trade.id)) { this.completedTrades.set(trade.id, trade); ibkrEvents.emit(events_1.IBKREVENTS.IBKR_SAVE_TRADE, trade); } if (contractId) this.openOrders.delete(contractId); break; case ib_1.OrderStatus.ApiCancelled: case ib_1.OrderStatus.Cancelled: // log(`Orders.syncOpenOrders`, `Order ${order.orderId} for contract cancelled`); if (contractId) { this.openOrders.delete(contractId); if (!this.cancelledOrders.has(orderId)) { this.cancelledOrders.set(orderId, order); } } ; break; case ib_1.OrderStatus.Submitted: // log(`Orders.syncOpenOrders`, `Order ${order.orderId} for contract submitted`); break; case ib_1.OrderStatus.Inactive: // log(`Orders.syncOpenOrders`, `Order ${order.orderId} for contract inactive`); break; case ib_1.OrderStatus.PendingCancel: // log(`Orders.syncOpenOrders`, `Order ${order.orderId} for contract pending cancel`); break; case ib_1.OrderStatus.PendingSubmit: // log(`Orders.syncOpenOrders`, `Order ${order.orderId} for contract pending submit`); break; case ib_1.OrderStatus.ApiPending: // log(`Orders.syncOpenOrders`, `Order ${order.orderId} for contract api pending`); break; case ib_1.OrderStatus.PreSubmitted: // log(`Orders.syncOpenOrders`, `Order ${order.orderId} for contract pre submitted`); break; case ib_1.OrderStatus.Unknown: // log(`Orders.syncOpenOrders`, `Order ${order.orderId} for contract unknown`); break; default: // log(`Orders.syncOpenOrders`, `Order ${order.orderId} for contract in an unknown state`); break; } } return [2 /*return*/]; }); }); })]; case 2: _a.sent(); return [2 /*return*/]; } }); }); }; /** * getOpenOrders */ this.asyncOpenOrders = function () { return __awaiter(_this, void 0, void 0, function () { var openOrders, orders; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, (0, rxjs_1.firstValueFrom)(this.ib.getOpenOrders())]; case 1: openOrders = _a.sent(); orders = (0, compact_1.default)(openOrders.all.map(function (order) { if (!order) { return; } _this.openOrderQueue.push(order); return order; })); return [4 /*yield*/, this.processOrderQueue()]; case 2: _a.sent(); return [2 /*return*/, orders]; } }); }); }; this.syncOpenOrders = function () { var portfoliosManager = Portfolios_1.default.Instance; portfoliosManager.init(); _this.GetOrders = _this.ib.getAutoOpenOrders(true).subscribe(function (openOrders) { openOrders.all.forEach(function (order) { if (!order) { return; } _this.openOrderQueue.push(order); }); _this.processOrderQueue(); }); }; /** * init */ this.init = function () { return __awaiter(_this, void 0, void 0, function () { var self, ib; return __generator(this, function (_a) { self = this; if (!self.ib) { ib = IBKRConnection_1.default.Instance.ib; self.ib = ib; this.syncOpenOrders(); } return [2 /*return*/]; }); }); }; this.parseOrder = function (orderPlaced, contractDetails) { if (contractDetails === null || contractDetails === void 0 ? void 0 : contractDetails.minTick) { if ((orderPlaced === null || orderPlaced === void 0 ? void 0 : orderPlaced.orderType) === ib_1.OrderType.STP_LMT || (orderPlaced === null || orderPlaced === void 0 ? void 0 : orderPlaced.orderType) === ib_1.OrderType.LMT) { if (orderPlaced === null || orderPlaced === void 0 ? void 0 : orderPlaced.lmtPrice) { orderPlaced.lmtPrice = Number((Math.round(orderPlaced.lmtPrice / contractDetails.minTick) * contractDetails.minTick).toFixed(2)); } } else { delete orderPlaced.lmtPrice; } if (orderPlaced === null || orderPlaced === void 0 ? void 0 : orderPlaced.auxPrice) { orderPlaced.auxPrice = Number((Math.round(orderPlaced.auxPrice / contractDetails.minTick) * contractDetails.minTick).toFixed(2)); } } ; var contract = (0, pickBy_1.default)((0, omit_1.default)(contractDetails.contract, ["primaryExch"]), identity_1.default); var order = (0, pickBy_1.default)(orderPlaced, identity_1.default); return { order: order, contract: contract }; }; /** * placeOrder */ this.placeOrder = function (contractDetails, orderToPlace) { return __awaiter(_this, void 0, void 0, function () { var _a, order, contract, _b, orderPlaced, error; return __generator(this, function (_c) { switch (_c.label) { case 0: _a = this.parseOrder(orderToPlace, contractDetails), order = _a.order, contract = _a.contract; (0, log_utils_1.logOrder)("Orders.placeOrder Placing order ".concat(order.orderId || ""), { order: order, contract: contract }); return [4 /*yield*/, (0, awaitP_1.default)(this.ib.placeNewOrder(contractDetails, order))]; case 1: _b = _c.sent(), orderPlaced = _b[0], error = _b[1]; if (error) { (0, log_utils_1.logOrder)("Orders.placeOrder Error placing order ".concat(error), { order: order, contract: contract }, true); return [2 /*return*/]; } if (orderPlaced) { // TODO save order tick, entry (0, log_utils_1.logOrder)("Orders.placeOrder Order placed id=".concat(orderPlaced || order.orderId), { order: order, contract: contract }); return [2 /*return*/, true]; } (0, log_utils_1.logOrder)("Orders.placeOrder Order NOT placed ".concat(order.orderId || ""), { order: order, contract: contract }, true); return [2 /*return*/, false]; } }); }); }; /** * modifyOrder */ this.modifyOrder = function (id, contractDetails, orderToPlace) { return __awaiter(_this, void 0, void 0, function () { var _a, order, contract; return __generator(this, function (_b) { try { _a = this.parseOrder(orderToPlace, contractDetails), order = _a.order, contract = _a.contract; this.ib.modifyOrder(id, contractDetails, order); // TODO save order tick, entry (0, log_utils_1.logOrder)("Orders.modifyOrder ".concat(id), { order: order, contract: contract }); return [2 /*return*/, true]; } catch (e) { (0, log_1.log)("Orders.modifyOrder", "Error modifying order ".concat(id)); return [2 /*return*/, false]; } return [2 /*return*/]; }); }); }; this.cancelOrder = function (orderId, orderCancel) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { try { this.ib.cancelOrder(orderId, orderCancel); // TODO save order tick, entry (0, log_1.log)("Orders.cancelOrder", "Order cancelled ".concat(orderId)); return [2 /*return*/, true]; } catch (e) { (0, log_1.log)("Orders.cancelOrder", "Error modifying order ".concat(orderId)); return [2 /*return*/, false]; } return [2 /*return*/]; }); }); }; this.cancelAllOrders = function (orderCancel) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { try { this.ib.cancelAllOrders(orderCancel); // TODO save order tick, entry (0, log_1.log)("Orders.cancelAllOrders", "Order placed ".concat(orderCancel)); return [2 /*return*/, true]; } catch (e) { (0, log_1.log)("Orders.cancelAllOrders", "Error modifying order ".concat(orderCancel)); return [2 /*return*/, false]; } return [2 /*return*/]; }); }); }; } Object.defineProperty(Orders, "Instance", { get: function () { return this._instance || (this._instance = new this()); }, enumerable: false, configurable: true }); Object.defineProperty(Orders.prototype, "orders", { get: function () { return Array.from(this.openOrders.values()); }, enumerable: false, configurable: true }); Object.defineProperty(Orders.prototype, "trades", { get: function () { return Array.from(this.completedTrades.values()); }, enumerable: false, configurable: true }); return Orders; }()); exports.Orders = Orders; exports.default = Orders; //# sourceMappingURL=Orders.js.map