@stoqey/ibkr
Version:
NodeJS Interactive Brokers wrapper & utilities using @stoqey/ib
367 lines • 21.9 kB
JavaScript
"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