@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
JavaScript
"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