UNPKG

@fraserdarwent/xapi-node

Version:

This project is made possible to get data from Forex market, execute market or limit order with NodeJS/JS through WebSocket connection

475 lines 20.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.XAPI = exports.DefaultRateLimit = exports.DefaultHostname = void 0; const Listener_1 = require("../modules/Listener"); const logger4_1 = require("logger4"); const Log_1 = require("../utils/Log"); const __1 = require(".."); const Enum_1 = require("../enum/Enum"); const Socket_1 = require("./Socket/Socket"); const Stream_1 = require("./Stream/Stream"); exports.DefaultHostname = "ws.xapi.pro"; exports.DefaultRateLimit = 850; class XAPI extends Listener_1.Listener { constructor({ accountId, password, type, appName = undefined, host = undefined, rateLimit = undefined, logger = new logger4_1.EmptyLogger(), safe = undefined, subscribeTrades = undefined, }) { super(); this._rateLimit = exports.DefaultRateLimit; this._tryReconnect = false; this._positions = {}; this._positionsUpdated = null; this._serverTime = null; this.timer = { interval: [], timeout: [], }; this.orders = {}; Log_1.changeLogger(logger); if (logger.path === null && typeof window === "undefined" && typeof module !== "undefined" && module.exports) { Log_1.Log.info("Logger path is not defined (this means Logger4 will not saving logs)"); } this._rateLimit = rateLimit === undefined ? exports.DefaultRateLimit : rateLimit; this.account = { type: type.toLowerCase() === "real" ? "real" : "demo", accountId, appName, host: host === undefined ? exports.DefaultHostname : host, safe: safe === true, subscribeTrades: subscribeTrades !== false, }; this.Socket = new Socket_1.Socket(this, password); this.Stream = new Stream_1.Stream(this); if (this.account.safe) { Log_1.Log.info("[TRADING DISABLED] tradeTransaction command is disabled in config (safe = true)"); } this.Stream.onConnectionChange((status) => { if (status !== __1.ConnectionStatus.CONNECTING) { Log_1.Log.hidden("Stream " + (status === __1.ConnectionStatus.CONNECTED ? "open" : "closed"), "INFO"); if (this.Socket.status === __1.ConnectionStatus.CONNECTED) { if (this.isReady) { this.Stream.ping().catch((e) => { Log_1.Log.error("Stream: ping request failed (XAPI.ts:170)"); }); if (this.isSubscribeTrades) { this.Socket.send .getTrades(true) .catch() .then(() => { if (this.isReady) { this.callListener(Enum_1.Listeners.xapi_onReady); } }); } else { this.callListener(Enum_1.Listeners.xapi_onReady); } } this.callListener(Enum_1.Listeners.xapi_onConnectionChange, [status]); } } }); this.Socket.onConnectionChange((status) => { if (status !== __1.ConnectionStatus.CONNECTING) { Log_1.Log.hidden("Socket " + (status === __1.ConnectionStatus.CONNECTED ? "open" : "closed"), "INFO"); if (status === __1.ConnectionStatus.DISCONNECTED) { this.Stream.session = ""; this.stopTimer(); } if (this.Stream.status === __1.ConnectionStatus.CONNECTED) { this.callListener(Enum_1.Listeners.xapi_onConnectionChange, [status]); } } }); this.Socket.listen.login((data, time, transaction) => { Log_1.Log.hidden("Login is successful (userId = " + this.accountId + ", accountType = " + this.accountType + ")", "INFO"); this.Stream.session = data.streamSessionId; if (this.isReady) { this.Stream.ping().catch((e) => { Log_1.Log.error("Stream: ping request failed (XAPI.ts:206)"); }); this.Socket.send .getTrades(true) .catch() .then(() => { if (this.isReady) { this.callListener(Enum_1.Listeners.xapi_onReady); } }); } }); this.Socket.listen.getTrades((data, time, transaction) => { const { sent } = transaction.request; if (sent !== null && sent.elapsedMs() < 1000) { const obj = {}; data.forEach((t) => { if (this._positions[t.position] === undefined || this._positions[t.position].value !== null) { obj[t.position] = { value: __1.Utils.formatPosition(t), lastUpdated: sent, }; } }); Object.values(this._positions).forEach((t) => { if (obj[t.position] === undefined && t.value !== null) { if (t.lastUpdated.elapsedMs() <= 1000) { obj[t.position] = t; } } }); this._positions = obj; this._positionsUpdated = new __1.Time(); } else { Log_1.Log.hidden("getTrades transaction (" + transaction.transactionId + ") is ignored"); } }); this.Stream.listen.getTrades((t, time) => { if (t.cmd === __1.CMD_FIELD.BALANCE || t.cmd === __1.CMD_FIELD.CREDIT) { return; } if (t.type === __1.TYPE_FIELD.PENDING && t.cmd !== __1.CMD_FIELD.BUY_LIMIT && t.cmd !== __1.CMD_FIELD.SELL_LIMIT && t.cmd !== __1.CMD_FIELD.BUY_STOP && t.cmd !== __1.CMD_FIELD.SELL_STOP) { this.callListener(Enum_1.Listeners.xapi_onPendingPosition, [ __1.Utils.formatPosition(t), ]); } else if (t.state === "Deleted") { if (this._positions[t.position] !== undefined && this._positions[t.position].value !== null) { this._positions[t.position] = { value: null, lastUpdated: time }; this.callListener(Enum_1.Listeners.xapi_onDeletePosition, [ __1.Utils.formatPosition(t), ]); } } else if (this._positions[t.position] === undefined || this._positions[t.position].value !== null) { if (this._positions[t.position] !== undefined) { const { value } = this._positions[t.position]; if (value) { const changes = __1.Utils.getObjectChanges(value, __1.Utils.formatPosition(t)); if (Object.keys(changes).length > 0) { this.callListener(Enum_1.Listeners.xapi_onChangePosition, [ __1.Utils.formatPosition(t), ]); } } } else { this.callListener(Enum_1.Listeners.xapi_onCreatePosition, [ __1.Utils.formatPosition(t), ]); } this._positions[t.position] = { value: __1.Utils.formatPosition(t), lastUpdated: time, }; } }); this.Socket.listen.getServerTime((data, time, transaction) => { if (transaction.response.received !== null && transaction.request.sent !== null) { const dif = transaction.response.received.getDifference(transaction.request.sent); this._serverTime = { timestamp: data.time, ping: dif, received: transaction.response.received, }; } }); this.Stream.listen.getTradeStatus((s, time) => { if (s.requestStatus !== __1.REQUEST_STATUS_FIELD.PENDING) { const { resolve, reject } = this.orders[s.order] || {}; delete s.price; if (resolve !== undefined && reject !== undefined) { if (s.requestStatus === __1.REQUEST_STATUS_FIELD.ACCEPTED) { resolve(s); } else { reject(s); } delete this.orders[s.order]; } else { this.orders[s.order] = { order: s.order, reject: undefined, resolve: undefined, data: s, time, }; } } }); this.onReady(() => { this.stopTimer(); if (this.isSubscribeTrades) { this.Stream.subscribe.getTrades().catch((e) => { Log_1.Log.error("Stream: getTrades request failed (XAPI.ts:311)"); }); this.Stream.subscribe.getTradeStatus().catch((e) => { Log_1.Log.error("Stream: getTrades request failed (XAPI.ts:314)"); }); } this.Socket.send.getServerTime().catch((e) => { Log_1.Log.error("Socket: getServerTime request failed (XAPI.ts:318)"); }); this.timer.interval.push(setInterval(() => { if (this.Socket.status === __1.ConnectionStatus.CONNECTED && !this.Socket.isQueueContains("ping")) { this.Socket.ping().catch((e) => { Log_1.Log.error("Socket: ping request failed (XAPI.ts:324)"); }); } if (this.Stream.status === __1.ConnectionStatus.CONNECTED && !this.Stream.isQueueContains("ping")) { this.Stream.ping().catch((e) => { Log_1.Log.error("Stream: ping request failed (XAPI.ts:330)"); }); } this.timer.timeout.forEach((i) => clearTimeout(i)); this.timer.timeout = []; this.timer.timeout.push(setTimeout(() => { if (this.Socket.status === __1.ConnectionStatus.CONNECTED && !this.Socket.isQueueContains("getServerTime")) { this.Socket.send.getServerTime().catch((e) => { Log_1.Log.error("Socket: getServerTime request failed (XAPI.ts:339)"); }); } }, 1000)); if (this.isSubscribeTrades) { this.timer.timeout.push(setTimeout(() => { if (this.Socket.status === __1.ConnectionStatus.CONNECTED && !this.Socket.isQueueContains("getTrades")) { this.Socket.send.getTrades(true).catch((e) => { Log_1.Log.error("Socket: getTrades request failed (XAPI.ts:348)"); }); } }, 2000)); } this.Socket.rejectOldTransactions(); this.Stream.rejectOldTransactions(); if (Object.keys(this.Socket.transactions).length > 20000) { this.Socket.removeOldTransactions(); } if (Object.keys(this.Stream.transactions).length > 20000) { this.Stream.removeOldTransactions(); } }, 19000)); if (this.isSubscribeTrades) { this.timer.interval.push(setInterval(() => { this.Stream.subscribe.getTrades().catch((e) => { Log_1.Log.error("Stream: getTrades request failed (XAPI.ts:365)"); }); this.Stream.subscribe.getTradeStatus().catch((e) => { Log_1.Log.error("Stream: getTrades request failed (XAPI.ts:368)"); }); }, 60000)); } this.timer.interval.push(setInterval(() => { if (this.Socket.status === __1.ConnectionStatus.CONNECTED) { Object.values(this.orders).forEach((order) => { if (order.time.elapsedMs() > 5000 && order.resolve !== undefined && order.reject !== undefined) { this.refreshOrderStatus(order.order); } }); } }, 5100)); }, "constructor"); } get logger() { return Log_1.Log; } get accountType() { return this.account.type; } get isTradingDisabled() { return this.account.safe; } get accountId() { return this.account.accountId; } get appName() { return this.account.appName; } get hostName() { return this.account.host; } get rateLimit() { return this._rateLimit; } get tryReconnect() { return this._tryReconnect; } get isSubscribeTrades() { return this.account.subscribeTrades; } get openPositions() { return this.positions.filter((t) => t.position_type === Enum_1.PositionType.open); } get limitPositions() { return this.positions.filter((t) => t.position_type === Enum_1.PositionType.limit); } get positionsUpdated() { return this._positionsUpdated; } get positions() { return Object.values(this._positions) .filter((t) => t.value !== null && (t.value.position_type === Enum_1.PositionType.limit || t.value.position_type === Enum_1.PositionType.open)) .map((t) => t.value); } get isConnectionReady() { return (this.Stream.status === __1.ConnectionStatus.CONNECTED && this.Socket.status === __1.ConnectionStatus.CONNECTED); } get isReady() { return (this.Stream.status === __1.ConnectionStatus.CONNECTED && this.Socket.status === __1.ConnectionStatus.CONNECTED && this.Stream.session.length > 0); } get serverTime() { if (this._serverTime === null) { return Date.now(); } else { const elapsedMs = this._serverTime.received.elapsedMs(); return Math.floor(this._serverTime.timestamp + this._serverTime.ping + (elapsedMs === null ? 0 : elapsedMs)); } } refreshOrderStatus(order) { this.Socket.send .tradeTransactionStatus(order) .then(({ returnData }) => { const { resolve, reject } = this.orders[order] || {}; if (resolve !== undefined && reject !== undefined && returnData.requestStatus !== __1.REQUEST_STATUS_FIELD.PENDING) { const obj = { requestStatus: returnData.requestStatus, order: returnData.order, message: returnData.message, customComment: returnData.customComment, }; if (returnData.requestStatus === __1.REQUEST_STATUS_FIELD.ACCEPTED) { resolve(obj); } else { reject(obj); } delete this.orders[order]; } }) .catch((e) => { Log_1.Log.error(e); }); } stopTimer() { this.timer.interval.forEach((i) => clearInterval(i)); this.timer.timeout.forEach((i) => clearTimeout(i)); this.timer = { interval: [], timeout: [] }; } connect() { this._tryReconnect = true; this.Stream.connect(); this.Socket.connect(); } disconnect() { return new Promise((resolve, reject) => { this.Stream.session = ""; this._tryReconnect = false; this.stopTimer(); this.Socket.stopTimer(); this.Stream.stopTimer(); this.Stream.closeConnection(); if (this.Socket.status) { this.Socket.logout() .catch() .then(() => { this.Socket.closeConnection(); Log_1.Log.info(this.account.accountId + " disconnected"); resolve(); }); } else { this.Socket.closeConnection(); Log_1.Log.info(this.account.accountId + " disconnected"); resolve(); } }); } getAccountCurrencyValue(anotherCurrency) { return Enum_1.Currency2Pair[anotherCurrency] === undefined ? Promise.reject(anotherCurrency + " is not relevant currency") : Promise.all([ this.Socket.send.getSymbol(Enum_1.Currency2Pair[anotherCurrency]), this.Socket.send.getProfitCalculation(1, __1.CMD_FIELD.BUY, 0, Enum_1.Currency2Pair[anotherCurrency], 1), ]).then((values) => { return (values[1].returnData.profit / values[0].returnData.contractSize); }); } getPriceHistory({ symbol, period = __1.PERIOD_FIELD.PERIOD_M1, ticks, startUTC = this.serverTime, }) { return (ticks === undefined ? this.Socket.send.getChartLastRequest(period, startUTC, symbol) : this.Socket.send.getChartRangeRequest(0, period, startUTC, symbol, ticks)).then((data) => { return { symbol, period, candles: data.returnData.rateInfos.map((candle) => { return [ candle.ctm, candle.open, candle.close + candle.open, candle.low + candle.open, candle.high + candle.open, candle.vol, ]; }), digits: data.returnData.digits, }; }); } onReady(callBack, key = null) { if (this.isReady) { callBack(); } return this.addListener(Enum_1.Listeners.xapi_onReady, callBack, key); } onReject(callBack, key = null) { return this.addListener(Enum_1.Listeners.xapi_onReject, callBack, key); } onConnectionChange(callBack, key = null) { return this.addListener(Enum_1.Listeners.xapi_onConnectionChange, callBack, key); } onCreatePosition(callBack, key = null) { return this.addListener(Enum_1.Listeners.xapi_onCreatePosition, callBack, key); } onDeletePosition(callBack, key = null) { return this.addListener(Enum_1.Listeners.xapi_onDeletePosition, callBack, key); } onChangePosition(callBack, key = null) { return this.addListener(Enum_1.Listeners.xapi_onChangePosition, callBack, key); } onPendingPosition(callBack, key = null) { return this.addListener(Enum_1.Listeners.xapi_onPendingPosition, callBack, key); } } exports.XAPI = XAPI; //# sourceMappingURL=XAPI.js.map